this
是一个令无数 JavaScript 编程者又爱又恨的知识点。它的重要性毋庸置疑,然而真正想掌握它却并不是易事。但愿本文能够帮助你们理解 this
。javascript
JavaScript 引擎在查找 this
时不会经过原型链一层一层的查找,由于 this
彻底是在函数调用时才能够肯定的,让咱们来看下面几种函数调用的形式。前端
普通的函数调用,这是咱们使用较多的一种, foo
是以单独的变量出现而不是属性。其中的 this
指向全局对象。java
function foo() {
console.log(this)
}
foo() // Window
复制代码
函数做为对象的方法调用,会经过 obj.func
或者 obj[func]
的形式调用。其中的 this
指向调用它的对象。node
const obj = {
name: 'lxfriday',
getName(){
console.log(this.name)
}
}
obj.getName() // lxfriday
复制代码
经过 new Constructor()
的形式调用,其 this
会指向新生成的对象。面试
function Person(name){
this.name = name
}
const person = new Person('lxfriday')
console.log(person.name) // lxfriday
复制代码
经过 foo.apply(thisObj)
或者 foo.call(thisObj)
的形式调用,其中的 this
指向 thisObj
。若是 thisObj
是 null
或者 undefined
,其中的 this
会指向全局上下文 Window
(在浏览器中)。编程
掌握以上的几种函数调用形式就基本能够覆盖开发中遇到的常见问题了,下面我翻译了一篇文章,帮助你更深刻的理解 this
。数组
本文接下来的内容翻译自 blog.bitsrc.io/what-is-thi…,做者 Rajat S,内容有删改,标题有改动。浏览器
若是你已经使用过一些 JavaScript 库,你必定会注意到一个特殊的关键字 this
。bash
this
在 JavaScript 中很常见,可是有不少开发人员花了不少时间来彻底理解 this
关键字的确切功能以及在代码中何处使用。闭包
在这篇文章中,我将帮助您深刻了解 this
其机制。
在深刻了解以前,请确保已在系统上安装了 Node 。而后,打开命令终端并运行 node 命令。
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
。
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
命令,您如今将得到预期的输出。
Constructor Pattern
JavaScript 没有任何特殊的构造函数。咱们所能作的就是使用 new
运算符将函数调用转换为构造函数调用,如上一节所示。
进行构造函数调用时,将建立一个新对象并将其设置为函数的 this
参数。而后,从函数隐式返回该对象,除非咱们有另外一个要显式返回的对象。
在 hero
函数内部编写如下 return
语句:
return {
heroName: "Batman",
realName: "Bruce Wayne",
};
复制代码
若是如今运行 node
命令,咱们将看到 return
语句将覆盖构造函数调用。
当 return
语句尝试返回不是对象的任何东西时,将隐式返回 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
绑定到特定对象来解决此问题。
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
,非严格模式下输出全局对象。
当咱们将一个方法做为回调传递给另外一个函数时,始终存在丢失该方法的预期接收者的风险,致使将 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
。
箭头函数和普通函数有很大的不一样,引用阮一峰 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 ();
复制代码
类是全部 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 = heroName
this.dialogue = this.dialogue.bind(this)
}
dialogue() {
console.log(`I am ${this.heroName}`)
}
}
复制代码
call
和 apply
的模拟实现大同小异,注意 apply
的参数是一个数组,绑定 this
都采用的是对象调用方法的形式。
Function.prototype.call = function(thisObj) {
thisObj = thisObj || window
const funcName = Symbol('func')
const that = this // func
thisObj[funcName] = that
const result = thisObj[funcName](...arguments)
delete thisObj[funcName]
return result
}
Function.prototype.apply = function(thisObj) {
thisObj = thisObj || window
const funcName = Symbol('func')
const that = this // func
const args = arguments[1] || []
thisObj[funcName] = that
const result = thisObj[funcName](...[thisObj, ...args])
delete thisObj[funcName]
return result
}
Function.prototype.bind = function(thisObj) {
thisObj = thisObj || window
const that = this // func
const outerArgs = [...arguments].slice(1)
return function(...innerArgs) {
return that.apply(thisObj, outerArgs.concat(innerArgs))
}
}
复制代码
往期精彩:
关注公众号能够看更多哦。
感谢阅读,欢迎关注个人公众号 云影 sky,带你解读前端技术,掌握最本质的技能。关注公众号能够拉你进讨论群,有任何问题都会回复。