你不知道的JavaScript上卷筆記


你不知道的JavaScript上卷筆記

前言

You don't know JavaScript是github上一個系列文章
 
初看到這一標題的時候,感覺怎么老外也搞標題黨,用這種沖突性比較強的題目吸引眼球,以致最初真沒去看內容。直到出了中文版《你不知道的JavaScript》,一看評價大家都說好,買來一讀,內容果然很好,很多地方,讓我這個半路轉行JavaScript的人豁然開朗。中文版現在出了上卷,中卷應該很快會推出,下卷就要等久一點了

作用域和閉包

作用域是什么

  1.現代JavaScript已經不再是解釋執行的,而是編譯執行的。但是與傳統的編譯語言不同,它不是提前編譯,編譯結果不能進行移植。編譯過程中,同樣會經過分詞/詞法分析,解析/語法分析,代碼生成三個階段。

  2.以var a = 2;語句為例,對這一程序語句對處理,需要經過引擎,編譯器,作用域三者的配合。其中,引擎從頭到尾負責整個javascript程序的編譯和執行過程;編譯器負責語法分析和代碼生成;作用域負責收集並維護由所有聲明的標識符組成的系列查詢,並實施一套規則,確定當前執行的代碼對這些標識符的訪問權限。

  3.對於var a = 2;編譯器首先查找作用域中是否已經有該名稱的變量,然后引擎中執行編譯器生成的代碼時,會首先查找作用域。如果找到就執行賦值操作,否則就拋出異常

  4.引擎對變量的查找有兩種:LHS查詢RHS查詢。當變量出現中賦值操作左側時是LHS查詢,出現中右側是RHS查詢

詞法作用域

  1.詞法作用域就是定義在詞法階段的作用域。詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪里決定的,詞法處理器分析代碼時會保持作用域不變

  1. function foo(a){ 
        var b = a * 2; 
        function bar(c){ 
            console.log(a,b,c); 
        } 
        bar(b * 3); 
    } 
    foo(2);     
    這個例子有三級嵌套的作用域
  2. 作用域查找會在找到第一個匹配的標識符時停止
  3. eval和with可以欺騙詞法作用域,不推薦使用

函數作用域和塊作用域

  1. JavaScript具有基於函數的作用域,屬於這個函數的變量都可以在整個函數的范圍內使用及復用
  2. (function fun(){})()
    函數表達式和函數聲明的區別是看function關鍵字出現在聲明中的位置。如果function是聲明中的第一個詞,那么就是一個函數聲明,否則就是一個函數表達式
  3. with,try/catch具有塊作用域,方便好用的實現塊級作用域的是es6帶來的let關鍵字

提升

  1.如下示例  

a=2; 
var a; 
console.log(a); 

  上述代碼輸出的不是undefined,而是2。因為上述代碼需要經過編譯器的編譯,編譯過程中首先會進行變量聲明,然后再由引擎進行變量賦值,所以,上述變量聲明雖然寫在第二行,但是聲明過程是首先執行的

  1. console.log(a); 
    var a = 2; 
    上述代碼不是拋ReferenceError異常, 而是輸出undefined
  2. 只有聲明會被提升,賦值及其他運行邏輯會留在原地 
  3. foo(); 
    function foo(){ 
        console.log(a);//undefined 
        var a = 2; 
    }
    foo();//不是ReferenceError,是TypeError var foo = function bar(){}; foo被提升並分配給所在作用域,所以foo()不會導致ReferenceError,但是foo沒有被賦值,對undefined進行函數調用,所以拋出TypeError

作用域閉包

  1. 將內部函數傳遞到所在詞法作用域以外,它都會持有對原始定義作用域的飲用,無論中何處執行這個函數都會使用閉包
  2. 本質上,無論何時何地,如果將函數當作第一級的值類型並到處傳遞,就會看到閉包在這些函數中的應用。在定時器,事件監聽器,ajax請求,web workers或者其他任何異步任務中,只要使用了回調函數,實際上就是在使用閉包
  3. 模塊的封裝利用了閉包,將內部變量隱藏,並返還一個公共api的對象,這一返回的對象對模塊的私有變量形成閉包訪問。

動態作用域

  1.詞法作用域是一套引擎如何尋找變量以及會在何處找到變量的規則。詞法作用域最重要的特征是它的定義過程發生中代碼的書寫階段

  2.動態作用域讓作用域作為一個在運行時就被動態確定的形式,而不是在寫代碼時進行靜態確定的形式。

function foo(){ 
    console.log(a);//2 
} 
function bar(){ 
    var a = 3; 
    foo(); 
} 
var a = 2; 
bar(); 

詞法作用域讓foo()中的a通過RHS引用到了全局作用域中的a,所以輸出2;動態作用域不關心函數和作用域如何聲明以及在何處聲明,只關心從何處調用。換言之,作用域鏈是基於調用棧的,而不是代碼中的作用域嵌套。如果以動態作用域來看,上面代碼中執行時會輸出3

  3.JavaScript不具備動態作用域,但是this機制中某種程度上很像動態作用域,this關注函數如何調用。

this詞法

  1. es6通過箭頭函數,將this同詞法作用域聯系起來了。
  2. 之前如果會遇到this丟失,常見方法時使用本地變量替換this引用 
    var obj = { 
        msg : 'awesome', 
        cool:function(){ 
            setTimeout(function timer(){ 
                console.log(this.msg); 
                },100);     
            } 
        }; 
    var msg = 'not awesome'; 
    obj.cool(); //not awesome 
    
    var obj = { 
        msg : 'awesome',
         cool:function(){ 
        var self = this; setTimeout(function timer(
            console.log(self.msg); 
            },100); 
        }  
    }; 
    var msg = 'not awesome'; 
    obj.cool(); //awesome 
    
    var obj = { 
        msg : 'awesome', 
        cool:function(){ setTimeout(() => { 
            console.log(this.msg); 
            },100); 
        } 
    }; 
    var msg = 'not awesome'; 
    obj.cool(); //awesome 
    
    var obj = { 
        msg : 'awesome', 
        cool:function(){ 
            setTimeout(function timer() { 
                console.log(this.msg); }.bind(this),100); 
                } 
        }; 
    var msg = 'not awesome'; 
    obj.cool(); //awesome            

this和對象原型

this全面解析

  1. 默認綁定。
    function foo(){ 
        console.log(this.a); 
    } 
    var a = 2; foo();//2
    上述示例中,函數調用應用了默認綁定,this指向全局對象
  2. 隱式綁定。調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含。
    function foo(){ 
        console.log(this.a); 
    } 
    var obj={ 
        a:2,
         foo:foo 
    }; 
    obj.foo();//2 
    foo()被調用時,落腳點指向obj對象。當函數引用有上下文對象時,隱式對象將函數調用中的this綁定到上下文對象。
  3. 顯示綁定。使用apply,call或者bind顯示綁定
  4. new綁定。
    • JavaScript中的構造函數只是一些使用new操作符時被調用的函數。它們並不會屬於某個類,也不會實例化一個類。實際上,它們甚至不能說是一種特殊的函數類型,它們只是被new操作符調用的普通函數。
    • 使用new來調用函數,或者說發生構造函數調用,會自動執行下面操作:a)創建一個全新的對象 b)這個新對象會被執行[[原型]]連接 c)這個新對象會綁定到函數調用的this d)如果函數沒有返回其他對象,那么new表達式中的函數調用會自動返回這個新對象
  5. 優先級。new綁定 > 顯示綁定 > 隱式綁定 > 默認綁定

對象

  1. 屬性描述符 
    var obj = {}; 
    Object.defineProperty(
        obj,
        "a",
        { 
            value:2, 
            writable:true, 
            configurable:true, 
            enumerable:true 
        }
    );
    obj.a;//2    
    • writable決定是否可以修改屬性的值,writable為false時,對obj.a的修改會靜默失敗。
    • configurable表示屬性是否可以配置,如果為false,再對obj調用defineProperty來修改設置,會拋出TypeError
    • enumerable表示屬性是否會出現在對象枚舉屬性中,如果為false,就不會出現在for...in循環中
  2. 對象常量。結合使用writable:false和configurable:false可以創建一個真正的常量屬性(不可修改,不可重定義,不可刪除)
  3. 禁止擴展。如果想禁止對一個對象添加新屬性並且保留已有屬性,可以使用Object.preventExtensions()方法
  4. 密封。Object.seal()會創建一個密封的對象,這個方法實際上會在一個現有對象上調用Object.preventExtensions()方法,並且把所有屬性標記為configurable:false
  5. 凍結。Object.freeze()會創建一個凍結對象,這個方法會在現有對象上調用Object.seal()方法,並把所有數據訪問的屬性標記為writable:false
  6. [[get]],[[put]]。
    • var obj = { 
          this._a_ = 2; 
          get a(){ return this._a_; }, 
          set a(val){ this._a_ = val * 2; } 
      }; 
      Object.defineProperty( 
          obj, 
          "b", 
          { 
              get:function(){return this._a_ * 2}, 
              enumerable:true 
          } 
      )

原型

本節為覺得書中寫的有點繞,不清晰。推薦閱讀

  1. constructor, prototype, proto 詳解
  2. JavaScript中proto與prototype的關系
  3. How does proto differ from constructor.prototype?
  4. 理解JavaScript面向對象的思路

行為委托

  1. [[prototype]]機制是對象中一個內部鏈接引用另一個對象 如果中第一個對象上沒找到需要的屬性或者方法引用,引擎就會繼續在[[prototyoe]]關聯的對象上進行查找。同理,如果后者中也沒找到需要的引用,就會繼續查找它的[[prototype]],以此類推,這一系列對象的鏈接稱為原型鏈。換言之,這個機制就是對象之間的關聯關系。
  2. [[prototype]]是一種不同於類的設計模式,它是一種委托行為的設計模式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM