JavaScript之自我總結篇

最近在看湯姆大叔的"深刻理解JavaScript系列",寫得真的不錯,對於我而言特別是12章到19章,由於大叔研究的點,就主要是從底層來研究JavaScript爲何會出現鍾種特有的語言現象,因此學習了大叔的文章後,再結合《高程》,本身對JavaScript的認知也更明白了,之前好多地方是知其然而不知其因此然,你要問我JavaScript爲何會出現這些現象,我也只能說這是它語言自己的特性嘛。html

如下是看了大叔Javascript系列(12到19章)的自我總結。算法

注:總結中摻雜了我的的觀點以及理解層度,因此有什麼錯誤的地方,還請不吝指教。 chrome

1、深刻理解JavaScript系列(12)之變量對象: 

在大叔這章中,大叔提到了一個概念就是‘變量對象(varibale object)’數組

‘變量對象’,是與執行上下文有關的,由於JavaScript在執行表達式時,總得知道相應的變量存儲在哪吧?否則怎麼獲取或改變對應的變量值呢?閉包

因此引入了一個‘變量對象’的概念。函數

都說了是對象嘛,就是以鍵值對的方式,存儲到變量對象中咯。學習

一、 在進入執行上下文時,‘變量對象’VOui

(1)會將函數的全部形參(若是咱們是在函數執行上下文),以形參名和其對應的值做爲變量對象的屬性,若是形參沒有對應的值,就是undefined咯。this

(2)會將全部函數聲明,以函數名和對應的函數對象做爲變量對象的屬性。若是,變量對象中已經存在了相同名稱的屬性,就徹底替代。spa

(3)會將全部變量聲明,以變量名和其對應值(undefined)做爲變量對象的屬性。若是變量名稱與上述(1)、(2)中的形參名或函數名撞車了,則變量聲明不會去影響        存在的這類屬性。

且,在上面提到,變量對象與執行上下文有關,那麼它們究竟什麼關係呢?

分兩種狀況:

一種狀況就是在進入任何執行上下文以前就建立的對象,此乃全局對象(Global object)global;

另外一種就是,咱們都知道在JavaScript中做用域是以函數function爲基準的,因此函數的變量對象其實就是執行上下文對象;

具體見如下兩幅圖:

 

                   圖一

 

                   圖二

注:函數中的變量對象是不能訪問的。

那爲何全局中的變量對象能訪問呢?

由於能夠經過this或者window,由於window是全局變量global的一個屬性,且引用了global。這也就是爲何在全局變量中訪問標識符時,用this或者直接訪問標識符時,會比用window快的緣由。

二、 在執行代碼時

變量對象VO,已經在進入上下文時,作了預處理。So,接下來就是根據具體的代碼改變對應的值了。

2、深刻理解JavaScript系列(13)之This:

說到this,簡單點嘛就是由調用者決定的,誰調用的就是誰。

以下:

function fn(){
    console.log(this);
};
var foo = {
    bar: fn
};
//輸出的this指向foo
foo.bar();

但JavaScript底層究竟是個怎樣的處理機制呢?

在大叔的這章中,引入了一個‘引用類型(Reference type)’的概念。

引用類型(Reference type),使用僞代碼能夠將其表示爲擁有兩個屬性的對象:

----base,即擁有屬性的那個對象;

----propertyName,即屬性名,從而能夠獲取相應的值。

以下:

且,要返回引用類型的值,只存在兩種狀況:

一、  處理一個標識符時;

二、  處理屬性訪問器( . 或 [ ] )時.

注意:只有兩種狀況哦,要從引用類型中獲得一個屬性值嘛,還須要一步,就是底層調用GetValue方法,從‘引用類型’對象中獲得對應屬性值的值。

咦,講了這麼多和this有什麼相關?

直接摘至大叔:

 

好了,若是理解了上面的流程,下面的幾個例子中this也就OK啦。

'use strict';
var foo = {
    bar: function(){
        console.log(this);
    }
};
foo.bar();//this爲foo
(foo.bar)();//this爲foo (foo.bar = foo.bar)();//this爲undefined (false || foo.bar)();//this爲undefined (foo.bar, foo.bar)();//this爲undefined
3、深刻理解JavaScript系列(14)之做用域鏈:

做用域鏈是上下文全部‘變量對象(varibale object)’的列表,提到‘變量對象’,so此鏈用來變量查詢。且函數上下文的做用域鏈在函數調用時建立,包含活動對象和這個函數內部的[[scope]]屬性,而這個[[scope]]屬性是全部父變量對象的層級鏈,在函數建立時存在其中,且不會改變,即,函數一旦建立了,不管你調或不調用,[[scope]]已存儲在函數對象中了。

當函數被調用時,進入執行上下文activeExecutionContext,其中包含Scope屬性,即做用域鏈。

做用域鏈(Scope)在上下文中具體見下:

activeExecutionContext = {
    VO:{...},//or AO
    this: thisValue,
    Scope/*Scope chain*/: [
        AO + [[Scope]]
    ]
}

Scope又包含變量對象和函數的 [[scope]]屬性。

當咱們解析一個標識符(標識符是變量名,函數名,函數參數名和全局對象中未識別的屬性名)時,解析過程將沿着這條鏈(Scope)去查找,查到就返回它的值,若是在Scope這條鏈中,沒有找到相應的值,就會沿着全局對象這條原型鏈去查找,由於函數活動對象沒有原型。

例子以下:

'use strict';
function foo(){
    function bar(){
        console.log(x);
    };
bar(); }; Object.prototype.x
= 10; this.__proto__.x = 200; foo();//x爲200

另外:經過函構造函數建立的函數的[[scope]]屬性老是惟一的全局對象.

4、深刻理解Javascript系列(15)之函數:
函數聲明與函數表達式的區別:

函數聲明,在‘變量對象’章中已經知道,在進入執行上下文時,會將全部的函數聲明以鍵值對的形式存儲到變量對象VO中。

但,

函數表達式不會添加到變量對象VO中,且在代碼執行階段建立,用完後馬上銷燬。命名函數表達式也同樣哦,由於它是表達式嘛。

例如:

<!DOCTYPE html>
    <head>
        <title>JavaScript</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
            'use strict';
            (function foo(x){
                console.log(x);
            }(1));
            foo(2);//此時會報錯:foo is not defined
        </script>
    </body>
</html>

執行以上代碼,chrome效果圖以下:

和函數表達式不同麼,用完即刻銷燬。那命名函數表達式有什麼用呢?

命名函數表達式,能夠經過名稱遞歸本身嘛。

但,剛纔不是說命名函數表達式和函數表達式都不會添加到變量對象VO中嗎?那它怎麼經過名稱本身調用本身的?

當解釋器在代碼執行階段,遇到命名的函數表達式,解釋器將建立一個輔助的特定對象,並添加到當前做用域鏈的最頂端。而後建立函數表達式,而後將命名函數表達式的名字添加到這個輔助的特定對象中,且值爲該函數引用,當命名函數表達式在其自身調用時,它就在這個特定的對象中找到本身。最後,當命名函數表達式執行完成後,從父做用域鏈中移除那個輔助的特定對象。

具體算法見下:

5、深刻理解JavaScript系列(16)之閉包:

由於做用域鏈,從而使得全部的函數都是閉包。

但,有一類函數比較特殊,那就是經過Function構造器建立的函數,由於其[[Scope]]只包含全局對象。

ECMAScript中,閉包指的是:

一、從理論角度:全部的函數。

    由於它們都在建立的時候,就將上層上下文的數據保存起來了。哪怕是最簡單的全局變量也是如此,由於函數中訪問的全局變量就至關因而訪問自由變量,這個時候使用最外層     的做用域。

二、從實踐角度:如下函數纔算閉包:

   (1)、即時建立它的上下文已經銷燬,它任然存在(好比,內部函數從父函數中返回);

   (2)、在代碼中引用了自由變量。

給出一段經典的代碼:

var data = [];
for( var k = 0; k < 3; k++){
    data[k] = function(){
        alert(k);
    };
};
data[0]();// 3,而不是0
data[1]();// 3,而不是1
data[2]();// 3,而不是2

經常使用的解決方法是,經過閉包,以下:

var data = [];
for( var k = 0; k < 3; k++){
    data[k] = (function _helper(x){
        return function(){
            alert(x);
        };
    })(k);//傳入"k"值
};
//如今結果正確了
data[0]();// 0
data[1]();// 1
data[2]();// 2

除了閉包,咱們還能夠怎麼解決呢?

以下:

var data = [];
for( var k = 0; k < 3; k++){
    (data[k] = function(){
        alert(arguments.callee.x);
    }).x = k;//將k做爲函數的一個屬性
};
//結果也是對的
data[0]();// 0
data[1]();// 1
data[2]();// 2

可是arguments.callee在ECMAScript5中的嚴格模式下是不能用的,因此咱們能夠用命名函數表達式來作。

以下:

var data = [];
for( var k = 0; k < 3; k++){
    (data[k] = function foo(){
        alert(foo.x);
    }).x = k;//將k做爲函數的一個屬性
};
//結果也是對的
data[0]();// 0
data[1]();// 1
data[2]();// 2
6、其餘:

ECMAScript中將對象做爲參數傳遞的策略——按共享傳遞:修改參數的屬性將會影響到外部,而從新賦值將不會影響到外部對象。

其實不只是參數傳遞,其餘都是這樣的策略(對象都是賦予地址值)。

以下:

var foo = {};
//b實際上是添加到function對象中的,而不是foo.a中的,由於foo.a只是引用了function,即獲得了它的地址而已
(foo.a = function(){

}).b = 10;
//獲得10
console.log(foo.a.b);
//將foo.a賦值予變量c
var c = foo.a;
//改變foo.a的值,如一個空對象
foo.a = {};
//輸出c.b,仍是獲得10
console.log(c.b);

數組也是一個對象,因此若是我將數組中的元素指向帶有this的函數,那麼其指向的是數組對象。

//聲明變量arr,並賦值爲數組
var arr = [];
//給arr數組的第一二個元素賦值
arr[0] = function(){
    console.log(this);
    //由於this指向的是arr對象,因此this['1']或this[1],就至關於arr[1]
    this['1']();
};
arr[1] = function(){
    console.log("I'm 1 ");
};
//this指向的是arr對象
arr[0]();

 好了,時間也不早了,晚安~

相關文章
相關標籤/搜索