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