一. this绑定规则
函数调用位置决定了this的绑定对象,必须找到正确的调用位置判断需要应用下面四条规则中的哪一条。
1.1 默认绑定
看下面代码:
function foo() { ???console.log(this.a);}var a = 1;foo(); // 2调用foo的时候,this应用了默认绑定,this指向了全局对象,但是在严格模式下,那么全局对象将无法进行默认绑定,因此this会绑定到undefined
function foo() { ???‘use strict‘; ???console.log(this.a);}var a = 1;foo(); ?// TypeRrror: this is undefined严格模式下与 foo() 的调用位置无关:
function foo() { ???console.log( this.a );}var a = 2;(function(){ ???"use strict"; ???foo(); // 2})();1.2 隐式绑定
另一条需要考虑的规则是调用位置是否有上下文对象
function foo() { ???console.log(this.a);}var obj = { ???a: 2, ???foo: foo};obj.foo(); // 2但是无论是直接在 obj 中定义还是先定义再添加为引用属性, 这个函数严格来说都不属于 obj 对象,然而, 调用位置会使用 obj 上下文来引用函数, 因此你可以说函数被调用时 obj 对象“ 拥有” 或者“ 包含” 它
对象属性引用链中只有最顶层或者说最后一层会影响调用位置。 举例来说:
function foo() { ???console.log(this.a);}var obj2 = { ???a: 42, ???foo: foo};var obj1 = { ???a: 2, ???obj2: obj2};obj1.obj2.foo(); // 421.2.1 隐式丢失
一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象, 也就是说它会应用默认绑定, 从而把 this 绑定到全局对象或者 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"虽然 bar 是 obj.foo 的一个引用, 但是实际上, 它引用的是 foo 函数本身, 因此此时的 bar() 其实是一个不带任何修饰的函数调用, 因此应用了默认绑定。在js内置函数中如setTimeout也是如此:
function foo() { ???console.log(this.a);}var obj = { ???a: 2, ???foo: foo};var a = "oops, global"; // a 是全局对象的属性setTimeout(obj.foo, 100); // "oops, global"和下面伪代码类似:
function setTimeout(fn, delay) { ???// 等待 delay 毫秒 ???fn(); // <-- 调用位置!}1.3.显示绑定
call(...),apply(...)可以指定this的绑定对象(前者接收多个参数如call(this, param1, param2, param3...),后者接受一个或两个参数apply(this, [...]))
function foo() { ???console.log(this.a);}var obj = { ???a: 2};foo.call(obj); // 2通过 foo.call(..), 我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。 如果你传入了一个原始值( 字符串类型、 布尔类型或者数字类型) 来当作 this 的绑定对 象, 这个原始值会被转换成它的对象形式( 也就是 new String(..)、 new Boolean(..) 或者 new Number(..))。 这通常被称为“ 装箱”。
1.3.1 硬绑定
function foo() { ???console.log(this.a);}var obj = { ???a: 2};var bar = function() { ???foo.call(obj);};bar(); // 2setTimeout(bar, 100); // 2// 硬绑定的 bar 不可能再修改它的 thisbar.call(window); // 2我们创建了函数 bar(), 并在它的内部手动调用 了 foo.call(obj), 因此强制把 foo 的 this 绑定到了 obj。 无论之后如何调用函数 bar, 它 总会手动在 obj 上调用 foo。 这种绑定是一种显式的强制绑定, 因此我们称之为硬绑定。创建一个 i可以重复使用的辅助函数:
function foo(something) { ???console.log(this.a, something); ???return this.a + something;}//简单的辅助绑定函数function bind(fn, obj) { ???return function() { ???????return fn.apply(obj, arguments); ???};}var obj = { ???a: 2};var bar = bind(foo, obj);var b = bar(3); // 2 3console.log(b); // 5由于硬绑定是一种非常常用的模式, 所以在 ES5 中提供了内置的方法 Function.prototype.bind, bind(..) 会返回一个硬编码的新函数, 它会把参数设置为 this 的上下文并调用原始函数
1.3.2 API调用的“上下文”
function foo(el) { ???console.log(el, this.id);}var obj = { ???id: "awesome"};// 调用 foo(..) 时把 this 绑定到 obj[1, 2, 3].forEach(foo, obj);// 1 awesome 2 awesome 3 awesome这些函数实际上就是通过 call(..) 或者 apply(..) 实现了显式绑定, 这样你可以少些一些代码。
1.4. new绑定
在 JavaScript 中, 构造函数只是一些 使用 new 操作符时被调用的函数。 它们并不会属于某个类, 也不会实例化一个类。 实际上, 它们甚至都不能说是一种特殊的函数类型, 它们只是被 new 操作符调用的普通函数而已。使用 new 来调用函数, 或者说发生构造函数调用时, 会自动执行下面的操作:
1. 创建( 或者说构造) 一个全新的对象
2. 这个新对象会被执行 [[ 原型 ]] 连接
3. 这个新对象会绑定到函数调用的 this
4. 如果函数没有返回其他对象, 那么 new 表达式中的函数调用会自动返回这个新对象
二. 优先级
毫无疑问, 默认绑定的优先级是四条规则中最低的,来看看隐式绑定和显示绑定
function foo() { ???console.log(this.a);}var obj1 = { ???a: 2, ???foo: foo};var obj2 = { ???a: 3, ???foo: foo};obj1.foo(); // 2obj2.foo(); // 3obj1.foo.call(obj2); // 3obj2.foo.call(obj1); // 2显式绑定优先级更高, new 绑定和隐式绑定的优先级谁高谁低:
function foo(something) { ???this.a = something;}var obj1 = { ???foo: foo};var obj2 = {};obj1.foo(2);console.log(obj1.a); // 2obj1.foo.call(obj2, 3);console.log(obj2.a); // 3var bar = new obj1.foo(4);console.log(obj1.a); // 2console.log(bar.a); // 4可以看到 new 绑定比隐式绑定优先级高。 但是 new 绑定和显式绑定谁的优先级更高呢?
function foo(something) { ???this.a = something;}var obj1 = {};var bar = foo.bind( obj1 );bar( 2 );console.log( obj1.a ); // 2var baz = new bar(3);console.log( obj1.a ); // 2console.log( baz.a ); // 3bar 被硬绑定到 obj1 上, 但是 new bar(3) 并没有像我们预计的那样把 obj1.a 修改为 3。 相反,new 修改了硬绑定( 到 obj1 的) 调用 bar(..) 中的 this。 因为使用了 new 绑定, 我们得到了一个名字为 baz 的新对象, 并且 baz.a 的值是 3。之所以要在 new 中使用硬绑定函数, 主要目的是预先设置函数的一些参数, 这样在使用 new 进行初始化时就可以只传入其余的参数。 bind(..) 的功能之一就是可以把除了第一个 参数( 第一个参数用于绑定 this) 之外的其他参数都传给下层的函数( 这种技术称为“ 部 分应用”, 是“ 柯里化” 的一种)。 举例来说:
function foo(p1, p2) { ???this.val = p1 + p2;}//之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么// 反正使用 new 时 this 会被修改var bar = foo.bind(null, "p1");var baz = new bar("p2");baz.val; // p1p2js中this的绑定规则及优先级
原文地址:https://www.cnblogs.com/billyu/p/10063823.html