前端面试必会 | 一文读懂 JavaScript 中的 this 关键字
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
。如果 thisObj
是 null
或者 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 中运行将不会出现我们预期的 Superman
和 Clark 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
或者 undefined
给 call
、 apply
作为上下文,将会导致 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入门第六章中的介绍:
函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象;不可以当作构造函数,也就是说,不可以使用
new
命令,否则会抛出一个错误;不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用rest
参数代替;不可以使用
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
call
和 apply
的模拟实现大同小异,注意 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 关键字相关推荐
- 带你一文读懂Javascript中ES6的Symbol
带你一文读懂Javascript中ES6的Symbol 前言 基础类型 Symbol Symbol.for 与 Symbol.keyFor Symbol.iterator Symbol.search ...
- 一文读懂机器学习中的模型偏差
一文读懂机器学习中的模型偏差 http://blog.sina.com.cn/s/blog_cfa68e330102yz2c.html 在人工智能(AI)和机器学习(ML)领域,将预测模型参与决策过程 ...
- 一文读懂机器学习中奇异值分解SVD
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 目录: 矩阵分解 1.1 矩阵分解作用 1.2 矩阵分解的方法一文 ...
- java中date类型如何赋值_一文读懂java中的Reference和引用类型
简介 java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟jav ...
- 一文读懂密码学中的证书
一文读懂密码学中的证书 之前的文章中,我们讲到了数字签名,数字签名的作用就是防止篡改和伪装,并且能够防止否认.但是要正确运用数字签名技术还有一个非常大的前提,那就是用来验证签名的公钥必须真正的属于发送 ...
- 一文读懂Java中File类、字节流、字符流、转换流
一文读懂Java中File类.字节流.字符流.转换流 第一章 递归:File类: 1.1:概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. ...
- 一文读懂SpringBoot中的事件机制
一文读懂SpringBoot中的事件机制?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法. 要"监听"事件,我们总是 ...
- 一文读懂信息安全中的恶意代码、病毒、木马、蠕虫......
一文读懂信息安全中的恶意代码.病毒.木马.蠕虫...... 病毒:破坏计算机功能或数据,以破坏为主,传染其他程序的方式是通过修改其他程序来把自身或其变种复制进去完成的,典型的熊猫烧香 蠕虫:通过网络的 ...
- 原创 | 一文读懂机器学习中的shapley值方法
作者:贾恩东本文约2000字,建议阅读9分钟本文为你介绍更公平分配利益权重的一种算法--Shapley值方法. 本篇文章是数据派一文读懂系列的新年第一篇原创,在这里祝贺大家新年学业有新成就,生活有新气 ...
最新文章
- mysql 5.7笔记_关于MySql 5.7.29免安装版本的一个笔记
- 让别人不知道你的网站使用PHP
- [Swift通天遁地]九、拔剑吧-(9)创建支持缩放、移动、裁切的相机视图控制器
- Linux 文件基本属性
- 【PAT乙级】1084 外观数列 (20 分)
- sap*/pass无法登陆
- requestbody接收不到参数_使用Spring MVC解析嵌套参数在三种 ContentType 下的绑定方式...
- 计算机系统组织结构,第4章 操作系统计算机组织结构.ppt
- 直接修改html文本页面没变化,VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法分析...
- python情感分析算法_Python 情感分析
- mac 下 hadoop、spark 的安装及配置
- python条件语句代码例子_Python 炫技操作:条件语句的七种写法
- listWdiget控件
- pc机收集信息cpu\配置\网络信息
- Verilog的基本语法
- 旋转的描述【2】——等效旋转矢量与四元数
- UINO优锘:产品实施“八步法”让客户感受DCV实施的专业性
- spring boot 项目页面显示不出来
- 超轻薄笔记本电脑软件测试,2009年度-13英寸超轻薄笔记本年度横向评测
- 不用带眼镜 东芝裸眼3D电视9个角度随便看
热门文章
- 基于51单片机红外测距仪阈值报警仪表设计
- MCU引脚输出模式中推挽输出与开漏输出电路原理区别
- 数电课程设计——数字频率计
- 《我的助理辞职了》,最近非常火的一篇文章,受益颇多,共勉!
- BUG-‘Tokenizer’ object has no attribute ‘oov_token’
- 工控服务器什么作用,服务器与工控机的用处和区别在哪?
- 快速使用Android串口
- ESModule 系列 :构建下一代基础设施 PDN
- php两个数字进行比较大小
- C语言 写一个函数,输入一个4位数字,要求输出这4个数字字符,但每两个数字之间空一个空格。如输入1990,应该输出“1 9 9 0”