你不知道的JS系列——全面解析this

前言


任何足够先进的技术都和魔法无异。this 关键字是 JavaScript 中最复杂的机制之一,搞懂它很重要。javascript

1、this 是什么?


thisJavaScript 中的关键字,在常见的面向对象语言中都有 this 的身影,相较下 JavaScript 中的 this 比较特殊,特殊在它会在执行期间动态改变指向。this 通常定义在函数中,若是按英文解释,很容易产生误解,this 既不指向函数自身,也不指向函数的词法做用域。它在运行时进行绑定,它指向什么彻底取决于函数在哪里被调用。java

2、为何要用 this


this 提供了一种更优雅的方式来隐式“传递”一个对象引用,让咱们能够避免显示传递上下文对象引发的代码混乱,所以能够将 API 设计 得更加简洁而且易于复用。数组

3、4条绑定规则


咱们广泛知道的规则是,谁调用 thisthis 指向谁。但这只是下面要讲的规则中的一条。浏览器

一、默认绑定

咱们在写原生 JS 时,会直接定义一个函数,而后直接进行独立的函数调用,此时应用默认绑定规则。
举例说明:安全

function run(){
    console.log(this.a);
}
var a = 1;
run();  // 1
复制代码

浏览器环境下,在非严格模式中,var 在全局做用域中定义的变量会自动添加到 window 对象下,在全局环境下执行函数 run ,就理解为 runwindow 中被调用,此时的 run 函数是定义在 window 对象中的。上面讲 this 指向什么彻底取决于函数在哪里被调用,因此此时 this 指向 window, window.a 天然输出 1
严格模式中,var 声明的变量不会自动绑定到 window 对象下,同理 run 函数也不是定义在 window 对象中的, run() 独立执行,执行 RHS 右查询 this ,查无此值,给到 undefined , undefined.a 天然报错,且报错类型为 TypeError ,类型错误,由于 undefined 不是一个对象。app

默认绑定下, thiswindowundefined 两者其一,取决因而否是严格模式。

二、隐式绑定

这就是咱们常说的,谁调用 thisthis 指向谁。
举例说明:函数

function run(){
    console.log(this.a);
}
var obj = {
    a: 1,
    run
}
obj.run();  // 1
复制代码

注意: 隐式丢失的发生,就是咱们常说的 this 丢失,多发生在函数赋值。
举例说明:post

function run() { 
   console.log( this.a ); 
}
var obj = { 
   a: 2, 
   run 
};
var fn = obj.run; // 函数别名!
var a = "welcome"; // a 是全局对象的属性 
fn();  // "welcome"
复制代码

你产生的困惑发生于var fn = obj.run;赋值语句,很好理解,不要把问题复杂化。正如咱们所知的,JS 中函数即对象,obj.run表明对run函数对象的引用,但此时执行的是赋值语句,赋值语句右侧通常执行 RHS 右查询,即查询具体的值,因此此时fn直接指向run函数自己,fn()的执行等同于直接执行run(),因此你的疑惑天然消除。
再看一个例子:ui

function run() { 
    console.log( this.a );
}
var obj = { 
    a: 2, 
    run 
};
var a = "welcome"; // a 是全局对象的属性 
setTimeout( obj.run, 100 ); // "welcome"
复制代码

setTimeout函数第一个参数为函数,咱们给它起个名字fn,参数obj.run的传入发生隐式赋值fn = obj.run,讲到这里,再结合上面,你该明白了。this

三、显示绑定

call、apply、bind 实现的绑定咱们称为实现显示绑定,这里不作过多解释。
解释一个咱们经常忽略的可选参数,看下面一段代码:

function run(param) { 
    console.log( param, this.id ); 
}
var obj = { 
    id: "welcome"
}; 
[1, 2, 3].forEach( run, obj );  // 1 "welcome" 2 "welcome" 3 "welcome"
复制代码

这里数组的forEach()方法,第二可选参数传递给函数的值通常用作 "this" 值。 若是这个参数为空, "undefined" 会传递给 "this" 值。

四、new绑定

咱们都知道 new 一个函数,函数内部的 this 指向新生成的实例,但这是为何呢?咱们须要知道 new 的过程当中发生了什么,固然咱们这里不会讲开辟什么堆栈空间之类的,这不在咱们的讨论范围。
new 就是一个能够调用普通函数的操做符,使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操做:

建立(或者说构造)一个全新的对象。

这个新对象会被执行 [[ 原型 ]] 链接。

这个新对象会绑定到函数调用的 this。

若是函数没有返回其余对象,那么 new 表达式中的函数调用会自动返回这个新对象。

4、优先级

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

因此咱们见到 this ,该根据此优先级规则来判别。

5、被忽略的 this

一些状况下,咱们会在显示绑定的时候传入 null,例如Math.max.apply(null,[1,2,3]),咱们并不关心传入的 this ,只是传入一个占位置,此时应用默认绑定。 举个例子:

function run() { 
    console.log( this.a );
}
var a = 1; 
run.call(null); // 1
复制代码

这里建议传入一个更安全的对象Object.create(null),Object.create(null)建立的对象与{}的区别在于,{}上面还会有Object原型上的toString()等方法,而Object.create(null)什么都没有。我喜欢用穷徒四壁来形容{},那么Object.create(null)对比之下就是连四壁也没有。

6、箭头函数下的 this

咱们都知道箭头函数没有本身的 this,它的 this 来自于外层做用域,也由于箭头函数没有本身的 this 这一特性,它不能被用做构造函数。 箭头函数不使用上面 this 的四种标准规则,函数执行时,它会捕获外层做用域的 this ,一经绑定再也没法修改,而且高于 new 绑定。

说明

文章更多的内容是来自于《你不知道的JavaScript》上卷,固然加了本身的一些理解。没时间去看书的同窗,不妨看看,查漏补缺。
React 的同窗看完,能够再看下这篇 <理解:为何React事件处理中要绑定this> ,巩固下对 this 的理解。

相关文章
相关标签/搜索