this 是一个令无数 JavaScript 编程者又爱又恨的知识点。它的重要性毋庸置疑,然而真正想掌握它却并非易事。希望本文可以帮助大家理解 this

JavaScript 中的 this

JavaScript 引擎在查找 this 时不会通过原型链一层一层的查找,因为 this 完全是在函数调用时才可以确定的,让我们来看下面几种函数调用的形式。

Function Invocation Pattern

普通的函数调用,这是我们使用较多的一种, foo 是以单独的变量出现而不是属性。其中的 this 指向全局对象。

function foo() {console.log(this)
}foo() // Window

Method Invocation Pattern

函数作为对象的方法调用,会通过 obj.func 或者 obj[func] 的形式调用。其中的 this 指向调用它的对象。

const obj = {name: 'lxfriday',getName(){console.log(this.name)}
}obj.getName() // lxfriday

Constructor Pattern

通过 new Constructor() 的形式调用,其 this 会指向新生成的对象。

function Person(name){this.name = name
}const person = new Person('lxfriday')
console.log(person.name) // lxfriday

Apply Pattern

通过 foo.apply(thisObj) 或者 foo.call(thisObj) 的形式调用,其中的 this 指向 thisObj。如果 thisObjnull 或者 undefined ,其中的 this 会指向全局上下文 Window(在浏览器中)。


掌握以上的几种函数调用形式就基本可以覆盖开发中遇到的常见问题了,下面我翻译了一篇文章,帮助你更深入的理解 this

本文接下来的内容翻译自 https://blog.bitsrc.io/what-is-this-in-javascript-3b03480514a7,作者 Rajat S,内容有删改,标题有改动。

如果你已经使用过一些 JavaScript 库,你一定会注意到一个特殊的关键字 this

this 在 JavaScript 中很常见,但是有很多开发人员花了很多时间来完全理解 this 关键字的确切功能以及在代码中何处使用。

在这篇文章中,我将帮助您深入了解 this 其机制。

在深入了解之前,请确保已在系统上安装了 Node 。然后,打开命令终端并运行 node 命令。

全局环境中的 this

this 的工作机制并不容易理解。为了理解 this 是如何工作的,我们将探索不同环境中的 this。首先我们从全局上下文开始。

在全局层面中,this 等同于全局对象,在 Node repl(交互式命令行) 环境中叫 global

$ node
> this === global
true

但上述情况只出现在 Node repl 环境中,如果我们在 JS 文件中跑相同的代码,我们将会得到不同的答案。

为了测试,我们创建一个 index.js 的文件,并添加下面的代码:

console.log(this === global);

然后通过 node 命令运行:

$ node index.js
false

出现上面情况的原因是在 JS 文件中, this 指向 module.exports,并不是指向 global

函数中的 this

Function Invocation Pattern

在函数中 this 的指向取决于函数的调用形式。所以,函数每次执行的时候,可能拥有不同的 this 指向。

index.js 文件中,编写一个非常简单的函数来检查 this 是否指向全局对象:

function fat() {console.log(this === global)
}
fat()

如果我们在 Node repl 环境执行上面的代码,将会得到 true,但是如果添加 use strict 到首行,将会得到 false,因为这个时候 this 的值为 undefined

为了进一步说明这一点,让我们创建一个定义超级英雄的真实姓名和英雄姓名的简单函数。

function Hero(heroName, realName) {this.realName = realName;this.heroName = heroName;
}
const superman= Hero("Superman", "Clark Kent");
console.log(superman);

请注意,这个函数不是在严格模式下执行的。代码在 node 中运行将不会出现我们预期的 SupermanClark Kent ,我们将得到 undefined

这背后的原因是由于该函数不是以严格模式编写的,所以 this 引用了全局对象。

如果我们在严格模式下运行这段代码,会因为 JavaScript 不允许给 undefined 增加属性而出现错误。这实际上是一件好事,因为它阻止我们创建全局变量。

最后,以大写形式编写函数的名称意味着我们需要使用 new 运算符将其作为构造函数来调用。将上面的代码片段的最后两行替换为:

const superman = new Hero("Superman", "Clark Kent");
console.log(superman);

再次运行 node index.js 命令,您现在将获得预期的输出。

构造函数中的 this

Constructor Pattern

JavaScript 没有任何特殊的构造函数。我们所能做的就是使用 new 运算符将函数调用转换为构造函数调用,如上一节所示。

进行构造函数调用时,将创建一个新对象并将其设置为函数的 this 参数。然后,从函数隐式返回该对象,除非我们有另一个要显式返回的对象。

hero 函数内部编写以下 return 语句:

return {heroName: "Batman",realName: "Bruce Wayne",
};

如果现在运行 node 命令,我们将看到 return 语句将覆盖构造函数调用。

return 语句尝试返回不是对象的任何东西时,将隐式返回 this

方法中的 this

Method Invocation Pattern

当将函数作为对象的方法调用时,this 指向该对象,然后将该对象称为该函数调用的接收者。

在下面代码中,有一个 dialogue 方法在 hero 对象内。通过 hero.dialogue() 形式调用时,dialogue 中的 this 就会指向 hero 本身。这里,hero 就是 dialogue 方法调用的接收者。

const hero = {heroName: "Batman",dialogue() {console.log(`I am ${this.heroName}!`);}
};
hero.dialogue();

上面的代码非常简单,但是实际开发时有可能方法调用的接收者并不是原对象。看下面的代码:

const saying = hero.dialogue();
saying();

这里,我们把方法赋值给一个变量,然后执行这个变量指向的函数,你会发现 this 的值是 undefined。这是因为 dialogue 方法已经无法跟踪原来的接收者对象,函数现在指向的是全局对象。

当我们将一个方法作为回调传递给另一个方法时,通常会发生接收器的丢失。我们可以通过添加包装函数或使用 bind 方法将 this 绑定到特定对象来解决此问题。

call、apply

Apply Pattern

尽管函数的 this 值是隐式设置的,但我们也可以通过 call()apply() 显式地绑定 this

让我们像这样重组前面的代码片段:

function dialogue () {console.log (`I am ${this.heroName}`);
}
const hero = {heroName: 'Batman',
};

我们需要将hero 对象作为接收器与 dialogue 函数连接。为此,我们可以使用 call()apply() 来实现连接:

dialogue.call(hero)
// or
dialogue.apply(hero)

需要注意的是,在非严格模式下,如果传递 null 或者 undefinedcallapply 作为上下文,将会导致 this 指向全局对象。

function dialogue() {console.log('this', this)
}
const hero = {heroName: 'Batman',
}
console.log(dialogue.call(null))

上述代码,在严格模式下输出 null,非严格模式下输出全局对象。

bind

当我们将一个方法作为回调传递给另一个函数时,始终存在丢失该方法的预期接收者的风险,导致将 this 参数设置为全局对象。

bind() 方法允许我们将 this 参数永久绑定到函数。因此,在下面的代码片段中,bind 将创建一个新 dialogue 函数并将其 this 值设置为 hero

const hero = {heroName: "Batman",dialogue() {console.log(`I am ${this.heroName}`);}
};
// 1s 后打印:I am Batman
setTimeout(hero.dialogue.bind(hero), 1000);

注意:对于用 bind 绑定 this 之后新生成的函数,使用 call 或者 apply 方法无法更改这个新函数的 this

箭头函数中的 this

箭头函数和普通函数有很大的不同,引用阮一峰 ES6入门第六章中的介绍:

  1. 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象

  2. 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误

  3. 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替

  4. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数

上面四点中,第一点尤其值得注意。this 对象的指向是可变的,但是在箭头函数中,它是固定的,它只指向箭头函数定义时的外层 this箭头函数没有自己的 this,所有绑定 this 的操作,如 call apply bind 等,对箭头函数中的 this 绑定都是无效的

让们看下面的代码:

const batman = this;
const bruce = () => {console.log(this === batman);
};
bruce();

在这里,我们将 this 的值存储在变量中,然后将该值与箭头函数内部的 this 值进行比较。node index.js 执行时将会输出 true

那箭头函数中的 this 可以做哪些事情呢?

箭头函数可以帮助我们在回调中访问 this。看一下我在下面写的 counter 对象:

const counter = {count: 0,increase() {setInterval(function() {console.log(++this.count);}, 1000);}
}
counter.increase();

运行上面的代码,会打印 NaN。这是因为 this.count 没有指向 counter 对象。它实际上指向全局对象。

要使此计数器工作,可以用箭头函数重写,下面代码将会正常运行:

const counter = {count: 0,increase () {setInterval (() => {console.log (++this.count);}, 1000);},
};
counter.increase ();

类中的 this

类是所有 JavaScript 应用程序中最重要的部分之一。让我们看看类内部 this 的行为。

一个类通常包含一个 constructor,其中 this 将指向新创建的对象。

但是,在使用方法的情况下,如果该方法以普通函数的形式调用,则 this 也可以指向任何其他值。就像一个方法一样,类也可能无法跟踪接收者。

我们用类重写上面的 Hero 函数。此类将包含构造函数和 dialogue() 方法。最后,我们创建此类的实例并调用该 dialogue 方法。

class Hero {constructor(heroName) {this.heroName = heroName;}dialogue() {console.log(`I am ${this.heroName}`)}
}
const batman = new Hero("Batman");
batman.dialogue();

constructor 中的 this 指向新创建的类实例。batman.dialogue() 调用时,我们将 dialogue() 作为 batman 接收器的方法调用。

但是,如果我们存储对 dialogue() 方法的引用,然后将其作为函数调用,则我们将再次失去方法的接收者,而 this 现在指向 undefined

为什么是指向 undefined 呢?这是因为 JavaScript 类内部隐式以严格模式运行。我们将 say() 作为一个函数调用而没有进行绑定。所以我们要手动的绑定。

const say = batman.dialogue.bind(batman);
say();

当然,我们也可以在构造函数内部绑定:

class Hero {constructor(heroName) {this.heroName = heroNamethis.dialogue = this.dialogue.bind(this)}dialogue() {console.log(`I am ${this.heroName}`)}
}

加餐:手写 call、apply、bind

callapply 的模拟实现大同小异,注意 apply 的参数是一个数组,绑定 this 都采用的是对象调用方法的形式。

Function.prototype.call = function(thisObj) {thisObj = thisObj || windowconst funcName = Symbol('func')const that = this // functhisObj[funcName] = thatconst result = thisObj[funcName](...arguments)delete thisObj[funcName]return result
}Function.prototype.apply = function(thisObj) {thisObj = thisObj || windowconst funcName = Symbol('func')const that = this // funcconst args = arguments[1] || []thisObj[funcName] = thatconst result = thisObj[funcName](...[thisObj, ...args])delete thisObj[funcName]return result
}Function.prototype.bind = function(thisObj) {thisObj = thisObj || windowconst that = this // funcconst outerArgs = [...arguments].slice(1)return function(...innerArgs) {return that.apply(thisObj, outerArgs.concat(innerArgs))}
}

最后

往期精彩:

  • 前端面试必会 | 一文读懂现代 JavaScript 中的变量提升 - let、const 和 var

  • 前端面试必会 | 一文读懂 JavaScript 中的闭包

  • 前端面试必会 | 一文读懂 JavaScript 中的作用域和作用域链

  • 前端面试必会 | 一文读懂 JavaScript 中的执行上下文

  • InterpObserver 和懒加载

  • 初探浏览器渲染原理

  • CSS 盒模型、布局和包含块

  • 详细解读 CSS 选择器优先级

关注公众号可以看更多哦。

感谢阅读,欢迎关注我的公众号 云影 sky,带你解读前端技术,掌握最本质的技能。关注公众号可以拉你进讨论群,有任何问题都会回复。

公众号

交流群

前端面试必会 | 一文读懂 JavaScript 中的 this 关键字相关推荐

  1. 带你一文读懂Javascript中ES6的Symbol

    带你一文读懂Javascript中ES6的Symbol 前言 基础类型 Symbol Symbol.for 与 Symbol.keyFor Symbol.iterator Symbol.search ...

  2. 一文读懂机器学习中的模型偏差

    一文读懂机器学习中的模型偏差 http://blog.sina.com.cn/s/blog_cfa68e330102yz2c.html 在人工智能(AI)和机器学习(ML)领域,将预测模型参与决策过程 ...

  3. 一文读懂机器学习中奇异值分解SVD

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 目录: 矩阵分解 1.1 矩阵分解作用 1.2 矩阵分解的方法一文 ...

  4. java中date类型如何赋值_一文读懂java中的Reference和引用类型

    简介 java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟jav ...

  5. 一文读懂密码学中的证书

    一文读懂密码学中的证书 之前的文章中,我们讲到了数字签名,数字签名的作用就是防止篡改和伪装,并且能够防止否认.但是要正确运用数字签名技术还有一个非常大的前提,那就是用来验证签名的公钥必须真正的属于发送 ...

  6. 一文读懂Java中File类、字节流、字符流、转换流

    一文读懂Java中File类.字节流.字符流.转换流 第一章 递归:File类: 1.1:概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. ...

  7. 一文读懂SpringBoot中的事件机制

    一文读懂SpringBoot中的事件机制?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法. 要"监听"事件,我们总是 ...

  8. 一文读懂信息安全中的恶意代码、病毒、木马、蠕虫......

    一文读懂信息安全中的恶意代码.病毒.木马.蠕虫...... 病毒:破坏计算机功能或数据,以破坏为主,传染其他程序的方式是通过修改其他程序来把自身或其变种复制进去完成的,典型的熊猫烧香 蠕虫:通过网络的 ...

  9. 原创 | 一文读懂机器学习中的shapley值方法

    作者:贾恩东本文约2000字,建议阅读9分钟本文为你介绍更公平分配利益权重的一种算法--Shapley值方法. 本篇文章是数据派一文读懂系列的新年第一篇原创,在这里祝贺大家新年学业有新成就,生活有新气 ...

最新文章

  1. mysql 5.7笔记_关于MySql 5.7.29免安装版本的一个笔记
  2. 让别人不知道你的网站使用PHP
  3. [Swift通天遁地]九、拔剑吧-(9)创建支持缩放、移动、裁切的相机视图控制器
  4. Linux 文件基本属性
  5. 【PAT乙级】1084 外观数列 (20 分)
  6. sap*/pass无法登陆
  7. requestbody接收不到参数_使用Spring MVC解析嵌套参数在三种 ContentType 下的绑定方式...
  8. 计算机系统组织结构,第4章 操作系统计算机组织结构.ppt
  9. 直接修改html文本页面没变化,VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法分析...
  10. python情感分析算法_Python 情感分析
  11. mac 下 hadoop、spark 的安装及配置
  12. python条件语句代码例子_Python 炫技操作:条件语句的七种写法
  13. listWdiget控件
  14. pc机收集信息cpu\配置\网络信息
  15. Verilog的基本语法
  16. 旋转的描述【2】——等效旋转矢量与四元数
  17. UINO优锘:产品实施“八步法”让客户感受DCV实施的专业性
  18. spring boot 项目页面显示不出来
  19. 超轻薄笔记本电脑软件测试,2009年度-13英寸超轻薄笔记本年度横向评测
  20. 不用带眼镜 东芝裸眼3D电视9个角度随便看

热门文章

  1. 基于51单片机红外测距仪阈值报警仪表设计
  2. MCU引脚输出模式中推挽输出与开漏输出电路原理区别
  3. 数电课程设计——数字频率计
  4. 《我的助理辞职了》,最近非常火的一篇文章,受益颇多,共勉!
  5. BUG-‘Tokenizer’ object has no attribute ‘oov_token’
  6. 工控服务器什么作用,服务器与工控机的用处和区别在哪?
  7. 快速使用Android串口
  8. ESModule 系列 :构建下一代基础设施 PDN
  9. php两个数字进行比较大小
  10. C语言 写一个函数,输入一个4位数字,要求输出这4个数字字符,但每两个数字之间空一个空格。如输入1990,应该输出“1 9 9 0”