你不知道的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.詞法作用域就是定義在詞法階段的作用域。詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪里決定的,詞法處理器分析代碼時會保持作用域不變
-
function foo(a){ var b = a * 2; function bar(c){ console.log(a,b,c); } bar(b * 3); } foo(2);
- 作用域查找會在找到第一個匹配的標識符時停止
- eval和with可以欺騙詞法作用域,不推薦使用
函數作用域和塊作用域
- JavaScript具有基於函數的作用域,屬於這個函數的變量都可以在整個函數的范圍內使用及復用
(function fun(){})()
函數表達式和函數聲明的區別是看function關鍵字出現在聲明中的位置。如果function是聲明中的第一個詞,那么就是一個函數聲明,否則就是一個函數表達式- with,try/catch具有塊作用域,方便好用的實現塊級作用域的是es6帶來的let關鍵字
提升
1.如下示例
a=2; var a; console.log(a);
上述代碼輸出的不是undefined,而是2。因為上述代碼需要經過編譯器的編譯,編譯過程中首先會進行變量聲明,然后再由引擎進行變量賦值,所以,上述變量聲明雖然寫在第二行,但是聲明過程是首先執行的
-
console.log(a); var a = 2;
- 只有聲明會被提升,賦值及其他運行邏輯會留在原地
-
foo(); function foo(){ console.log(a);//undefined var a = 2; }
作用域閉包
- 將內部函數傳遞到所在詞法作用域以外,它都會持有對原始定義作用域的飲用,無論中何處執行這個函數都會使用閉包
- 本質上,無論何時何地,如果將函數當作第一級的值類型並到處傳遞,就會看到閉包在這些函數中的應用。在定時器,事件監聽器,ajax請求,web workers或者其他任何異步任務中,只要使用了回調函數,實際上就是在使用閉包
- 模塊的封裝利用了閉包,將內部變量隱藏,並返還一個公共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詞法
- es6通過箭頭函數,將this同詞法作用域聯系起來了。
- 之前如果會遇到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全面解析
- 默認綁定。
function foo(){ console.log(this.a); } var a = 2; foo();//2
- 隱式綁定。調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含。
function foo(){ console.log(this.a); } var obj={ a:2, foo:foo }; obj.foo();//2
- 顯示綁定。使用apply,call或者bind顯示綁定
- new綁定。
- JavaScript中的構造函數只是一些使用new操作符時被調用的函數。它們並不會屬於某個類,也不會實例化一個類。實際上,它們甚至不能說是一種特殊的函數類型,它們只是被new操作符調用的普通函數。
- 使用new來調用函數,或者說發生構造函數調用,會自動執行下面操作:a)創建一個全新的對象 b)這個新對象會被執行[[原型]]連接 c)這個新對象會綁定到函數調用的this d)如果函數沒有返回其他對象,那么new表達式中的函數調用會自動返回這個新對象
- 優先級。new綁定 > 顯示綁定 > 隱式綁定 > 默認綁定
對象
- 屬性描述符
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循環中
- 對象常量。結合使用writable:false和configurable:false可以創建一個真正的常量屬性(不可修改,不可重定義,不可刪除)
- 禁止擴展。如果想禁止對一個對象添加新屬性並且保留已有屬性,可以使用Object.preventExtensions()方法
- 密封。Object.seal()會創建一個密封的對象,這個方法實際上會在一個現有對象上調用Object.preventExtensions()方法,並且把所有屬性標記為configurable:false
- 凍結。Object.freeze()會創建一個凍結對象,這個方法會在現有對象上調用Object.seal()方法,並把所有數據訪問的屬性標記為writable:false
- [[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 } )
-
原型
本節為覺得書中寫的有點繞,不清晰。推薦閱讀
- constructor, prototype, proto 詳解
- JavaScript中proto與prototype的關系
- How does proto differ from constructor.prototype?
- 理解JavaScript面向對象的思路
行為委托
- [[prototype]]機制是對象中一個內部鏈接引用另一個對象 如果中第一個對象上沒找到需要的屬性或者方法引用,引擎就會繼續在[[prototyoe]]關聯的對象上進行查找。同理,如果后者中也沒找到需要的引用,就會繼續查找它的[[prototype]],以此類推,這一系列對象的鏈接稱為原型鏈。換言之,這個機制就是對象之間的關聯關系。
- [[prototype]]是一種不同於類的設計模式,它是一種委托行為的設計模式。