一、this 是什么 ?
this是一个在每个函数作用域中自动定义的特殊标识符关键字。
this不是编写时绑定,而是运行时绑定,它依赖于函数调用的上下文条件。 this 绑定于函数声明的位置没有任何关系,而与函数被调用的方式紧密相连。
当一个函数被调用时,会建立一个称为执行环境的活动记录。这个记录包含函数式从何处(调用栈)被调用的,函数是如何被调用的,传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间被使用的this 引用。
调用点(call-site):函数在代码中被调用的位置(不是被声明的位置)。
二、如何决定this指向哪里?
1、默认绑定
独立函数调用,可以认为这种this规则是在没有其他规则适用时的默认规则。
function foo() {
console.log( this.a ); // this.a 解析为全局变量
//在改情况下,对此方法调用的this实施了默认绑定,所以this 指向全局对象
}
var a = 2;
foo(); // 2
注意:在严格模式(strict mode)中对于默认绑定来说全局对象是不合法的,this 将会被设置为undefined
2、隐含绑定
调用点是否有一个环境对象,也称为 拥有者或容器对象。我们不得不改变目标对象使它自身包含一个对函数的引用,而后使用这个函数引用属性来间接地(隐含地)将 this 绑定到这个对象上。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
调用点 使用 obj 环境来 引用 函数,所以你 可以说 obj 对象在函数被调用的时间点上“拥有”或“包含”这个 函数引用。
在 foo() 被调用的位置上,它被冠以一个指向 obj 的对象引用。当一个方法引用存在一个环境对象时,隐含绑定 规则会说:是这个对象应当被用于这个函数调用的 this 绑定。
隐含丢失
当一个 隐式绑定丢失了它的绑定,这通常意味着它会退回大默认绑定,根据strict mode状态,其结果不是全局对象就是 undefined。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数引用!
var a = "oops, global"; // `a` 也是一个全局对象的属性
bar(); // "oops, global"
3、明确绑定
call()和apply()它们接收的第一个参数都是一个用于 this 的对象,之后使用这个指定的 this 来调用函数。因为你已经直接指明你想让 this 是什么,所以我们称这种方式为 明确绑定。
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2 通过foo.call(obj)明确绑定,强制函数的this 指向 obj。
注意:就this的绑定角度讲,call()和apply()是完全一样的,但是在处理其他参数的方式上有所不同
硬绑定
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// `bar` 将 `foo` 的 `this` 硬绑定到 `obj`
// 所以它不可以被覆盖
bar.call( window ); // 2
我们来看看这个变种是如何工作的。我们创建了一个函数 bar(),在它的内部手动调用 foo.call(obj),由此强制 this 绑定到 obj 并调用 foo。无论你过后怎样调用函数 bar,它总是手动使用 obj 调用 foo。这种绑定即明确又坚定,所以我们称之为 硬绑定(hard binding)。
4、new 绑定
在传统的面向类语言中,“构造器”是附着在类上的一种特殊方法,当使用 new 操作符来初始化一个类时,这个类的构造器就会被调用。
在JS中,构造器 仅仅是一个函数,它们偶然地与前置的 new 操作符一起调用。它们不依附于类,它们也不初始化一个类。它们甚至不是一种特殊的函数类型。它们本质上只是一般的函数,在被使用 new 来调用时改变了行为。
当在函数前面被加入 new 调用时,也就是构造器调用时,下面这些事情会自动完成:
1)一个全新的对象会凭空创建(就是被构建)
2)这个新构建的对象会被接入原形链([[Prototype]]-linked)
3)这个新构建的对象被设置为函数调用的 this 绑定
4)除非函数返回一个它自己的其他 对象,否则这个被 new 调用的函数将 自动 返回这个新构建的对象。
5、绑定规则优先级
明确绑定优先级高于隐含绑定,new绑定的优先级高于隐含绑定;硬绑定优先级高于 new 绑定。
注意: new 和 call/apply 不能同时使用,也就是不能直接对比测试 new 绑定 和 明确绑定。但是我们依然可以使用 硬绑定 来测试这两个规则的优先级。
总结
为执行中的函数判定 this 绑定需要找到这个函数的直接调用点。找到之后,四种规则将会以这种优先顺序施用于调用点:
1、通过 new 调用?使用新构建的对象。
2、通过 call 或 apply(或 bind)调用?使用指定的对象。
3、通过持有调用的环境对象调用?使用那个环境对象。
4、默认:strict mode 下是 undefined,否则就是全局对象。