[TLDR]Javascript中关于this机制的四条规则

372 阅读2分钟
  1. 默认绑定
  2. 隐式绑定
  3. 显式绑定
  4. new绑定

this提供了一种更优雅的方式来 隐式“ 传递” 一个对象引用, 因此可以将 API 设计得更加 简洁并且易于复用。

人们很容易把 this 理解成指向函数自身, 这个推断从英语的语法角度来说是说得通的。

this在任何情况下都不指向函数的词法作用域,this 的绑定和函数 声明的位置没有任何关系, 只 取决于函数的调用 方式。

在理解this的绑定过程之前,首先要理解调用位置。最重要的事分析调用栈,我们关心的调用位置就是档期那正在执行的函数的前一个调用中。

默认绑定

首先介绍最常用的调用类型:独立函数调用

function foo() { 
    console. log( this. a ); 
} 

var a = 2; 

foo(); // 2 

在本例中,函数调用时应用了this 的默认绑定,因此this只想全局变量

如果使用严格模式,那么全局对象将无法使用默认绑定,因此this会绑定到undefined

隐式绑定

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

function foo() {
 console. log( this. a ); 
} 

var obj2 = { a: 42, foo: foo };
var obj1 = { a: 2, obj2: obj2 }; 

obj1\. obj2\. foo();  // 42

隐式丢失

一个最常见 的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,比如将函数传入回调函数的时候。

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 ); // "oops, global"

参数传递其实是一种隐式赋值,因此我们传入的函数也会被隐式赋值。如果把函数传入语言的内置函数而不是自己声明的函数,结果是一样的。

显式绑定

可以使用函数的callapply方法

function foo() { 
  console. log( this. a ); 
} 

var obj = { a: 2 };

foo. call( obj ); // 2

通过foo.call(..),我们可以调用foo时强制把它的this绑定到ojb上。如果你传入一个原始值来当作this的绑定对象,这个原始值会被转换成它的对象形式。

new绑定

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

  1. 创建一个全新的对象

  2. 这个新对象会被执行**[[原型]]**连接

  3. 这个新对象会被绑定到函数调用的this

    function foo( a) { this. a = a; }

    var bar = new foo( 2);

    console. log( bar.a ); // 2

优先级

我们已经了解函数调用中this绑定的四条规则,你需要做的时找到函数的调用位置并判断应用哪条规则。但是,如果某个调用位置可以应用多条规则该怎么办?

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

绑定例外

被忽略的this

如果你把null或者undefined作为 this 的绑定对象传入call、 apply 或者 bind, 这些值在调 用时会被忽略, 实际应用的是默认绑定规则

间接引用

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 = o.foo 的 返回值是目标函数的引用, 因此调用位置 是 foo() 而不是 p.foo() 或者 o.foo()。 根据 我们之前说过的, 这里会应用默认绑定。 

箭头函数

ES6中介绍了一种无法使用这些规则的特殊函数类型,箭头函数

不适用this的四种标准规则,而是根据外层作用域来决定this

function foo() { 
    // 返回 一个 箭头 函数 
    return (a) => { 
        // this 继承 自 foo()
         console. log( this. a );
     }; 
}

var obj1 = { a: 2 }; 
var obj2 = { a: 3 }; 
var bar = foo.call( obj1 ); 

bar.call( obj2 ); // 2, 不是 3!

这其实跟ES6之前代码中的self = this机制一样,箭头函数就是像替代this机制,本质上来说。