Javascript 当中的 this 与其他语言是完全不同的机制,很有可能会让一些编写其他语言的工程师迷惑。
1. 误以为 this 指向函数自身
根据 this 的英语语法,很容易将函数中出现的 this 理解为函数自身。在 javascript 当中函数作为一等公民,确实可以在调用的时候将属性值存储起来。但是如果使用方法不对,就会发生与实际预期不一致的情况。具体情况,请看下面代码
function fn(num){ this.count++; } fn.count = 0; for(var i=0;i<3;i++){ fn(i); } console.log(fn.count); // 0
如果 fn 函数里面的 this 指向自身函数,那么 count 属性的属性值就应该产生变化,但实际上却是纹丝不动。对于这个问题,有些人会利用作用域来解决,比如这么写
var data = { count:0 }; function fn(num){ data.count++; } for(var i=0;i<3;i++){ fn(i); } console.log(data.count); //3
又或者更直接的这么写
function fn(num){ fn.count++; } fn.count = 0; for(var i=0;i<3;i++){ fn(i); } console.log(fn.count);//3
虽然这两种方式都输出了正确的结果,但是却避开了 this 到底绑定在哪里的问题。如果对一个事物的工作原理不清晰,就往往会产生头痛治头,脚痛治脚的问题,从而导致代码变得的丑陋,而且维护性也会变得很差。
2. this神奇的绑定规则
2.1 默认绑定规则
第一种是最常见的 this 的绑定,看一下下面的代码
function fn(){ console.log(window === this); //浏览器环境 } fn(); //true
函数fn 是直接在全局作用域下调用的,没有带其他任何修饰,这种情况下,函数调用的时候使用了 this 的默认绑定,指向了全局对象。
这样就清楚了第一个例子中的 this 指向, fn 函数中的 this 指向了全局变量,所以 this.count++ 相当于 window.count++(浏览器环境下),当然不会对 fn 函数的count属性产生影响。
有一点要说明的是,上面种情况只能在非严格模式(strict mode)下才能发生,在严格模式下,会将 this 默认绑定为 undefined。以避免全局变量的污染。
2.2 隐式绑定规则
如果函数在以对象为上下文进行调用,那么 this 的绑定就会产生变化。this 会绑定到调用这个函数的对象,查看下面代码:
var obj = { a:1, fn:function(){ console.log(this.a); } } obj.fn(); //1
即使函数声明不在对象当中,this 指向仍会产生变化
function fn(){ console.log(this.a); } var obj = { a:1, fn:fn } obj.fn(); //1
由此可见,this 的绑定,不与函数定义的位置有关,而是与调用者和调用方式有关。
在隐式的绑定规则下,有一些特殊的地方,需要注意。
2.2.1 多层对象调用 this 的指向
function fn(){ console.log(this.a); } var obj = { a:1, obj2:obj2 } var obj2 = { a:2, obj3:obj3 } var obj3 = { a:3, fn:fn } obj.obj2.obj3.fn(); //3
在多层对象引用下,this 指向的是调用的函数的那个对象。
2.2.2 隐式赋值可能存在丢失现象
查看下面代码
function fn(){ console.log(this); } var obj = { fn:fn } var fun = obj.fn; fun(); //window
虽然 fn 引用了 obj.fun ,但是函数的调用方式,仍是不带任何修饰的,所以 this 还是绑定在了 window 上。
还有一种情况,容易让大家忽略,那就是传参的时候,其实会进行隐式赋值。
function fn(){ console.log(this); } function doFn(fn){ fn(); } var obj = { fn:fn } doFn(obj.fn); //window
隐式绑定 this 不是一种很推荐的方式,因为很有可能就发生丢失的情况,如果业务当中对 this 的绑定有要求,建议还是使用显示绑定的方式。
2.3 显式绑定规则
显示绑定就是利用函数原型上的 apply 与 call 方法来对 this 进行绑定。用法就是把想要绑定的对象作为第一个参数传进去。
function fn(){ console.log(this); } var obj = {}; fn.call(obj); //{}
有些时候会想将函数的 this 绑定在某个对象上,但是不需要立即调用,这样的话,直接利用 call 或者 apply 是无法做的。
function fn(){ console.log(this); } function bind(fn){ fn(); } var obj = { fn:fn } bind.call(obj,fn); //window
上面这个例子,看似好像可以,但实际上是 bind 函数的 this 绑定到了 obj 这个对象,但是 fn 仍然是没有任何修饰的调用,所以 fn 仍然是默认的绑定方式。
function fn(){ console.log(this); } function bind(fn,obj){ return function(){ fn.apply(obj,arguments); } } var obj = { fn:fn } var fun = bind(fn,obj); fun(); //obj