[JavaScript]'this'详解

文章结构


普通的 this

使用new操作符调用构造函数生成对象时,构造函数内的this为当前所new出来的对象。

      
      
1
2
3
4
5
      
      
代码一:
function Car() {
console.log( this);
}
new Car();

输出完整的对象结构:

      
      
1
      
      
Car {}

下面的代码定义了一个对象benz,并调用该对象的printHistory(),由对象benz调用方法体,所以方法体内的this为对象benz本身。

      
      
1
2
3
4
5
6
7
8
      
      
代码二:
var benz = {
printHistory : function() {
console.log( this);
}
};
benz.printHistory();

输出完整的类结构:

      
      
1
      
      
{ printHistory: [Function] }

以上两种情况,this的含义都很明确,即当前的对象本身。


外漏函数中的 this

      
      
1
2
3
4
5
6
7
8
9
10
      
      
代码三:
var benz = {
printHistory : function() {
console.log( this);
}
};
// 将对象中的方法暴露出来成为外漏函数
var printHistory = benz.printHistory;
printHistory();

执行代码后,控制台输出如下:

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      
      
D:\desk\JavaScript>node this.js
{ DTRACE_NET_SERVER_CONNECTION: [Function], // 打印开始 ...为全局对象或Window
DTRACE_NET_STREAM_END: [Function],
... ... ...
... ... ...
... ... ...
COUNTER_HTTP_CLIENT_REQUEST: [Function], // sodino.com
COUNTER_HTTP_CLIENT_RESPONSE: [Function],
global: [Circular],
process:
process {
title: '管理员: Node.js command prompt - node this.js',
version: 'v5.7.0',
moduleLoadList: // sodino.com
[ 'Binding contextify',
'Binding natives',
'NativeModule events',

以上代码打印的this直接为控制台全局对象GLOBAL或浏览器Window了。
这个现象会让很多人困惑。
要理解该现象的原因要从查看调用链去理解:被调用函数是一个对象的属性还是函数自己。
如果它被作为属性调用,那么this的值将变成该属性的对象,否则this的值将被默认赋值为控制台全局对象GLOBAL或浏览器的Window了。


嵌套函数中的 this

对 benz::printHistory()中增加一嵌套函数,如下代码:

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
      
      
代码四:
var benz = {
printHistory : function() {
// print history after AD2000
console.log( this); // 这里this是benz本身
( function(){
// print history before AD2000
console.log( this); // 这里this是Global或Window
})();
}
};
benz.printHistory();

控制台输出如下:

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      
      
D:\desk\JavaScript>node this.js
{ printHistory: [Function] } // 第一次打印仍为benz本身
{ DTRACE_NET_SERVER_CONNECTION: [Function], // 第二次打印开始 ...为全局对象或Window
DTRACE_NET_STREAM_END: [Function],
... ... ...
... ... ...
... ... ...
COUNTER_HTTP_CLIENT_REQUEST: [Function], // sodino.com
COUNTER_HTTP_CLIENT_RESPONSE: [Function],
global: [Circular],
process:
process {
title: '管理员: Node.js command prompt - node this.js',
version: 'v5.7.0',
moduleLoadList: // sodino.com
[ 'Binding contextify',
'Binding natives',
'NativeModule events',

以上代码,第一次打印this,仍是benz本身;但在嵌套函数中打印的this则为控制台全局对象或浏览器的Window了。
this不同于普通的对象声明,this的值无法在被嵌套的函数作用域中保持不变,这是JavaScript的缺陷。
但回想上文中“被调用函数是一个对象的属性还是函数自己”这一判断标准,嵌套函数并不是benz中的属性,所以this值为控制台全局对象或浏览器的Window也可以顺利理解了。

如果想在嵌套函数使用到上下文的this,有两种办法:
方法一:新声明一个变量持有上下文this

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
      
      
代码五:
var benz = {
printHistory : function() {
// print something after AD2000
console.log( this);
var self = this; // 声明新变量持有this
( function(){
// print something before AD2000
console.log(self); // 引用上下文this
})();
}
};
benz.printHistory();

该段代码执行的控制台输出如下:

      
      
1
2
3
      
      
D:\ desk\ JavaScript> node this .js
{ printHistory: [Function] }
{ printHistory: [Function] }

方法二:使用Function.prototype.bind(thisArg[, arg1[, arg2[, …]]])

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
      
      
代码六:
var benz = {
printHistory : function() {
// print something after AD2000
console.log( this);
( function(){
// print something before AD2000
console.log( this);
}).bind( this)();
}
};
benz.printHistory();

Function.prototype.bind()方法会返回一个新的函数体,新函数体的功能和原先函数保持一致,只是在新函数体内this的值为bind()方法参数中第一个参数的值。
bind()方法返回新函数后,就通过“bind(this) ()“直接被执行了。
由此,为控制台的两处打印信息都是benz本身:

      
      
1
2
3
      
      
D:\ desk\ JavaScript> node this .js
{ printHistory: [Function] }
{ printHistory: [Function] }

在维持上下文环境一致的实现选择上,bind()是更受推荐的方法。

由此,也引出下面的内容:如何篡改函数体中的this.


篡改函数中的 this

上文已经通过Function.prototype.bind()方法将嵌套函数中this的值指定为bind()的第一个参数值。JavaScript还有另外两个方法也可以实现同样的效果。

Function.prototype.apply(thisArg, [argsArray])
Function.prototype.call(thisArg[, arg1[, arg2[, …]]])

apply()和call()方法的功能是相同的,都是调用一个对象的一个方法,并用第一个参数的值替换该方法原来的对象,即替换掉方法中的this的值。
apply()与call()方法的区别只是所接收的方法参数不同,除了第一个参数外,apply()接收的第二个参数为数组,即允许将多个参数合并为一个数组传入,而call()只能从第二个参数起逐个一一传入。

通过对上文中代码二的修改,使用apply()或call()来篡改函数中this的实现如下代码所示:

      
      
1
2
3
4
5
6
7
8
9
10
11
      
      
代码七:
var benz = {
printHistory : function() {
console.log( this);
}
};
var printHistory = benz.printHistory;
printHistory.apply(benz); // 指定了printHistory的上下文环境仍为benz
printHistory.call(benz); // 当没有额外的参数时,apply()和call()的用法是相同的

以上代码的控制台输出都打印出了benz本身,如下所示:

      
      
1
2
3
      
      
D:\ desk\ JavaScript> node this .js
{ printHistory: [Function] }
{ printHistory: [Function] }

另外,以下的几个方法同样也可以篡改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
2
3
4
5
6
7
8
9
      
      
代码八:
'use strict'
// 'use strict'启用了严格模式,可以注释掉取消该模式
var isStrict = ( function(){
return ! this;
}());
console.log( 'isStrict=' + isStrict);

以上代码在控制台输出为:

      
      
1
2
      
      
D:\desk\JavaScript>node this.js
isStrict= true

总结

函数体内this就只有两个值,对象本身或运行时环境的全局对象。
在非严格模式下,当在Node.js控制台时,全局对象值为 GLOBAL ;当在HTML浏览器运行时,全局对象值为Window。
在严格模式下,全局对象是undefined。


后记

注意”总结“里这一句”函数体内this就只有两个值“特意说明了是在”函数体内“。那在函数体外呢?
sodino做了下测试,在不同的运行时环境中,函数体外this的值根据运行时环境而有所不同。

直接运行代码九中这一句打印语句

      
      
1
2
      
      
代码九:
console.log( this);

在Node.js控制台运行时,this是个{}空对象,如下图:
node.js

在Chrome浏览器中,this是Window。如下图:
Window


About Sodino

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值