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

发布于 2022-10-04 11:28:40 字数 3713 浏览 113 评论 0

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

一、this 是什么?


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

二、为什么要用 this ?


this 提供了一种更优雅的方式来隐式 “传递” 一个对象引用,让我们可以避免显示传递上下文对象引起的代码混乱,因此可以将 API 设计 得更加简洁并且易于复用。

三、4 条绑定规则


我们普遍知道的规则是,谁调用 this , this 指向谁。但这只是下面要讲的规则中的一条。

1、默认绑定

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

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

浏览器环境下,在非严格模式中,var 在全局作用域中定义的变量会自动添加到 window 对象下,在全局环境下执行函数 run , 就理解为 run 在 window 中被调用,此时的 run 函数是定义在 window 对象中的。上面讲 this 指向什么完全取决于函数在哪里被调用,所以此时 this 指向 window, window.a 自然输出 1。

严格模式中,var 声明的变量不会自动绑定到 window 对象下,同理 run 函数也不是定义在 window 对象中的, run() 独立执行,执行 RHS 右查询 this ,查无此值,给到 undefined , undefined.a 自然报错,且报错类型为 TypeError , 类型错误,因为 undefined 不是一个对象。

默认绑定下,this 是 window 、undefined 二者其一,取决于是否是严格模式。

2、隐式绑定

这就是我们常说的,谁调用 this , this 指向谁,举例说明:

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

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

function run() { 
   console.log( this.a ); 
}
var obj = { 
   a: 2, 
   run 
};
var fn = obj.run; 
var a = "welcome"; 
fn();  
复制代码

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

function run() { 
    console.log( this.a );
}
var obj = { 
    a: 2, 
    run 
};
var a = "welcome"; 
setTimeout( obj.run, 100 ); 
复制代码

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

3、显示绑定

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

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

这里数组的forEach()方法,第二可选参数传递给函数的值一般用做 "this" 值。 如果这个参数为空, "undefined" 会传递给 "this" 值。

4、new 绑定

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

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

四、优先级

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

所以我们见到 this , 该根据此优先级规则来判别。

五、被忽略的 this

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

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

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

六、箭头函数下的 this

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

怪我太投入

暂无简介

0 文章
0 评论
21 人气
更多

推荐作者

花开柳相依

文章 0 评论 0

zyhello

文章 0 评论 0

故友

文章 0 评论 0

对风讲故事

文章 0 评论 0

Oo萌小芽oO

文章 0 评论 0

梦明

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文