JS对象(this)绑定规则

摘录于《你不知道的js》(上)

  1. 由 new 调用?绑定到新创建的对象
  2. 由 call 或者 apply(或者 bind )调用?绑定到指定对象
  3. 由上下文对象调用?绑定到那个上下文对象
  4. 默认:在严格模式下绑定到 undefined,否则绑定到全局对象
  5. ES6 中的 this 绑定在箭头函数中不会使用以上四条标准,会继承外层函数调用的 this 绑定(无论 this 绑定到什么),而且箭头函数的绑定无法被修改(new 也不行)

默认绑定,绑定到全局对象

function foo() {
	console.log( this.a );
}
var a = 2;
foo(); // 2

隐式绑定,当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象

function foo() {
	console.log( this.a );
}
var obj = {
	a: 2,
	foo: foo
};
obj.foo(); // 2

对象属性引用链中只有最顶层会影响调用位置。举例来说:

function foo() {
	console.log( this.a );
}
var obj2 = {
	a: 42,
	foo: foo
};
var obj1 = {
	a: 2,
	obj2: obj2
};
obj1.obj2.foo(); // 42

隐式丢失

一个最常见的 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(); // "undefined"

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样。

function foo() {
	console.log( this.a );
}
function doFoo(fn) {
	// fn 其实引用的是 foo
	fn(); // <-- 调用位置!
}
var obj = {
	a: 2,
	foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "undefined"

显式绑定,JavaScript 提供的绝大多数函数以及你自己创建的所有函数都可以使用call(…) 和 apply(…) 方法,它们的第一个参数是一个对象,它们会把这个对象绑定到this ,接着在调用函数时指定这个 this 。因为你可以直接指定 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(…) )。这通常被称为“装箱”。

通过显示绑定的变种硬绑定解决绑定丢失问题

function foo() {
	console.log( this.a );
}
var obj = {
	a:2
};
var bar = function() {
	foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call( window ); // 2

创建硬绑定函数

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 3
console.log( b ); // 5

由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 Function.prototype.bind,bind(…) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数

function foo(something) {
	console.log( this.a, something );
	return this.a + something;
}
var obj = {
	a:2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5

new 绑定

在 JavaScript 中,构造函数只是一些使用 new 操作符时被调用的普通函数。它们并不会属于某个类,也不会实例化一个类。实际上并不存在所谓的 “构造函数”,只有对于函数的“构造调用”

使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this 。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
function foo(a) {
	this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2

使用 new 来调用 foo(…) 时,我们会构造一个新对象并把它绑定到 foo(…) 调用中的 this 上

最后如何判断 this 到底绑定到哪里?

  1. 函数是否在 new 中调用( new 绑定)?如果是的话 this 绑定的是新创建的对象。
var bar = new foo()
  1. 函数是否通过 call 、 apply (显式绑定)或者硬绑定调用?如果是的话, this 绑定的是指定的对象。
var bar = foo.call(obj2)
  1. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话, this 绑定的是那个上下文对象。
var bar = obj1.foo()
  1. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined ,否则绑定到全局对象。
var bar = foo()

注意 :

function foo() {
	console.log( this.a );
}
var a = 2;
foo.call( null ); // 2

一种非常常见的做法是使用 apply(…) 来“展开”一个数组,并当作参数传入一个函数。类似地, bind(…) 可以对参数进行柯里化(预先设置一些参数)

function foo(a,b) {
	console.log( "a:" + a + ", b:" + b );
}
// 把数组“展开”成参数
foo.apply( null, [2, 3] ); // a:2, b:3

// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3

如果想更安全地忽略 this 绑定,你可以使用一个DMZ对象,如 ∅ = Object.create( null ),以保护全局对象,用 ∅ 代替 null 传入

猜你喜欢

转载自blog.csdn.net/HAIYUANBOY/article/details/88085085
今日推荐