[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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章