call、apply、bind
1.call
我们知道,函数可通过调用call方法改变其内部的this指向,默认this指向window或global,下面来一段简单的代码:
function foo(){ return this.a; } var obj = { a:1 }; console.log(foo.call(obj));// 1
从结果来看,foo函数中的this绑定了obj,并返回了obj中a属性的值。
2.apply
apply与call基本相同,掌握了call也就理解了apply,只不过apply方法的第二个参数是用了一个数组的形式进行传递。
function sum(b,c){ return this.a + b + c; }; var obj = { a:1 }; console.log(sum.apply(obj,[2,3]));// 6
以上两个是改变函数this指向最常用也是最简单的两个方法。
现在我想在每两秒输出一次count的值,count值不断递增,通常我们是这样做的
var obj = { count:0, timer:function(){ var _this = this; setInterval(function(){ _this.count++; console.log(obj.count); },2000); } }; obj.timer();// 1, 2, 3,...
我们用_this引用上下文obj,以用于在setInterval回调函数中能访问到obj,这样写是没有错,但是我们现在有另外一种方式可以实现,es6中提供了箭头函数,使用箭头函数我们无须写function关键字,也不用担心this所在的作用域,请看
var obj = { count:0, timer:function(){ setInterval(()=>{ this.count++; console.log(obj.count); },2000); } }; obj.timer();// 1, 2, 3,...
这样写出了少写一行代码以外,也不用担心对this的困惑了。但是有一点,箭头函数虽好,但它是一个匿名函数,如果我们要在函数内部调用函数本身,该怎么办呢,
也许你会说,用callee,可是callee已将近被废弃,我们最好还是不要用的好。
如果你的代码要在函数内部调用自己时,可以给函数起一个名字,也就是用具名函数吧。但如果用具名函数,那this怎么办呢!这可真是一个鱼和熊掌不能兼得的问题呀!
不过你忘记了,如题,我们还有一个方法没有用到,那就是es5中的bind!这可是个好东西了,有了它,我们的代码可以这样
var obj = { count:0, timer:function(){ setInterval(function foo(){ this.count++; console.log(obj.count); }.bind(this),2000); } }; obj.timer();// 1, 2, 3,...
据说源码比较复杂,下面的代码是MDN提供的bind实现方式
Function.prototype.bind = function (oThis) { if (typeof this !== "function") { throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fBound && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; };
在Function的原型链上扩展的bind方法,返回一个新的函数。
this instanceof fBound && oThis? this: oThis;//用于判断函数是否被new调用,如果是this指向new出的实例,不是则使用bind中的第一个参数,为null则忽略 fNOP.prototype = this.prototype; //用一个空的fn继承this的原型 fBound.prototype = new fNOP(); //返回的函数与调用bind的函数共享原型链
我在chrome测试时,发现bind中如果传递null,输入结果不是我期望的,使用chrome默认的bind则不会,这说明上面贴出的代码与源码实现还是有很大差异的,如果要针对IE可再加上if(!Function.prototype.bind)判断,在IE兼容模式下7、8、11兼容性视图会输入undifined
下面是我的测试代码:
function test(something,a,b){ this.a = something+a+b; }; var obj = {}; var newFn = test.bind(obj,1,2); var res= new newFn(10); console.log(res.a);// 13 而使用 var newFn = test.bind(null,1,2);// undefined