深入理解函數內部原理(3)——動態的this

在閱讀本博客之前先閱讀:

深入理解函數之函數定義、調用、解析、執行 解剖http://blog.csdn.net/wmaoshu/article/details/60469571
深入理解函數之對一個函數實例進行深入的分析
http://blog.csdn.net/wmaoshu/article/details/60766030
本博客主要是深入討論下js中this相關的東西。


JS中this是什麼

正如在前面博客 執行環境 http://blog.csdn.net/wmaoshu/article/details/60466990
中介紹的那樣,其實this的本質是作爲執行環境的一部分存在的,又因爲執行環境是在進入可執行代碼區域開始執行代碼的時候創建的,所以,this也就和代碼的執行有關了,與代碼的結構沒有關係,所以this具有動態的特性,this的值是在執行的過程中確定的。
每一個執行環境與一個可執行代碼塊相關聯,所以在這個可執行代碼塊中的所有出現的this標識符,最終解析成執行環境中this這個屬性的值。
其實,this的相關內部原理已經在第一篇博客中深入的說明了,這裏只不過總結一下。


不同環境下this值怎麼判斷

情況一:通過標識符形式函數調用

function add(a,b){
        return (a+b);
    };
add(1,2);

通過前面幾篇博客想必這個函數調用的原理明白了,那麼,我就從add(1,2);執行開始進行分析,對於在這條語句只想內部幹了什麼不清楚的可以看之前的博客。

當執行到add(1,2);時,是一個函數調用表達式,開始對調用運算符”()”之前的操作對象add進行標識符的解析,調用GetIdentifierReference函數,傳入參數爲 當前執行環境的此法環境 全局環境、查找的標識符字符串 add、是否嚴格查找 false。開始進行在作用域鏈中查找,最終返回一個引用{base:全局詞法環境的對象式環境記錄項,name:add,strict:false} 爲Ref。
通過對Ref進行GetValue操作的到在全局對象中add的值爲一個函數對象 F。並且檢查一下是個函數。
然後重點來了,開始確定this。經過檢查,Ref確實是一個引用類型,所以就開始取base值進行查看,發現是一個 對象式環境記錄項,通過ImplicitThisValue得到的this值爲undefined(因爲全局對象中provideThis爲false)。
調用[Call]方法後,傳入this爲undefined,和1,2,創建這個函數add 的執行環境addCTX,進入函數。
開始確定addCTX中this綁定(thisBind)的值,在這裏對於是否在嚴格環境下對上一步傳入來的this值進行調整。如果嚴格模式的話不做處理,直接這個thisBind的值爲undefined,否則的話會轉化爲全局對象(window)。

<script>
/*"use strict";*/
    function add(a,b){
        console.log(this);/*嚴格模式下:window非嚴格模式下:undefined*/
        return (a+b);
    };
    add(1,2);
</script>

情況二:通過屬性訪問表達式的形式函數調用

<script>
var o = {
    "add": function(a,b){
        return (a+b);
    }
};
o.add(1,2);/*屬性訪問表達式兩種不同的等價的方式,這種.的方式會最終轉化成[]訪問的方式*/
o["add"](1,2);
</script>

解析o[“add”]的時候,最終返回的是一個引用{base:o,name:add,strict:false};通過getValue的方式調用add屬性值爲F,確定F爲一個函數之後,確定this。
如果得知base爲一個對象,所以就會將這個對象作爲this的值,也就是說在這一步this就是o。
然後調用[Call]傳入this爲o、1,2作爲參數,創建一個F相關的執行環境,再嚴格模式和非嚴格模式下都是o,,所以這個執行環境的this綁定未o.

<script>
var o = {
    "add": function(a,b){
        console.log(this);/*Object {add: function}add: function (a,b)__proto__: Object*/
        return (a+b);
    }
};
o["add"](1,2);
</script>

情況三:通過除屬性表達式之外的其他表達式(比如賦值表達式、函數表達式)形式調用

之前兩種方式相比大家都耳熟能詳了,接下來介紹這種方式掌握了就說明知道了js中this的本質。先上來介紹幾個例子。

<script>
"use strict";  /*注意是嚴格模式下*/
(function(a,b){
        console.log(this); /*????*/
        return (a+b);
})(1,2);
</script>
<script>
"use strict";  /*注意是嚴格模式下*/
var o = {
    "add": function(a,b){
        console.log(this); /*????*/
        return (a+b);
    }
};
var a = null;
//這些分別是多少
(a = o["add"])(1,2);
(o["add"])(1,2);
(null,null,o["add"])(1,2);
(o?o["add"]:false)(1,2);
(o["add"]||false)(1,2);
</script>

其實這些本質就是取決於函數調用運算符“()”的操作數經過解析執行後是否是一個引用類型,如果是一個引用類型的可能爲一個對象,如果不是引用類型的話在嚴格模式下必然是undefined,在非嚴格模式下爲window。
對於第一個操作數是一個函數表達式來說,這個返回的是一個函數對象不是一個引用,通過getValue的方式的到的也是這個函數對象所以this爲undefined。
對於下面的賦值運算符返回的也是一個賦值右邊的值也是一個函數對象,還有逗號表達式也是,同樣還有選擇表達式和邏輯運算都是返回的值,如果有機會看ECMAScript官方文檔的話,會發現這些表達式的計算步驟中都會將這些標識符通過getValue已經取值過了,所以都返回的是一個值而不是一個引用,只有(o[“add”])(1,2);重組表達式不一樣,對於()運算目的是提高優先級並沒有計算的意思,所以這種方式可o“add”;一樣返回的人是一個引用{base:o,name:add,strict:false};

情況四:this與構造器相關,這個將會單獨在下一篇博客 (通過new聲明一個對象後都幹了什麼?)中介紹

再一個題目:

function add(){
    var value = 12;
    fun((function s(){  console.log(value)}) );
    s = null;/*防止內存泄漏*/
};
function fun(f){
    var value = -32;
    f();
}
add();

答案:12

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