JavaScript中的this关键字的绑定

this 关键字在 JavaScript中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。

this的绑定对象的判定规则总结

  1. 由new调用?绑定到新创建的对象。
  2. 由call或者apply(或者bind)调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到undefined,否则绑定到全局对象。

一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略this绑定,你可以使用一个DMZ对象,比如ø = Object.create(null),以保护全局对象。
ES6中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this.


默认绑定

无法应用其他规则时的默认规则
在严格模式下绑定到undefined,否则绑定到全局对象(浏览器环境为”window”,Node环境中为”global”).

在非严格模式中的浏览器环境下输出1

// 非严格模式的浏览器环境
var a = "global";
function foo() {
  console.log(this.a);
  } 
foo(); // "global"

以下代码在非严格模式中的Node.js环境下输出undefined

// 非严格模式的Node环境
var a = "global";
function foo() {
  console.log(this.a);
  } 
foo(); // undefined

在严格模式下报错

// 严格模式
var a = "global";
function foo() {
  "use strict";
  console.log(this.a);
  } 
foo(); // TypeError: Cannot read property 'a' of undefined

隐式绑定

所谓隐式绑定,就是在没有显示引用apply(),call() 等方法时改变调用时的上下文对象.

// 隐式绑定
// 非严格模式的浏览器环境
function foo() {
  console.log( this.a );
} 
var a = "global"
var obj = {
  a: "obj",
  foo: foo
};
var bar = obj.foo;

obj.foo(); // "obj"
bar();  // "global"
setTimeout(obj.foo,100);  // "global"
// 隐式绑定
// 非严格模式的Node环境
function foo() {
  console.log( this.a );
} 
var a = "global"
var obj = {
  a: "obj",
  foo: foo
};
var bar = obj.foo;

obj.foo(); // "obj"
bar();  // undefined
setTimeout(obj.foo,100);  // undefined

对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo() 与定时器setTimeOut()来说,foo运行在全局环境,所以this指向全局环境,造成隐式绑定丢失.

同样地,在严格模式下bar() 会报错(无法绑定到全局对象)


显式绑定

那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,可以使用call() 或者apply()方法改变this 的指向.

call()apply() 的区别在于参数的不同,call()接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。

// 显示绑定
function foo(b,c) {
  console.log(this.a + b + c);
}
var obj = {
  a:1
}

// call(),1+2+3=6
foo.call(obj,2,3);  // 6

// apply(),1+2+3=6
foo.apply(obj,[2,3]);  // 6

通过foo.call(..),我们可以在调用foo时强制把它的this绑定到obj上。
如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(..)new Boolean(..)或者new Number(..))。这通常被称为“装箱”。

另外,使用bind()方法可以永久改变调用时this的指向,解决绑定丢失问题.但是要注意bind 只生效一次.

//  bind()
function foo(){
  console.log(this.a);
}
var bar = foo.bind({a:1});
var baz = bar.bind({a:2});  //bar已经使用过bind(),这里绑定不生效
bar();  // 1
baz();  // 1

new绑定

JavaScript中的new与其他语言不太一样.
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

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

整个过程可以理解为:

// new 绑定

// Foo没有返回值
function Foo() {
  this.a = 1;   
}
var foo = new Foo();
// Foo的实例foo连接到Foo的原型
console.log(foo.__proto__ === Foo.prototype);  // true
console.log(foo.a);   // 1

// Bar有返回值
function Bar() {
  this.a = 1;
  return {a:2}
}
var bar = new Bar()
// Bar的实例对象bar没有连接到Bar的原型
console.log(bar.__proto__ === Bar.prototype);   // false
console.log(bar.a);   // 2

优先级

显示绑定 > 隐式绑定 > 默认绑定
new绑定 > 隐式绑定 > 默认绑定

现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:

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

绑定例外

在一些特定场景不符合以上4条规则

  • null或者undefined作为this的绑定对象传入callapply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:
function foo() {
  console.log(this.a);
} 
var a = 2;
foo.call( null ); // 2
  • 间接引用
function foo() {
  console.log( this.a );
} 
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
// 只执行函数执行时处于全局环境下,因此使用默认绑定
(p.foo = o.foo)(); // 2
p.foo();  // 4
  • 封装软绑定,当手动把this绑定到指定对象的时间

箭头函数中的this

箭头函数不会创建自己的this,它从会从自己的作用域链的上一层继承this。
在箭头函数中,this与封闭词法上下文的this保持一致。在全局代码中,它将被设置为全局对象

function foo() {
  console.log(this.a);
}
let obj = {
  a:"obj",
  b:foo,
  c:()=>console.log(this.a),
  d:
}
obj.b();  // "obj"
obj.c();  // undefined

参考文献:
MDN:this
《你不知道的JS》(上卷)

猜你喜欢

转载自blog.csdn.net/weixin_40870788/article/details/80862631