文章結構
使用new操作符調用構造函數生成對象時,構造函數內的this爲當前所new出來的對象。
1 2 3 4 5
| 代碼一: function Car() { console.log(this); } new 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的含義都很明確,即當前的對象本身。
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了。
對 benz::printHistory()中增加一嵌套函數,如下代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 代碼四: var benz = { printHistory : function() { console.log(this); (function(){ console.log(this); })(); } }; 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() { console.log(this); var self = this; (function(){ console.log(self); })(); } }; 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() { console.log(this); (function(){ 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.
上文已經通過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.call(benz);
|
以上代碼的控制檯輸出都打印出了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' 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的值根據運行時環境而有所不同。
直接運行代碼九中這一句打印語句
在Node.js控制檯運行時,this是個{}
空對象,如下圖:
在Chrome瀏覽器中,this是Window。如下圖:
About Sodino