我是FE,也是Fe

前端来源于不断的点滴积累。我一直在努力。

统计

留言簿(15)

阅读排行榜

评论排行榜

underscore中的function类函数解析

underscore是一个非常不错的基础javascript库,他提供了很多实用的方法,弥补了javascript原生 API调用的一些不足,读他的文档的时候,读到array,object,uitlity的时候已经非常兴奋了。觉得足够用了。但是我看到function这部分的时候,发现这些函数真的非常的有意义,结合源代码来看看这部分function相关的功能。

_.bind方法

最常见的方法。作用是改变默认的function中的this指向。需要说明的是在ECMA 5这个版本中function已经自带了一个bind方法,参见这里(该文章具体介绍了bind的集中使用场景)。bind的使用方法是:
_.bind(function, object, [*arguments]) 

 下面是一个使用demo:

var func = function(greeting){ 
    
//this指向的是bind的第二个参数
     //  greeting 是bind的第三个参数
    return greeting + ': ' + this.name 
};
// bind返回的是一个新的function对象
var newfunc = _.bind(func, {name : 'moe'}, 'hi');

func();


原本以为这个bind的源码会很简单,无非就是用apply返回新的function,但是看源码发现挺讲究:

 

_.bind = function bind(func, context) {
    
var bound, args;
    
//如果function存在原生的bind方法使用原生的bind
    if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
    
//不是function,抛异常
    if (!_.isFunction(func)) throw new TypeError;
    
//将后面的参数转化成数组
    args = slice.call(arguments, 2);
    
return bound = function() {
      
//如果当前的this已经指向的一个function的实例,就不需要再改变this的指向,因为此时的function已经作为一个构造函数在使用
      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
      
//否则function视为构造函数使用,要保证this为构造函数的实例
      ctor.prototype = func.prototype;
      
var self = new ctor;
      
//将function强制转换成一个类的构造函数
      var result = func.apply(self, args.concat(slice.call(arguments)));
      
//Object(result) === result 只有当result是Object时才会成立,基本的数据类型如number,string则不成立
      if (Object(result) === result) return result;
      
return self;
    };
  };
代码实现还是考虑了普通函数调用,构造函数调用,通过成员函数调用的情况,逻辑实现的很全面。

_.bindAll

bindAll方法可以将一个对象中所有的成员函数的this都指向这个对象,什么情况下对象的成员函数的this不指向对象呢?比如:

var buttonView = {
  label   : 'underscore',
  onClick : 
function(){ alert('clicked: ' + this.label); },
  onHover : 
function(){ console.log('hovering: ' + this.label); }
};
_.bindAll(buttonView);
//当成员函数作为事件监听的时候,因为默认的事件监听,this都会指向当前事件源
//
bindAll之后可以保证onClick中的this仍指向buttonView
jQuery('#underscore_button').bind('click', buttonView.onClick);

_.memoize(function, [hashFunction])

该方法可以缓存函数返回结果,如果一个函数计算需要很长的时间,多次反复计算可以只计算一次缓存结果,默认的缓存key是函数调用时的第一个参数,也可以自己定义function(第二个参数)来计算key

_.memoize = function(func, hasher) {
    
var memo = {};//缓存存放位置
     //_.indentity默认取数组第一个元素
    hasher || (hasher = _.identity);
    
return function() {
      
var key = hasher.apply(this, arguments);
      
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
    };
  };

delay : _.delay(function, wait, [*arguments])

delay方法在指定的wait后面执行函数与setTimeout功能一致

defer: _.defer(function, [*arguments])

defer也是延迟执行方法,不同的是他能保证在当前堆栈中的所有的代码跑完之后再执行function。其实就是setTimeout(fn,1);

throttle:_.throttle(function, wait)

throttle这个单词的意思是使减速,用于控制频繁触发的 function的的频率,比如,拖动页面滚动条时scroll方法会以很高的频率触发,如果在scroll的处理事件中做了很费时的操作,会导致浏览器假死,如果使用了throttle后,function被触发的频率可以降低。

document.body.onscroll = _.throttle(function(){
        console.log(
"scrolling:"+(document.body.scrollTop|| document.body.scrollTop);
    },
100); 

scroll事件默认50ms触发一次,但是使用throttle之后事件触发频率为100ms一次

debounce: _.debounce(function, wait, [immediate])

debounce 本意是“使反跳”,这个翻译是在让人看不明白。同样用于处理频繁触发的事件,处理方法时,对于频繁处理的时间,只在第一次触发(是否触发取决于immdiate 参数),和事件频繁触发最后一次触发(有最多wait的延时)。拿滚动事件为例,滚动事件50ms触发一次,如果设置wait为100ms。则在最后一次触发scroll事件时,也就是停止滚动时,在100ms后触发function。如果immediate参数为true,开始滚动时也会触发function

document.body.onscroll = _.debounce(function(){
        
//一次滚动过程触发两次该函数
        console.log("scrolling:"+(document.body.scrollTop|| document.body.scrollTop);
    },
100,true);

在整个滚动过程中触发function,对于只关注整个滚动前后变化的处理非常有用。

下面是_.debounce和_.throttle的源码:

_.debounce = function(func, wait, immediate) {
    
var timeout, result;
    
return function() {
          
var context = this, args = arguments;
          
var later = function() {
            timeout 
= null;//最后一次调用时清除延时
            if (!immediate) result = func.apply(context, args);
          };
          
var callNow = immediate && !timeout;
          
//每次func被调用,都是先清除延时再重新设置延时,这样只有最后一次触发func再经过wait延时后才会调用func
          clearTimeout(timeout);//
          timeout = setTimeout(later, wait);
          
//如果第一次func被调用 && immediate ->立即执行func
          if (callNow) result = func.apply(context, args);
          
return result;
    };
};

_.throttle 
= function(func, wait) {
    
var context, args, timeout, throttling, more, result;

    
//延时wait后将more  throttling 设置为false
    var whenDone = _.debounce(function(){ 
        more 
= throttling = false
    }, wait);
    
return function() {
        context 
= this; args = arguments;
        
var later = function() {
            timeout 
= null;
            
if (more) { //more:最后一次func调用时,确保还能再调用一次
                  result = func.apply(context, args);
            }
            whenDone();
        };
        
if (!timeout) timeout = setTimeout(later, wait);
        
if (throttling) {
            more 
= true;
        } 
else {
            
//每次触发func 有会保证throttling 设置为true
            throttling = true;
            result 
= func.apply(context, args);
        }
        
//每次触发func 在 wait延时后将 more  throttling 设置为false
        whenDone();
        
return result;
    };
};

once: _.once(function)

once能确保func只调用一次,如果用func返回一个什么对象,这个对象成了单例。源码也比较简单,无非就是用一个标志位来标示是否运行过,缓存返回值

_.once = function(func) {
    
var ran = false, memo;
    
return function() {
      
if (ran) return memo;
      ran 
= true;
      memo 
= func.apply(this, arguments);
      func 
= null;
      
return memo;
    };
  };

wrap: _.wrap(function, wrapper)

wrap可以将函数再包裹一层,返回一个新的函数,新的函数里面可以调用原来的函数,可以将原函数的处理结果再处理一次返回。类似与AOP切面。在函数处理前/后动态的添加一些额外的处理,下面是一个使用demo

var hello = function(name) { return "hello: " + name; };
//wrap返回一个新的函数
hello = _.wrap(hello, function(func) {
  
// 在新函数内部可以继续调用原函数
  return "before, " + func("moe"+ ", after";
});
hello();

wrap的源码:

_.wrap = function(func, wrapper) {
    
return function() {
     
//将原函数当新函数的一个参数传入
      var args = [func];
      push.apply(args, arguments);
      
return wrapper.apply(this, args);
    };
  };

compose: _.compose(*functions)

将多个函数处理过程合并,每个函数可以调用前面函数的运行结果,_.compose(func1,func2);相当于func1(func2())。看看他的源码:

_.compose = function() {
    
var funcs = arguments;
    
return function() {
      
var args = arguments;
      
//循环调用参数中的function
      for (var i = funcs.length - 1; i >= 0; i--) {
        args 
= [funcs[i].apply(this, args)];
      }
      
return args[0];//先调用的函数结果最为下一个函数的参数
    };
  };

after:_.after(count, function)

创建一个新的函数,当func反复调用时,count次才调用一次,比如:

function a(){
    alert(
"a");
}

var afterA = _.after(3,a);
afterA();
//调用
afterA();//不alert
afterA();//不alert
afterA();//调用


源码:

_.after = function(times, func) {
    
if (times <= 0return func();
    
return function() {
      
if (--times < 1) {
        
return func.apply(this, arguments);
      }
    };
  };


总结:
上面这些函数在开发中经常能用到,能解决很多特定的问题。undercore的源码也看得出非常老道,可以非常好的学习资料。

参考资料:
ES5中的bind介绍
从underscore.js的源码学习javascript
带注释的underscore源码



posted on 2012-11-08 13:14 衡锋 阅读(2855) 评论(2)  编辑  收藏 所属分类: javascriptWeb开发

评论

# re: underscore中的function类函数解析 2013-08-09 14:24 digno

function a(som){
alert(som);
}
afterA = _.after(3,a);
afterA("a"); // no
afterA("b"); // no
afterA("c"); // yes
afterA("d"); // yes
afterA("e"); // yes
afterA("f"); // yes   回复  更多评论   

# re: underscore中的function类函数解析[未登录] 2013-10-21 16:39 wj

@digno

很对  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航: