【JavaScript】'this'详解
文章结构
普通的 this
使用new操作符调用构造函数生成对象时,构造函数内的this为当前所new出来的对象。
1 | 代码一: |
输出完整的对象结构:
1 | Car {} |
下面的代码定义了一个对象benz,并调用该对象的printHistory(),由对象benz调用方法体,所以方法体内的this为对象benz本身。
1 | 代码二: |
输出完整的类结构:
1 | { printHistory: [Function] } |
以上两种情况,this的含义都很明确,即当前的对象本身。
外漏函数中的 this
1 | 代码三: |
执行代码后,控制台输出如下:
1 | D:\desk\JavaScript>node this.js |
以上代码打印的this直接为控制台全局对象GLOBAL或浏览器Window了。
这个现象会让很多人困惑。
要理解该现象的原因要从查看调用链去理解:被调用函数是一个对象的属性还是函数自己。
如果它被作为属性调用,那么this的值将变成该属性的对象,否则this的值将被默认赋值为控制台全局对象GLOBAL或浏览器的Window了。
嵌套函数中的 this
对 benz::printHistory()中增加一嵌套函数,如下代码:
1 | 代码四: |
控制台输出如下:
1 | D:\desk\JavaScript>node this.js |
以上代码,第一次打印this,仍是benz本身;但在嵌套函数中打印的this则为控制台全局对象或浏览器的Window了。
this不同于普通的对象声明,this的值无法在被嵌套的函数作用域中保持不变,这是JavaScript的缺陷。
但回想上文中“被调用函数是一个对象的属性还是函数自己”这一判断标准,嵌套函数并不是benz中的属性,所以this值为控制台全局对象或浏览器的Window也可以顺利理解了。
如果想在嵌套函数使用到上下文的this,有两种办法:
方法一:新声明一个变量持有上下文this
1 | 代码五: |
该段代码执行的控制台输出如下:
1 | D:\desk\JavaScript>node this.js |
方法二:使用[Function.prototype.bind(thisArg[, arg1[, arg2[, …]]])](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)
1 | 代码六: |
Function.prototype.bind()方法会返回一个新的函数体,新函数体的功能和原先函数保持一致,只是在新函数体内this的值为bind()方法参数中第一个参数的值。
bind()方法返回新函数后,就通过“bind(this) ()“直接被执行了。
由此,为控制台的两处打印信息都是benz本身:
1 | D:\desk\JavaScript>node this.js |
在维持上下文环境一致的实现选择上,bind()是更受推荐的方法。
由此,也引出下面的内容:如何篡改函数体中的this.
篡改函数中的 this
上文已经通过Function.prototype.bind()方法将嵌套函数中this的值指定为bind()的第一个参数值。JavaScript还有另外两个方法也可以实现同样的效果。
Function.prototype.apply(thisArg, [argsArray])
[Function.prototype.call(thisArg[, arg1[, arg2[, …]]])](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call)
apply()和call()方法的功能是相同的,都是调用一个对象的一个方法,并用第一个参数的值替换该方法原来的对象,即替换掉方法中的this的值。
apply()与call()方法的区别只是所接收的方法参数不同,除了第一个参数外,apply()接收的第二个参数为数组,即允许将多个参数合并为一个数组传入,而call()只能从第二个参数起逐个一一传入。
通过对上文中代码二的修改,使用apply()或call()来篡改函数中this的实现如下代码所示:
1 | 代码七: |
以上代码的控制台输出都打印出了benz本身,如下所示:
1 | D:\desk\JavaScript>node this.js |
另外,以下的几个方法同样也可以篡改callback函数中的this值,只是能用性没有apply()/call()/bind()强。
Array.prototype.find(callback[, thisArg])
Array.prototype.every(callback[, thisArg])
Array.prototype.forEach(callback[, thisArg])
Array.prototype.some(callback[, thisArg])
严格模式的判断
根据ECMAScript 3和非严格的ECMAScript 5对函数调用的规定,调用上下文(this的值)是全局对象。
在严格模式下,调用上下文(this的值)则是undefined。
所以”this“可以用来判断当前是否是严格模式。方法如下:
1 | 代码八: |
以上代码在控制台输出为:
1 | D:\desk\JavaScript>node this.js |
总结
函数体内this就只有两个值,对象本身或运行时环境的全局对象。
在非严格模式下,当在Node.js控制台时,全局对象值为 GLOBAL ;当在HTML浏览器运行时,全局对象值为Window。
在严格模式下,全局对象是undefined。
后记
注意”总结“里这一句”函数体内this就只有两个值“特意说明了是在”函数体内“。那在函数体外呢?
sodino做了下测试,在不同的运行时环境中,函数体外this的值根据运行时环境而有所不同。
直接运行代码九中这一句打印语句
1 | 代码九: |
在Node.js控制台运行时,this是个{}
空对象,如下图:
在Chrome浏览器中,this是Window。如下图: