摘要:調用棧就是為了到達當前執行位置所調用的所有函數。由于無法控制回調函數的執行方式,因此就沒有辦法控制調用位置得到期望的綁定,下一節我們會介紹如何通過固定來修復這個問題。
在《你不知道的this》中我們排除了對于this的錯誤理解,并且明白了每個函數的this是在調用時綁定的,完全取決于函數的調用位置。在本節中我們主要介紹一下幾個主要內容:
什么是調用位置
綁定規則
this詞法
調用位置調用位置:就是函數在代碼中被調用的位置(而不是聲明位置)。要想回答this到底引用的是什么?只有仔細分析調用位置才能回答這個問題。
而分析調用位置最重要的就是分析調用棧。下面是調用棧的定義。
調用棧:就是為了到達當前執行位置所調用的所有函數。
function baz(){ //當前調用棧是:baz //因此,當前調用位置是全局作用域 console.log("baz"); bar(); //bar的調用位置 } function bar(){ //當前調用棧是:baz -> bar //因此,當前調用位置是在baz中 console.log("bar"); foo(); //foo的調用位置 } function foo(){ //當前調用棧是:baz -> bar -> foo //因此,當前調用位置是在baz中 console.log("foo"); } baz(); // baz的調用位置綁定規則
我們的思路是,通過找到函數的調用位置,然后判斷需要應用規則中的哪一條。便可決定this的綁定對象。關于this的綁定規則主要是以下四種:
默認綁定
隱式綁定
顯式綁定
new綁定
1.默認綁定默認綁定的典型類型是:獨立函數調用。 思考如下代碼:
function foo(){ console.log(this.a); } var a = 2; foo(); // 2
調用foo()時,函數應用了默認綁定,this只想全局對象window(這是在非嚴格模式下,若是在嚴格模式下會報錯),所以this.a被解析成了全局變量a。所以,在不使用任何修飾的函數引用進行調用,只能使用默認綁定,無法應用其他規則。
2.隱式綁定隱式綁定的常見形式是在調用位置具有上下文對象,或者說被某個對象擁有或者包含。看如下代碼:
function foo(){ console.log(this.a); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
這里函數foo()是預先定義好的,然后再將其添加為obj對象的引用屬性。調用位置使用obj上下文來引用函數,因此可以說函數被調用時obj對象“擁有”或者“包含”它。
無論你如何稱呼這個模式,當foo()被調用時,它的前面確實是加上了obj的引用。當函數引用上下文對象時,隱式綁定規則就會把函數調用中的this綁定到這個上下文對象。所以,this.a和obj.a是一樣的。
另一個需要注意的點是:對象屬性引用鏈中只有最后一層在調用位置起作用
function foo(){ console.log(this.a); } var obj2 = { a: 100, foo: foo }; var obj1 = { a: 1, obj2: obj2 } obj1.obj2.foo(); // 100
一個最常見的問題就是:隱式綁定的函數會丟失綁定對象。也就是是說它會應用默認綁定,而把this綁定到全局對象或者undifined上,取決于是否是嚴格模式。
function foo(){ console.log(this.a); } var obj ={ a: 2, foo: foo } var bar = obj.foo; //函數別名 var a = "global"; bar(); // "global"
雖然bar是obj.foo的一個引用,但實際上,他引用的是foo函數本身, 因此, 此時的bar()其實是一個不帶任何修飾的函數調用,因此,它應用了默認綁定。
一種更微妙,更常見的并且更出乎意料的情況發生在傳入回調函數時:
function foo(){ console.log(this.a); } function callBack(fn){ fn(); } var obj = { a: 2, foo: foo } var a = "global"; callBack(obj.foo); // "global"
參數傳遞其實就是一種隱式賦值,這句話我們可以用下面的兩段代碼來詳細的講解:
var a = 1; function fn(){ alert(a); //1 a = 2; } fn(); alert(a); // 2 _ _ _ var a = 1; function fn(a){ alert(a); //undifined a = 2; } fn(); alert(a); // 1
思考一下結果是否與你想象的一致呢?
在第一段代碼中:
首先,在全局作用域中,先通過變量提升,找到了標識符a和函數fn,a此時有個默認值為undifined。然后,在執行階段我們先將變量a賦值為1,緊跟著函數fn()執行。此時,在函數域中,依舊應用變量提升的規則,但是什么都沒找到,接著執行函數內的代碼:alert(a),因為在函數中并沒有找到變量a。所以,通過作用域鏈向上層的父級作用域中查找,我們找到了a,并且此時a的值已經被賦值為1,所以,alert(a)這句的結果就是1。下一句代碼:a = 2,注意a的前面沒有關鍵字var, 即這里的a是全局的,也就是說在執行這句代碼時,他修改了全局作用域中a的值,即a現在為2。最后在執行alert(a)時,自然而然a的值便是2了。
在第二段代碼中:
同樣,通過變量提升,我們找到了標識符a和函數fn,a此時的默認值也為undifined。開始執行,a首先被賦值為1。然后,函數執行,這里與第一段代碼的不同之處在于,在函數fn中傳入了參數a,那么這么做的結果就是:在函數域先運用變量提升的規則,不會像第一段代碼中那樣什么都找不到,而是相當于定義了一個值為undifined(調用的時候沒有傳入參數)的變量a,所以當執行函數域中的alert(a)時,結果就為undifined,而不會通過作用域鏈向上去查找,因為本函數中已經找到了,只不過是以參數的形式傳入的。同理代碼(a = 2)會修改a的值,即在函數域中,a的值現在為2(讀者可以去嘗試在函數中最后面alert一下a的值)。而在函數外執行alert(a),我們得到的結果便是1,因為該句代碼是在全局中執行的,即會在全局中去查找變量a,而不會去訪問函數域中的a。這也是因為,在JavaSceipt中子作用域可以訪問父作用域而反過來卻不行的規則。
回到我們this綁定丟失的話題上,說了這么多,我其實就是想說:參數傳遞其實就是一種隱式賦值,參數傳遞其實就是一種隱式賦值,參數傳遞其實就是一種隱式賦值,重要的事說三遍!
我們按照上面的方式來解析代碼:在執行callBack(obj.foo)時,在函數作用域通過變量提升找到了參數fn,它的默認值為undifined,然后我們將參數傳入,其實相當于(var fn = obj.foo),這就與前面的將其直接賦值給一個變量對等上了,然后再執行fn(),應用默認綁定,此時的this已經不指向obj了,而是指向window(嚴格模式)。
如果把函數傳入內置的函數而不是傳入你自己聲明的函數,會發生什么呢?結果是一樣的,沒有區別:
function foo(){ console.log(this.a) } var obj = { a: 2, foo: foo } var a = "global"; setTimeout(obj.foo, 1000); //"global"
JavaSceipt環境中內置的setTimeout()函數實現和下面的偽代碼類似:
function setTimeout(fn, delay){ //等待delay秒 fn(); //調用位置 }
就向你們看到的那樣,回調函數丟失this綁定的情況是非常常見的,并且還有一種情況this的行為會出乎我們意料:調用回調函數的函數可能會修改this。由于無法控制回調函數的執行方式,因此就沒有辦法控制調用位置得到期望的綁定,下一節我們會介紹如何通過固定this來“修復“這個問題。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78968.html
摘要:當我們不想再對象內部間接包含引用函數,而像在某個對象上強制調用函數。我們可以用中內置的和的方法來實現,這兩個方法的第一個參數是一個對象,是給準備的,接著再調用函數時將其綁定到。 this是什么 在javascript中,每個執行上下文可以抽象成一組對象showImg(https://segmentfault.com/img/bVuKR7); 而this是與執行上下文相關的特殊對象,任何...
摘要:在傳統的面向類的語言中,構造函數是類中的一些特殊方法,使用初始化類是會調用類中的構造函數。 在上一節中我們詳細介紹了this的兩種綁定方式,默認綁定和隱式綁定,在這一節我們繼續介紹this的另外兩種綁定方式顯示綁定和new綁定。那么,我們要解決的問題當然就是上一節中我們提到的:this丟失! 顯式綁定 在隱式綁定中,我們必須在一個對象的內部包含一個指向函數的屬性,并通過這個屬性間接引用...
摘要:關于的全棉解析上的文章地址判斷函數是否在中調用綁定如果是的話綁定的是新創建的對象。顯而易見,這種方式可能會導致許多難以分析和追蹤的。默認在嚴格模式下綁定到,否則綁定到全局對象。 關于this的全棉解析(上)的文章地址 判斷this 函數是否在new中調用(new綁定)?如果是的話this綁定的是新創建的對象。 bar = new foo() 函數是否通過call、apply(顯式綁定...
摘要:關于的全面解析下頁面鏈接的調用位置調用位置就是函數在代碼中被調用的位置而不是聲明的位置,尋找調用位置就是尋找函數被調用的位置,最重要的是分析調用棧就是為了到達當前執行位置所調用的所有函數。因此,調用函數時被綁定到這個對象上,所以和是一樣的。 關于this的全面解析(下)頁面鏈接 this的調用位置 調用位置就是函數在代碼中被調用的位置(而不是聲明的位置),尋找調用位置就是尋找函數被調用...
閱讀 3785·2023-04-26 02:07
閱讀 3671·2021-10-27 14:14
閱讀 2859·2021-10-14 09:49
閱讀 1624·2019-08-30 15:43
閱讀 2611·2019-08-29 18:33
閱讀 2369·2019-08-29 17:01
閱讀 915·2019-08-29 15:11
閱讀 582·2019-08-29 11:06