this无疑是javascript中特别复杂的机制了,最使我们困扰的就是this的指向,仅以本文对我所理解的this做一个总结和记录。每一句话都是重点!文章参考《你不知道的javascript》。
首先需要对this有一个大概的理解:每个函数的this都是在调用时被绑定的,完全取决于函数的调用位置。
绑定规则
当我们找到函数的调用位置以后,需要判断应用了哪条绑定规则,绑定规则分为四种:默认绑定,隐式绑定,显式绑定,new绑定。
一,默认绑定
这是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认应用。
如下,这里的foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
function foo() { console.log(this.a); } var a = 2; foo(); //2
注意:虽然this的绑定规则完全取决于调用位置,但是只有foo()运行在非严格模式下默认绑定才能绑定到全局对象。严格模式下与foo()的调用位置无关。如果使用严格模式,那么全局对象将无法使用默认绑定,因此会绑定到undefined。
function foo() { "use strict"; console.log(this.a); } var a = 2; foo();
报错:
二,隐式绑定
这条规则需要考虑的是调用位置是否有上下文对象,或者说是否被某个对象拥有或包含。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象,像下面这个样子,调用foo()时this被绑定到obj,因此this.a和obj.a是一样的:
对象属性引用链中只有最后一层会影响调用位置。
function foo() { console.log(this.a); }; var obj2 = { a: 32, foo: foo } var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); //32
隐式丢失
这也是最常见的this绑定问题了,被隐式绑定的函数会丢失绑定对象,也就是说会应用默认绑定,从而把this绑定到全局对象或者undefined上(取决于是否严格模式)。
有以下几种情况:
1.虽然bar是obj.foo的引用,但是实际上它引用的是foo函数本身,此时的bar便是一个不带任何修饰的函数调用,因此应用了默认样式。
function foo() { console.log(this.a); }; var obj = { a: 2, foo: foo } var bar = obj.foo; var a = "global"; bar(); //global
2.参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值。
function foo() { console.log(this.a); }; function doFoo(fn) { fn(); } var obj = { a: 2, foo: foo } var a = "global"; doFoo(obj.foo); //global
3.参数传入内置函数时也是一样
function foo() { console.log(this.a); }; var obj = { a: 2, foo: foo } var a = "global"; setTimeout(obj.foo, 100); //global
三,显式绑定
如果不想在对象内部包含函数引用,而想在某个对象上强制调用函数,可以使用call()和apply()方法,它们的第一个参数时对象,会把这个对象绑定到this。称之为显式绑定。
强制把foo的this绑定到obj,无论之后如何调用bar,总是会在obj上调用。
function foo() { console.log(this.a); }; var obj = { a: 2 } var bar = function() { foo.call(obj); } bar(); //2 setTimeout(bar, 100); //2 bar.call(window); //2
四,new绑定
使用new来调用foo时,会构造一个新的对象并把它绑定到foo()调用中的this上。
function foo(a) { this.a = a; }; var bar = new foo(2); console.log(bar.a); //2
优先级
既然几种绑定规则讨论完了,那么我们来比较一下他们的优先级顺序。细节不做讨论,可以自行测试。总之结果是:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
也就是说:
如果函数在new中调用,则this指向的是新创建的对象;再看是否通过call,apply调用,是的话this就指向指定的那个对象;然后看是否在某个上下文调用,如果是,就指向这个上下文对象;最后若都没有就是默认绑定了。