摘要:考慮這個(gè)問(wèn)題還需要了解一個(gè)概念調(diào)用棧到達(dá)當(dāng)前執(zhí)行位置而被調(diào)用的所有方法的堆棧。之后只要調(diào)用函數(shù),就會(huì)調(diào)用函數(shù),綁定的值始終不變。就是中新增的函數(shù)箭頭函數(shù)。
在之前的對(duì)象原型的文章中,我們講到了在函數(shù)前面加new然后進(jìn)行調(diào)用之后發(fā)生的4件事情,當(dāng)時(shí)只把跟原型有關(guān)的東西介紹了一下?,F(xiàn)在我們來(lái)學(xué)習(xí)一下其他的內(nèi)容。
首先先來(lái)回顧一下會(huì)有哪四件事情發(fā)生吧:
一個(gè)全新的對(duì)象被創(chuàng)建
這個(gè)新的對(duì)象會(huì)被接入到原型鏈
這個(gè)新的對(duì)象被設(shè)置為函數(shù)調(diào)用的this綁定
除非函數(shù)有返回對(duì)象,否則這個(gè)被new調(diào)用的函數(shù)將自動(dòng)返回這個(gè)新的對(duì)象
這里有個(gè)新的東西this綁定,也就是接下來(lái)我們要介紹的東西啦。
第一個(gè)問(wèn)題就是this是什么?(以下回答摘自You-Dont-Know-JS)
this是在函數(shù)被調(diào)用的時(shí)候建立的一個(gè)綁定,指向的內(nèi)容完全由函數(shù)被調(diào)用的調(diào)用點(diǎn)來(lái)決定的。
簡(jiǎn)單點(diǎn)說(shuō),this就是一個(gè)綁定,指向一個(gè)內(nèi)容。
那么this指向的內(nèi)容又是什么呢?前面說(shuō)到這個(gè)內(nèi)容由函數(shù)被調(diào)用的調(diào)用點(diǎn)來(lái)決定。所謂的調(diào)用點(diǎn),就是函數(shù)在代碼中被調(diào)用的地方。也就是說(shuō),我們需要找到函數(shù)在哪里被調(diào)用,從而確定this指向的內(nèi)容。考慮這個(gè)問(wèn)題還需要了解一個(gè)概念:調(diào)用棧(到達(dá)當(dāng)前執(zhí)行位置而被調(diào)用的所有方法的堆棧)。
看段代碼來(lái)深入理解一下調(diào)用棧和調(diào)用點(diǎn)這兩個(gè)概念:
function foo() { // 調(diào)用棧是: `foo` // 調(diào)用點(diǎn)是global scope(全局作用域) console.log( "foo" ); bar(); // <-- `bar`的調(diào)用點(diǎn) } function bar() { // 調(diào)用棧是: `foo` -> `bar` // 調(diào)用點(diǎn)位于`foo` console.log( "bar" ); baz(); // <-- `baz`的調(diào)用點(diǎn) } function baz() { // 調(diào)用棧是: `foo` -> `bar` -> `baz` // 調(diào)用點(diǎn)位于`bar` console.log( "baz" ); } foo(); // <-- `foo`的調(diào)用點(diǎn)
上面這個(gè)代碼跟注釋?xiě)?yīng)該已經(jīng)很清楚了解釋了調(diào)用棧和調(diào)用點(diǎn)這兩個(gè)概念了。
搞清楚這些概念之后,我們還是不知道this會(huì)指向什么。既然說(shuō)this指向的內(nèi)容完全由調(diào)用點(diǎn)決定,那么調(diào)用點(diǎn)又是怎么決定的呢?
還記得文章最開(kāi)始提到的東西么,關(guān)于new的4件事情,第三點(diǎn)講的是新對(duì)象被設(shè)置為函數(shù)調(diào)用的this綁定。
看下代碼:
function foo(){ this.a = a; } var bar = new foo(2); //調(diào)用foo函數(shù)來(lái)創(chuàng)建一個(gè)新對(duì)象bar console.log(bar.a);
使用new來(lái)調(diào)用函數(shù)foo的時(shí)候,我們創(chuàng)建了一個(gè)新對(duì)象bar并且把bar綁定到了foo()里面的this.這就是所謂的new綁定。
那么在JavaScript中,關(guān)于this綁定,除了new綁定,還有3種其它的規(guī)則:
默認(rèn)綁定
隱式綁定
顯示綁定
下面我們依次來(lái)一一介紹。
默認(rèn)綁定
看名字我們就能看出來(lái),這是最普通最基礎(chǔ)的綁定。一般來(lái)說(shuō),獨(dú)立函數(shù)調(diào)用的時(shí)候this就是默認(rèn)綁定。
來(lái)看個(gè)例子:
function foo(){ console.log(this.a); } var a = 2; foo(); //2
代碼很簡(jiǎn)單,我們主要關(guān)心的是this。我們先看結(jié)果:this綁定到了全局變量。
具體分析一下也很簡(jiǎn)單,這里的函數(shù)調(diào)用就是我們平常在使用的最簡(jiǎn)單的獨(dú)立函數(shù)的調(diào)用,跟前面介紹的規(guī)則也很符合。
這里有一個(gè)要注意的小細(xì)節(jié)就是如果是在嚴(yán)格模式下,默認(rèn)綁定的值會(huì)變成undefined。如果是非嚴(yán)格模式的話,就是綁定到全局變量了。
隱式綁定
這個(gè)規(guī)則一般是看函數(shù)調(diào)用的位置是否有上下文對(duì)象,或者說(shuō)是否被某個(gè)對(duì)象擁有或者包含。
通過(guò)代碼來(lái)深入理解一下:
function foo(){ console.log(this.a); } var obj = { a:2, foo:foo }; obj.foo(); //2
代碼同樣很好理解,函數(shù)foo()作為引用屬性添加在對(duì)象obj里面,但這并不能說(shuō)明函數(shù)foo()屬于obj對(duì)象。但是從調(diào)用的位置上看,會(huì)使用obj這個(gè)對(duì)象來(lái)引用函數(shù)foo,那么函數(shù)在被調(diào)用的時(shí)候,是被obj這個(gè)對(duì)象擁有或者包含的。
簡(jiǎn)單點(diǎn)說(shuō),函數(shù)在被調(diào)用的時(shí)候,是通過(guò)對(duì)象來(lái)引用的,那么函數(shù)里的this就會(huì)綁定到這個(gè)對(duì)象上面。
再來(lái)看一個(gè)稍微復(fù)雜一點(diǎn)的例子:
function foo(){ console.log(this.a); } var obj = { a:1, foo:foo }; var obj1 = { a:2, obj:obj } obj1.obj.foo(); //1
這里的話,我們會(huì)發(fā)現(xiàn)多了一個(gè)obj1這個(gè)對(duì)象,而且這個(gè)對(duì)象里有屬性a和對(duì)象obj。然后我們調(diào)用的時(shí)候會(huì)發(fā)現(xiàn)結(jié)果輸出的是obj里面的屬性a的值。
簡(jiǎn)單的結(jié)論就是,在多層的對(duì)象引用屬性中,只有最頂層或者說(shuō)最后一層才會(huì)影響調(diào)用位置。
顯式綁定
通過(guò)上面隱式綁定的規(guī)則介紹可以知道,它是通過(guò)對(duì)象間接綁定this的,那么很明顯顯式綁定就是直接的,或者說(shuō)就是強(qiáng)行指定我們想要讓this綁定的對(duì)象。那么怎么來(lái)進(jìn)行強(qiáng)行綁定呢?
一般來(lái)說(shuō),是使用函數(shù)的call()和apply()方法(絕大部分函數(shù)都會(huì)有這兩個(gè)方法)。
這兩個(gè)方法的作用都是一樣的,就是替換this指向。唯一不同的就是接收參數(shù)的方法不一樣。apply()方法接收兩個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)對(duì)象(也就是我們想要讓this指向的新的對(duì)象,不填的話就是全局對(duì)象),第二個(gè)參數(shù)一個(gè)參數(shù)數(shù)組。call()方法的話第一個(gè)參數(shù)跟apply是一樣的,但是后面要把傳遞的參數(shù)全部都列舉出來(lái)。
簡(jiǎn)單來(lái)看個(gè)例子:
function foo(){ console.log(this.a); } var obj = { a:2 }; foo.call(obj); //2
最后一行代碼,函數(shù)foo調(diào)用了call方法,強(qiáng)行把this綁定到了obj對(duì)象上。
至此,關(guān)于this綁定的基礎(chǔ)的4種規(guī)則就介紹得差不多了,實(shí)際上有些規(guī)則在應(yīng)用的時(shí)候可能不那么盡如人意,我們依舊從代碼入手:
function foo(){ console.log(this.a); } var obj = { a:2, foo:foo }; var bar = obj.foo; var a = 1; bar(); //1
一開(kāi)始我們可能都會(huì)覺(jué)得輸出的結(jié)果應(yīng)該是2。因?yàn)閎ar這個(gè)對(duì)象在創(chuàng)建的時(shí)候調(diào)用了obj里面的foo函數(shù)。但實(shí)際上只是另一個(gè)foo自己的引用。而且bar函數(shù)在調(diào)用的時(shí)候是作為獨(dú)立函數(shù)調(diào)用的,跟我們前面講的默認(rèn)綁定的規(guī)則很符合,所以這里的this就綁定到了全局對(duì)象。
這種情況在回調(diào)函數(shù)里更容易發(fā)生,比如下面的代碼:
function foo(){ console.log(this.a); } function doFoo(f){ f(); } var obj = { a:2, foo:foo }; var a = 1; doFoo(obj.foo); //1
最后一行代碼實(shí)際上就是f = obj.foo,自然結(jié)果就跟上面是一樣的。
那么有什么方法可以解決這個(gè)問(wèn)題呢?
在顯示綁定中,有一個(gè)它的變種,我們稱之為硬綁定,可以解決上面的問(wèn)題。
繼續(xù)看代碼:
function foo(){ console.log(this.a); } var obj = { a:2 }; var bar = function(){ foo.call(obj); } bar(); //2 setTimeout(bar,1000); bar.call(window); //2
這段代碼解釋了硬綁定的工作原理:它創(chuàng)建了一個(gè)函數(shù)bar,然后在函數(shù)里面通過(guò)foo.call(..)強(qiáng)行把this綁定到了obj對(duì)象上面。之后只要調(diào)用函數(shù)bar,就會(huì)調(diào)用函數(shù)foo,綁定的值始終不變。
然后我們稍微改變一下,讓它變成一個(gè)可復(fù)用的幫助函數(shù):
function foo(){ console.log(this.a); } function bind(f,obj){ return function(){ return f.apply(obj,arguments); }; } var obj = { a:2 }; var bar = bind(foo,obj); var b = bar(3); console.log(b); //2
由于硬綁定經(jīng)常被使用,所以它在ES5的時(shí)候就作為內(nèi)建工具了:Function.prototype.bind。上面的代碼就是bind方法的原理。
bind方法的作用和call和apply一樣,都是替換this指向,它的參數(shù)也和call一樣。不一樣的就是bind方法返回的是一個(gè)函數(shù)。
然后我們要介紹一個(gè)比較特殊的函數(shù),因?yàn)樗荒芨鶕?jù)前面介紹的4條規(guī)則來(lái)判斷this的指向。就是ES6中新增的函數(shù):箭頭函數(shù)(=>)。它是根據(jù)外層作用域或者全局作用域來(lái)決定this指向的。
看段代碼:
function foo(){ return (a) => { console.log(this.a); }; } var obj1 = { a:1 }; var obj2 = { a:2 }; var bar = foo.call(obj1); bar.call(obj2);//1
foo()內(nèi)部創(chuàng)建的箭頭函數(shù)會(huì)捕獲調(diào)用時(shí)foo()的this。因?yàn)閒oo使用了call方法,所以foo()的this綁定到了obj1。然后bar對(duì)象被創(chuàng)建的時(shí)候引用了箭頭函數(shù),所以bar的this也被綁定到了obj1上面。而且箭頭函數(shù)的綁定是無(wú)法被修改的。所以最后輸出的結(jié)果是1而不是2。
最后,雖然我們已經(jīng)了解了this綁定的基本規(guī)則,但是如果說(shuō)我們找到了函數(shù)在哪里調(diào)用,然后又發(fā)現(xiàn)4種規(guī)則里有多種規(guī)則可以適用,那我們應(yīng)該選擇哪一種呢?
這就涉及到了這些規(guī)則的優(yōu)先級(jí):
首先看是不是有new調(diào)用,如果是的話就綁定到新創(chuàng)建的對(duì)象;
然后看是不是有call或者apply或者bind調(diào)用,如果是那就綁定到指定對(duì)象;
再之后看是不是由上下文調(diào)用,如果是就綁定到那個(gè)上下文對(duì)象;
最后的話就只剩下默認(rèn)綁定了(注意嚴(yán)格模式下是undefined,非嚴(yán)格模式下綁定到全局對(duì)象)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93700.html
摘要:結(jié)構(gòu)其中為整個(gè)項(xiàng)目入口,為中的類,負(fù)責(zé)對(duì)測(cè)試信息進(jìn)行記錄。通過(guò)拋出錯(cuò)誤而不是返回布爾值的方式來(lái)通知用戶,能夠更加明顯的通知用戶,也方便向上拋出異常進(jìn)行傳遞。 背景 為了研究與學(xué)習(xí)某些測(cè)試框架的工作原理,同時(shí)也為了完成培訓(xùn)中實(shí)現(xiàn)一個(gè)簡(jiǎn)單的測(cè)試框架的原因,我對(duì)should.js的代碼進(jìn)行了學(xué)習(xí)與分析,現(xiàn)在與大家來(lái)進(jìn)行交流下。 目錄 ext assertion.js assertion-e...
摘要:簡(jiǎn)介源于數(shù)據(jù)挖掘的一個(gè)作業(yè),這里用來(lái)實(shí)現(xiàn)一下這個(gè)機(jī)器學(xué)習(xí)中最簡(jiǎn)單的算法之一算法最近鄰分類法。其實(shí)這些標(biāo)簽就對(duì)應(yīng)于機(jī)器學(xué)習(xí)中的特征這一重要概念,而訓(xùn)練我們識(shí)別的過(guò)程就對(duì)應(yīng)于泛化這一概念。 1. 簡(jiǎn)介 源于數(shù)據(jù)挖掘的一個(gè)作業(yè), 這里用Node.js來(lái)實(shí)現(xiàn)一下這個(gè)機(jī)器學(xué)習(xí)中最簡(jiǎn)單的算法之一k-nearest-neighbor算法(k最近鄰分類法)。 k-nearest-neighbor-cl...
摘要:最近在學(xué)習(xí)的表格排序,沒(méi)想到看不起眼的表格排序?qū)嶋H上卻暗含了眾多知識(shí)點(diǎn)。二實(shí)現(xiàn)表格排序使用獲取數(shù)據(jù)之所以使用動(dòng)態(tài)獲取數(shù)據(jù),是為了使用文檔碎片綁定數(shù)據(jù)。 最近在學(xué)習(xí)js的表格排序,沒(méi)想到看不起眼的表格排序?qū)嶋H上卻暗含了眾多JS知識(shí)點(diǎn)。在這里記錄一下此次學(xué)習(xí)過(guò)程。希望對(duì)大家也有所幫助。 完整的表格排序涉及了下列這些知識(shí)點(diǎn): call方法使用 sort方法深入 數(shù)據(jù)綁定 DOM映射 下面...
摘要:最近在學(xué)習(xí)的表格排序,沒(méi)想到看不起眼的表格排序?qū)嶋H上卻暗含了眾多知識(shí)點(diǎn)。二實(shí)現(xiàn)表格排序使用獲取數(shù)據(jù)之所以使用動(dòng)態(tài)獲取數(shù)據(jù),是為了使用文檔碎片綁定數(shù)據(jù)。 最近在學(xué)習(xí)js的表格排序,沒(méi)想到看不起眼的表格排序?qū)嶋H上卻暗含了眾多JS知識(shí)點(diǎn)。在這里記錄一下此次學(xué)習(xí)過(guò)程。希望對(duì)大家也有所幫助。 完整的表格排序涉及了下列這些知識(shí)點(diǎn): call方法使用 sort方法深入 數(shù)據(jù)綁定 DOM映射 下面...
閱讀 1727·2021-11-11 10:58
閱讀 4217·2021-09-09 09:33
閱讀 1268·2021-08-18 10:23
閱讀 1558·2019-08-30 15:52
閱讀 1634·2019-08-30 11:06
閱讀 1878·2019-08-29 14:03
閱讀 1517·2019-08-26 14:06
閱讀 2969·2019-08-26 10:39