这个话题首先得从apply开始说起。bindAll内部实现使用了bind,而bind的内部实现又使用了apply。所以明白apply是什么作为第一步是非常重要的!
利用apply来帮我们改变this
先来看个例子:
var some = function(){
console.log(this+"is here!");
}
some();
当我们执行这段代码的时候,得到的结果是
[object window] is here!
所以我么可以简单的理解,这里的this就是window,
它在global的环境下,的确它就是window,那为了改变this,我们怎么办呢,这时候apply就登场了,它可以改变调用范围内this的内容,如:
var some = function(){
console.log(this+"is here!");
}
some.apply("some");
执行后就会得到:
some is here!
看到apply帮我们改变了该范围内this的值,并且将this想要替换的值放在apply函数的第一个参数的位置,其后的参数继续是原来的函数的参数:
为什么需要bind?
首先也来看个例子:
function Person(name){
this.name = name;
this.greet = function(){
console.log("hello everyone, I am " + this.name);
};
}
var tom = new Person("Tom");
tom.greet();//成功打印<hello everyone, I am Tom>
上述代码可以很成功的执行,但如果我们做如下改变:
function Person(name){
this.name = name;
this.greet = function(){
console.log("hello everyone, I am " + this.name);
};
}
var tom = new Person("Tom");
var greet = tom.greet();
greet();//输出<hello everyone, I am undefined>
这里就不行了,原因在于上下文环境变了,以上代码相当于将本来是放在Person下的greet函数放到了全局下面执行,this得到的是window,而window没有name属性,所以只能输出undefined。同样如上可以使用apply来解决这个问题,如下:
function Person(name){
this.name = name;
this.greet = function(){
console.log("hello everyone, I am " + this.name);
};
}
var tom = new Person("Tom");
var greet = tom.greet();
greet.apply(tom);//输出<hello everyone, I am Tom>
这里将tom替换到this,肯定输出Tom了。其实bind也正是在干这件事情,它返回一个新的函数,这个新的函数拥有我们提供的上下文环境。来看一下bind方法的源代码:
return function(){
return function.apply(obj,args.concat(slice.call(arguments)));
}
可见,bind内部使用了apply来实现。再来看看underscore的bindAll方法,
function Person(name){
this.name = name;
this.greet = function(){
console.log("hello everyone, I am " + this.name);
};
}
var tom = new Person("Tom");
_.bindAll(tom,"greet");
var greet = tom.greet();
greet();//输出<hello everyone, I am Tom>
我个人更喜欢_.bindAll的方法,它的意思是将greet函数的上下文环境this永远置为tom,所以在任何地方调用都不会出现undefined,书写起来也更方便。bind会返回一个函数需要接收,而_.bindAll并不需要,更方便。
bind和bindAll在Backbone还有一些区别,如在Backbone中:
var ProductView = Backbone.View.extend({
initialize: function() {
_.bind(this.render, this);
this.model.bind('change', this.render);
}
});
这段代码不会成功执行,因为this.render并没有接收改变新的值,仍然为原来的,正确的写法为:
var ProductView = Backbone.View.extend({
initialize: function() {
this.render=_.bind(this.render, this);
this.model.bind('change', this.render);
//或者this.model.bind('change',_.bind(this.render,this));
}
});
如果不喜欢这种异步IO方式的同学,可能使用如下方法比较方便,我就是这种人!哈哈
var ProductView = Backbone.View.extend({
initialize: function() {
_.bindAll(this.render, this);
this.model.bind('change', this.render);
}
});
不知道讲清楚了没!应该吧~