學習ECMAScript標準和具體實現-JavaScript


時間 2016-03-27 16:42:59  阿潘道
原文  http://apsay.com/?p=1618
主題 ECMAScript JavaScript

ECMAScript是實現一種編程語言的標準理論。實踐老是先於理論。ECMAScript標準理論主要來自JavaScript的實際應用。Mozilla基金會是JavaScript的官方組織,Mozilla Developer Network網站有JavaScript相關資料文檔。由於MDN的文檔是衆包形式編輯,過後審查。微軟的JavaScript文檔也不錯,並且翻譯可靠,大公司就是比基金會財大氣粗啊,都是官方出錢翻譯。若是有拿不許的地方,以ECMAScript標準規範文檔爲準。

學習流程,請按順序閱讀,微軟和MDN的JavaScript教程能夠速讀,後面的文章要精讀,深刻學習ECMAScript理論知識。在閱讀示例代碼時,有語句上不懂得地方,能夠查閱微軟和MDN的JavaScript 參考文檔和ECMAScript標準文檔。

儘可能閱讀英文資料,若是實在不懂,也最好先用谷歌翻譯對着原文過一遍,再讀翻譯後的,特別是那些沒有進過校驗審覈的翻譯。閱讀中文翻譯資料的時候,若是遇到閱讀不暢或者有歧義的詞句,必定要查查字典,百科,文檔的英語原文解釋。

JavaScript編程知識學習:

MSDN JavaScript 基礎 https://msdn.microsoft.com/zh-cn/library/6974wx4d(v=vs.94).aspx
MSDN JavaScript 高級 https://msdn.microsoft.com/zh-cn/library/b9w25k6f(v=vs.94).aspx
MDN JavaScript 指南 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide
ECMAScript標準理論知識學習:

Novtopro的文章
http://novtopro.coding.io/2015/10/08/understanding-javascript-execution-context/
http://novtopro.coding.io/2015/10/08/understanding-javascript-lexical-environment-variable-environment/
Dmitry Soshnikov 的 JavaScript 系列文章
ECMA-262-3 in detail. http://dmitrysoshnikov.com/tag/ecma-262-3/ 翻譯 bubkoo http://bubkoo.com/tags/ecmascript/
ECMA-262-5 in detail. http://dmitrysoshnikov.com/tag/es-5/
Jason Orendorff的ES6 In Depth https://hacks.mozilla.org/category/es6-in-depth/ 翻譯 劉振濤 深刻解析 ES6 http://www.infoq.com/cn/es6-in-depth/
Dr. Axel Rauschmayer http://exploringjs.com/  翻譯 https://github.com/es6-org/exploring-es6
文檔手冊:

MDN的JavaScript 參考文檔 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference
微軟MSDN的JavaScript 參考文檔 https://msdn.microsoft.com/zh-cn/library/yek4tbz0(v=vs.94).aspx
ECMAScript最新標準文檔 https://tc39.github.io/ecma262/ 老標準的中文翻譯 http://yanhaijing.com/es5/
在線運行示例代碼的環境:

repl.it https://repl.it/languages 請注意區分JavaScript/JavaScript(Web)/ES2015/Nodejs的不一樣。
codepen http://codepen.io/
轉碼器 Babel/Traceur https://github.com/ES-CN/es6-tools

由於JavaScript引擎/虛擬機/解釋器對ECMAScript新標準的支持老是滯後的,因此須要轉碼器把程序員按新標準寫的JavaScript轉換成如今宿主環境(瀏覽器/服務器)已支持的老標準寫法。

轉碼流程:第一種,提早在開發環境,把ES新標準源碼轉換成老標準源碼後,提交到生產環境加載使用。好比src to lib;第二種,在生產環境實時轉碼。好比瀏覽器先加載轉碼器JS,而後加載新標準JS,並標示是須要轉碼的JS(Traceur:type=」module」)。 https://github.com/google/traceur-compiler/wiki/Getting-Started

學習總結,靈活性過高的代價,就是穩定性太差,容易出錯。簡單靈活的編程語言,其發展路徑每每是愈來愈複雜的語法,更明確和嚴格的規範。

添加到 ES新標準中的新特性並非無章可循,多數在其餘語言中已有或被JS第三方類庫已實現,並且被證實頗有用。

學習ES新標準把One JavaScript同一個JavaScript,Lexical Environment詞法環境,Iteration Protocols迭代協議,Internal Methods標準內部方法,Syntactic sugar 語法糖,這些概念理解透了,由此而新增長的相關語法規則,對象,關鍵字,運算符等就都知道了。

One JavaScript: avoiding versioning in ECMAScript 同一個JavaScript:在ECMAScript中防止出現多版本(兼容原則)。基於兼容原則,之前那些被成爲JavaScript毒瘤的東西都會保留。標準委員會採用了嚴格模式的方式來清理。在新標準中新增長的特性的句法結構裏都默認使用嚴格模式,好比模塊。由於One JavaScript,各類新舊規則太多,都是些陳述性記憶性知識,須要的時候查下資料就好了。

http://www.2ality.com/2014/12/one-javascript.html
Implementation 大意是某個抽象事物的realization or execution。好比JavaScript以及其引擎是ECMAScript的Implementation。不一樣的JavaScript引擎實現方式也是不同的。在Dmitry Soshnikov的文章裏屢次用到SpiderMonkey implementation,another implementation, eg Chrome’s V8等詞句,在出現implementations複數的地方,也列出了各類JavaScript解釋器引擎。

https://en.wikipedia.org/wiki/Implementation
ECMAScript 是基於對象的:基本語言和宿主設施都由對象提供,ECMAScript 程序是一組可通訊的對象。ECMAScript 對象 (objects) 是 屬性 (properties) 的集合,每一個屬性有零個或多個 特性 (attributes),它肯定怎樣使用此屬性。屬性是持有其餘 對象 (objects), 原始值 (primitive values), 函數 (functions) 的容器。函數是可調用對象 (callable object)。A function that is associated with an object via a property is called a method。(注意:ES裏的方法概念是指一個被賦值給一個對象屬性,而且經過這個對象的屬性調用的函數。請必定不要用Class-based programming裏的方法來想固然。)

雖然新ECMAScript的語法加入了類術語,可是ECMAScript對象從根本上就不是基於類。不一樣於類,對象能夠經過多樣的方式建立,包括經過字面量符號,構造器 建立對象,而後執行代碼,這段代碼對它們的屬性分配初始值,初始化它們的所有或一部分。每個構造器都是一個函數,這個函數有一個名字叫作」prototype」 原型的屬性,這個屬性是用來實現基於原型對象的屬性繼承和共享。

在new表達式裏,對象的建立是經過使用構造器。 每個經過構造器建立的對象,它的構造器的原型屬性在內部自動賦值了一個原型對象的引用(obj.constructor.prototype=prototypeObj),而且這個原型對象可能也有個不是null的原型對象引用,依此類推,這就叫作原型鏈。原型鏈也是分級的(hierarchical),然而因爲動態性,它能夠很容易地從新排列,從而改變層級和結構。

基於原型編程(prototype-based programming)是面向對象編程的一種方式。這種編程模型也能夠叫作原型方式,面向原型,無類或者基於實例的編程。委託機制是支持基於原型編程語言的特徵。

在基於原型編程的語言中使用委託機制delegation,語言運行時可以調用正確的方法或者找到正確的數據塊,只是經過跟蹤來自對象原型的委託指針delegation pointers序列或delegation links委託連接序列,直到發現一個匹配的。

ECMAScript就是依靠委託機制實現了基於原型對象的屬性繼承和共享。在ECMAScript裏Cloning是指一個對象link到另外一個對象。那種原樣複製建立另外一個對象的屬性數據的實現叫作concatenative prototyping。新標準的Object.assign方法提供了幾乎徹底複製屬性的功能。徹底克隆:

function clone(orig) {
    let origProto = Object.getPrototypeOf(orig);
    return Object.assign(Object.create(origProto), orig);
}
學習ECMAScript的核心就是要理解這種委託機制,無論是Prototype Chain,仍是Lexical Environment/Scope Chain,都是這種委託機制的具體實現。

一個對象是一個屬性集合,並擁有一個獨立的 prototype原型對象。這個 prototype 能夠是一個對象或者 null。經過 Object.create(null) 方法能夠獲得prototype原型是null的對象。就像純粹的哈希表。

Property是指對象的屬性,而Attribute是指Property的屬性,好比Attributes of a Data Property有[[Value]],[[Writable]],[[Enumerable]],[[Configurable]];Attributes of an Accessor Property有[[Get]],[[Set]],[[Enumerable]],[[Configurable]]。Dmitry也說,So from this viewpoint a property is as an object itself。property’s attributes的大意是屬性對象的屬性。property是一個屬性對象,對象固然能夠有本身的屬性。MDN的翻譯,是把Attribute翻譯成特性,在文檔中有些地方也用flavors代替attributes,並且和屬性描述符descriptor概念息息相關。

一個「屬性(property)」的概念在語義上並不細分爲「鍵(key)」,「數組索引(array index)」,「方法(method)」或「屬性(property)」。它們都是property屬性。屬性訪問器(即 . 和 [])之間沒有語義上的區別。

屬性的讀寫是經過內部方法 [[Get]] 和 [[Put]] 來實現。這兩個方法是經過調用屬性訪問器 —— 點符號或方括號。

除了對象的自有屬性外,[[Get]] 方法也考慮到了對象原型鏈中的屬性。所以原型中的屬性也像對象的自有屬性同樣能夠被訪問到。

若是對原始值使用屬性訪問器取值,訪問以前會先對原始值進行對象包裝,而後經過包裝的對象進行訪問屬性,屬性訪問之後,包裝對象就會被刪除。

對象有一些內部屬性,這些屬性是(編程語言實現引擎的一部分)a part of implementation,不能在 ECMAScript programs 中直接獲得(一些引擎也容許訪問其中的一些屬性)。這些屬性有外加兩個方括號[[ ]] 。

對象的 prototype 是之內部的 [[Prototype]] 屬性來引用的。 對象的[[Prototype]] 內部屬性和構造器的prototype 屬性是不一樣的。實例對象建立時的 [[Prototype]] 是從構造器的 prototype 屬性上得到值,可是,對於構造器的 prototype 屬性的重置不會影響到已建立對象的原型。改變的只是構造器的 prototype 屬性!

因爲實例對象的原型是獨立於它的構造函數和構造函數的 prototype 屬性的,構造函數在完成了它的主要目的 – 建立對象 – 以後能夠被刪除。原型對象將仍然存在,並經過 [[Prototype]] 屬性引用.

ES 中的 Object.getPropertyOf(o) 方法,能夠直接返回一個對象的 [[Prototype]] 屬性 —— 實例的初始原型。然而和 __proto__ 不一樣,這個方法只是一個 getter,它不容許設定原型。JavaScript引擎SpiderMonkey,提供了對於對象原型的顯示引用,經過一個非標準的 __proto__ 屬性。

「foo instanceof Foo」 instanceof 運算符只是獲取左邊實例對象的原型鏈 —— foo.[[Prototype]],檢查原型鏈中是否有運算符右邊的對象構造函數的原型 ——Foo.prototype。

爲了內存佔用的性能優化,方法一般定義在原型中。這意味着,經過一個構造器建立的全部實例對象,老是共用相同的方法。

新標準提供了Proxy object來自定義這些內部方法的行爲。在代理類的捕獲器方法內,若是須要用內部方法的默認行爲,可使用Reflect類,它是個相似Math的工具類,全部方法都是靜態方法,直接使用類名.方法調用。

ECMAScript標準的內部方法 https://tc39.github.io/ecma262/#table-5

不要用C++,JAVA的術語體系來理解ECMAScript。ECMAScript是無類的classless。新標準提供了class等Class-based programming的語句,我反對,這純粹是爲了方便Class-based programming程序員的語法糖,做爲曾經的AS3程序員,真不但願JavaScript變成另外一AS3,那真是沒了Script只有Java了。ECMAScript對象和Class-based programming裏類的對象沒有一點關係。Class-based programming基於類的編程是面向對象編程的另一種方式。

原型委託實現寫法:

// Define the PrototypeA constructor 定義原型對象A的構造函數
function PrototypeA() {
}
// Define the PrototypeB constructor 定義原型對象B構造函數
function PrototypeB() {
  PrototypeA.call(this); // PrototypeB call PrototypeA constructor. 先調用原型對象A的構造函數,把原型對象A的初始化代碼執行一遍,可是this是PrototypeB。
}
// 原型委託實現
PrototypeB.prototype = Object.create(PrototypeA.prototype); //把PrototypeB的原型對象屬性賦值爲一個新對象,這個新對象的原型是PrototypeA的原型對象。PrototypeB加入PrototypeA的原型鏈。
PrototypeB.prototype.constructor = PrototypeB; //把這個新對象的構造函數改成本身的,不改就仍是PrototypeA的構造函數。prototypeB.constructor===PrototypeB.prototype.constructor===PrototypeB
var prototypeB = new PrototypeB();
類繼承語法糖寫法:

class Superclass {
  constructor() {
  }
}
class Subclass extends Superclass {
  constructor() {
    super();
  }
}
https://tc39.github.io/ecma262/#sec-ecmascript-overview
http://yanhaijing.com/es5/#6
https://en.wikipedia.org/wiki/Prototype-based_programming
https://en.wikipedia.org/wiki/Delegation_(programming)
標識符identifier:變量名、函數名、形參,等等。

grammatical語法的,syntactic句法的,Lexical詞法的。

JavaScript編譯的過程包括:1.Lexing(tokenizing)2.Parsing 3.Compiling。代碼執行以前,編譯器對代碼進行編譯,在編譯的Lexing / Tokenizing階段,就肯定全部的標識符是在哪聲明、如何聲明以及在執行階段如何解析和查找。Lexical Scope是在Lexing / Tokenizing階段被肯定下來的做用域,因此Lexical Scope也被稱爲Static Scope。Lexical Scope能夠等同於Lexical Environment。

詞法環境和環境記錄數據的值,只是純粹理論上的空架子,沒必要對應到具體的ECMAScript實現(JavaScript解釋器)中,在ECMAScript程序中不可能直接取得或者操做這樣的數據值。Lexical Environments and Environment Record values are purely specification mechanisms and need not correspond to any specific artefact of an ECMAScript implementation. It is impossible for an ECMAScript program to directly access or manipulate such values.

一個詞法環境是一個規範類型,根據ECMAScript代碼的詞法嵌套結構來定義標識符和具體的變量和函數的關聯關係。一個詞法環境由一個環境記錄和一個外層詞法環境(外層詞法環境多是null)組成。 一般一個詞法環境與ECMAScript代碼的一些具體句法結構有關,好比每次a FunctionDeclaration, a BlockStatement, or a Catch clause of a TryStatement這些代碼的詞法分析完成,一個新的詞法環境就建立完成。

A global environment 是一個詞法環境,它沒有外層詞法環境,它的外層是null。

A module environment 是一個詞法環境,它的外層環境是全局環境global environment。

function environment是一個詞法環境,它可能創建一個新的this標識符綁定關係。

在realm領域的詞法分析完成前,全部ECMAScript代碼必須和一個領域關聯。概念上,一個領域包括一組內部對象,一個ECMAScript全局環境-在這全局環境範圍裏面全部的 ECMAScript代碼已經加載完畢,和其餘相關的資源與狀態。

在語句塊詞法分析的時候,一個新的聲明環境記錄被建立,而且塊範圍內每個變量,常量,函數,函數生成器都被綁定,聲明的類也會被實例化。

我以爲environment那套理論比以前的更容易理解些。這也是爲何在新標準中完全廢棄了scope相關概念,改用environment相關概念來表述。

Executable Code and Execution Contexts

可執行的代碼和代碼的執行上下文(脈絡)。爲了和Environment概念區分,Contexts不建議翻譯成環境,脈絡一詞不錯,能表達出代碼按句法結構執行的時間流的意思。臺灣人多數不翻譯,仍是別翻譯了,Execution Contexts通常縮寫爲EC。

EC是純粹的規範上的機制。ECMAScript code不可能直接訪問或者觀察一個EC。

An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript 解釋器(好比V8)。在時間的任何點上,只存在一個EC,那是事實上正在執行的代碼。這也叫作運行中的EC。

EC棧是用來跟蹤EC的。運做中的EC始終是棧頂的元素。

執行中的代碼有三種狀態,perform執行, suspend暫停, and resume繼續。生成器就是這種機制。

JavaScript解釋器中每個EC的執行都會經歷如下兩個階段:1.建立階段(Creation Stage)2.激活/運行階段(Activation Stage / Code Execution Stage)

在EC Creation Stage,完成當前EC中的函數形參賦值,函數聲明,變量聲明等,此階段賦值都是默認值undefined。注意語句塊EC中的變量在聲明語句前不能夠被使用。

ECMAScript對象有兩個鏈,一個是environment/scope chain(代碼嵌套形式),一個是prototype chain (原型屬性賦值引用形式)。

規範對於environment/scope chain的抽象定義,environment/scope chain是一個對象列表。它和EC有關,是全部EC數據對象的列表,用於在處理標識符時候進行變量查詢。

在查詢過程當中,environment中的局部變量比外層environment的變量會優先被採用。

若是一個屬性在對象中沒有直接找到,查詢將在原型鏈中繼續。即常說的二維鏈查找。(1)environment/scope chain 環節;(2)prototype chain原型鏈環節。

當一個函數在其代碼中引用的標識符不是內部變量/內部函數/形參,那麼這個標識符就稱爲自由變量,查找這些自由變量時就須要用到chain。

ECMAScript的函數參數傳遞策略。學術上明確說法應該是按共享傳遞,原始值是傳遞值的拷貝副本,對象是傳遞引用地址的拷貝副本。引用傳遞是把一個值是對象引用的變量傳遞給函數,函數內的實參名是這個變量的別名,若是把實參從新賦值成另外一個對象引用,也會改變外部這個變量的引用。而共享傳遞是拷貝了一份變量的對象引用地址傳遞進去了,在函數內的實參和外部變量自己已經沒有任何關係了,只不過它們的值,是同一個對象的引用而已。

閉包是代碼塊和建立該代碼塊的EC中數據的結合。在 ECMAScript 中「代碼塊」就是函數。

對 ECMAScript 中的閉包做兩個定義(即兩種閉包):

從理論角度:全部的函數都是閉包。由於它們都在建立時保存了外層EC的數據。函數中使用全局變量也是在使用自由變量,用到了最外層EC數據。

從實踐角度:閉包是建立時的外層EC已經銷燬,但保存了外層EC數據,並使用了外層EC中變量的函數。

對於要實現將局部變量在EC銷燬後仍然保存下來,基於棧的實現顯然是不適用的(由於與基於棧的結構相矛盾)。 所以在這種狀況下,上層做用域的閉包數據是經過動態分配內存的方式來實現的(基於「堆」的實現),配合使用垃圾回收器(garbage collector 簡稱 GC)和引用計數(reference counting)。這種實現方式比基於棧的實現性能要低,然而,任何一種實現老是能夠優化的:能夠分析函數是否使用了自由變量,函數式參數或者函數式值,而後根據狀況來決定 —— 是將數據存放在棧中仍是堆中。

https://tc39.github.io/ecma262/#sec-executable-code-and-execution-contexts
http://dmitrysoshnikov.com/ecmascript/es5-chapter-3-1-lexical-environments-common-theory/
http://yanhaijing.com/es5/#120
https://en.wikipedia.org/wiki/Scope_(computer_science)
內置對象和原生對象是由 ECMAScript 規範和實現器來定義的,它們之間的區別並不大。原生對象(native objects)是指由 ECMAScript 實現引擎提供的所有對象(其中一些是內助對象,另外一些能夠是在程序擴展中建立的,好比用戶定義的對象)。

內置對象(built-in objects)是原生對象的子類型,它們會在程序開始前預先創建到 ECMAScript 中(好比parseInt,Math 等等)。

宿主對象(host objects)是由宿主環境(一般是一個瀏覽器)提供的對象,好比 window,alert 等。

注意,宿主對象多是 ES 自身實現的,徹底符合規範的語義。從這點來講,他們能稱爲「原生宿主」對象(儘快很理論),不過規範沒有定義「原生宿主」對象的概念。

ECMAScript標準中定義了十種數據類型。

在編程語言引擎實現級別才能使用(accessible only at implementation level)的三種:

Reference (解釋諸如 deletetypeofthis 等運算,它由一個基本對象(base object)和屬性名組成。)

List (解釋參數列表的行爲,在 new 表達式和函數調用中)

Completion(解釋 breakcontinuereturn 和 throw 語句的行爲。)

在程序編程中能直接使用(directly accessible)的七種:Undefined Null Boolean Number String Symbol Object

前六種是原始值類型。Object 類型是惟一用來表示 ECMAScript 對象的類型。Object 是一種無序的鍵值對的集合。雖然「對於(typeof 運算中)null 的值應該返回 object 字符串」來實現的(bug in ECMAScript, should be null),但ECMA-262-3 中定義 null 的類型爲 Null。

undefined和null,都是程序中一個惟一的只讀數據,內存中專門指定了一個地址存放這個數據,因此在程序中它們都是相等。變量聲明後,若是沒有賦值語句,則默認值就是undefined。它們的做用用來告訴解釋器兩種特殊狀況:undefined是指解釋器認爲開發者不知道沒有賦值或者開發者故意玩解釋器也行,一般會拋出ReferenceError;null是指解釋器認爲開發者知道值爲null或者認爲開發者應該知道。

var x; var y; console.log(x===y);//true 
console.log(undefined===undefined);//true
var x=nullvar y=null; console.log(x===y);//true 
console.log(null===null);//true
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/undefined
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/null
NaN和undefined、null的意思相似,也是表示一種特殊狀況,在但願獲得正常數字的地方,獲得了一個當前語言不能表示的數。NaN是根據一個規則計算獲得的值,因此NaN不等於NaN,由於每一個NaN的內存地址是不同的,可是卻又在NaN的計算規則範圍內,因此解釋器能夠識別這一類特殊數據。 「not a number」, has a specific meaning for numbers represented as IEEE-754 floating-point values.

console.log(NaN===NaN);//false
console.log(typeof NaN); //number
https://en.wikipedia.org/wiki/NaN
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
規範定義了四個對於原始值特殊包裝類:Boolean-object String-object Number-object Symbol-object。這些對象的建立,是經過相應的內置構造器建立,而且包含原生值做爲其內部屬性。原始數據類型建立一個顯式包裝器對象從 ES6 開始再也不被支持。 然而,現有原始包裝對象,如 new Boolean、 new String以及new Number由於遺留緣由仍可被建立。

對象轉換爲原始值能夠經過valueOf/toString方法。返回默認值根據對象的類型而定。類型轉換能夠經過顯示使用構造函數(constructor as a function)者new 運算符operator 。在使用一些運算符時,也可能會發生顯式和隱式的類型轉換。

Symbol 的設計初衷是爲了不衝突。Symbol 類型的值能夠做爲對象的屬性名,且不與任何其它值相等,不會發生衝突。和數組同樣,symbol-keyed 屬性不能經過 . 運算符來訪問,必須使用[]運算符。

Iteration protocols迭代協議是指兩類協議:可遍歷(可迭代)協議 iterable protocol和 迭代器模式(迭代子模式)協議iterator protocol。可遍歷協議具體到實現就是有一個Symbol.iterator接口方法,方法返回一個迭代器iterator。迭代器模式協議具體實現就是迭代器iterator對象,它有一個next()接口方法而且方法返回一個有done和value屬性的數據對象。

爲了變成可遍歷對象iterable, 一個對象必須實現 @@iterator 方法, 意思是這個對象(或者它原型鏈prototype chain上的某個對象)必須有一個名字是 Symbol.iterator 的屬性.當一個對象須要被遍歷的時候,它的@@iterator方法被調用而且無需參數,而後返回一個用於在遍歷中得到值的迭代器iterator 。

若是一個可遍歷對象的@@iterator方法不是返回一個迭代器對象,那麼它就是一個non-well-formed 可遍歷對象 。使用它會出現運行時異常或者buggy行爲。

能夠爲任何對象實現一個 myObject[Symbol.iterator]() 方法。好比使全部的 jQuery 對象都支持 for-of 語句,jQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

爲何[Symbol.iterator]()語法看起來如此奇怪?問題的關鍵在於方法名,原本正常方法命名能夠是 iterator(),可是歷史代碼可能已經存在名爲「iterator」的方法,這將致使代碼混亂,違背了最大兼容性原則。因此,標準委員會引入全新的 Symbol類型,用Symbol.iterator 做爲屬性名,並用[]形式調用迭代方法,解決了兼容問題。

原本就奇怪,因此寫法儘可能用易懂的方式,好比MDN官方這種寫法someString[Symbol.iterator] = function() {}, myIterable[Symbol.iterator] = function* () {}。很清晰的代表給一個對象的一個叫Symbol.iterator的屬性賦值一個函數。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols

A generator object is both, iterator and iterable一個生成器對象既是迭代器對象也是可遍歷對象。使用Generator 生成器是一種更簡潔可靠的實現自定義迭代器的方式。它就像是迭代器的生產工廠。生成器對象的原型是迭代器對象,因此迭代器的屬性方法都有,生成器實例的next()獲得有done和value屬性的數據對象。

經過generator function能夠獲得一個生成器對象,具體語法通常用function* 表達式。在generator function裏面,不用寫迭代器的next()方法和維護done的值,只用寫業務邏輯和value。用yield 表達式 return value。yield* 表達式還能夠返回另外一個iterable object,而且開始這個可迭代對象的迭代器的單步序列。生成器的next()能夠傳參數給生成器,做爲上一個yield表達式的值,因此第一個next()傳參是沒用的。示例代碼:

function* GenFun() {
    let item = (yield "yield1");
       console.log(item);//next2
       yield "yield2";
}
let genObj = GenFun();
console.log(genObj.next("next1"));//{ value: 'yield1', done: false }
console.log(genObj.next("next2"));//{ value: 'yield2', done: false }
生成器實例另外還提供gen.return(value),這個方法至關於直接執行生成器的最後一個yield 表達式,而且返回一個自定義的值,和最後一個yield表達式的value同樣或者不同都行,而且修改done的值爲true。須要注意,若是done已是true,再用return()方法,自定義值無效,和next()同樣都是返回undefined。還有個gen.throw(exception)方法,拋出個錯誤。

Promise和Generator很像,MDN參考文檔也把它們放一塊,若是說Generator是封裝了迭代器的細節實現代碼,開發者只須要關注於邏輯和結果,那麼Promise也是這樣。簡化了異步回調的實現。Generator能夠和Promise結合使用實現Async Functions。

https://tc39.github.io/ecmascript-asyncawait/
String, Array, TypedArray, Map and Set 是全部內置可遍歷對象, 由於它們的原型對象都有一個 @@iterator 方法.

the for-of loops, spread operator, yield*, and destructuring assignment一般用於可遍歷對象操做。

spread operator容許一個表達式在某處展開,在多個參數(用於函數調用)或者多個元素(用於數組字面量)或者多個變量(用於解構賦值)的地方就會這樣。…運算符只有用於可遍歷對象纔有效。

var obj = {"key1":"value1"};
function myFunction(x) {
  console.log(x); // undefined
}
myFunction(...obj);
var args = [...obj];
console.log(args, args.length) //[] 0
Destructuring assignment解析賦值表達式,就是從表達式右邊的數組或者對象結構中自動解析數據,並把解析獲得的數據賦值給對應的變量。注意,若是左邊的變量是數組方括號結構,則右邊必須是可遍歷對象。若是左邊的對象花括號結構,則右邊只要是對象就行,原始值有包裝對象的會強制轉換,也就是說除 null 和 undefined 外任何值。記住Object類不是iterable,固然你能夠把它變成iterable。在MDN手冊中,Destructuring assignment解析賦值也歸類於Assignment operators賦值運算符。

let [num,...bars] = [1, 2, 3];
console.log(bars);// [ 2, 3 ]
let [,,bar] = [1, 2, 3];
console.log(bar);//3
在NDN的JavaScript Guide裏,Array和Map,Set都屬於collections of data。它們的區別就是,Array是ordered by an index value, Map,Set是ordered by a key。Map相似無屬性的Object,Set相似Array,可是數據存取效率更高,適合大量數據。Map and Set的遍歷順序就是插入數據的順序,先插入的先出來(FIFO—first in first out)。

Map and Set 用new生成實例的時候,構造函數參數只有是可遍歷對象才能轉換爲集合數據,注意Map傳入的可遍歷對象的迭代器方法返回的value必須是能轉換成key-value pair形式。示例代碼:

function* gen() { yield 1; }
let g = gen();
let mapData = new Map(g); //TypeError: Iterator value 1 is not an entry object

function* gen() { yield [1]; }
let g = gen();
let mapData = new Map(g);
console.log([...mapData]); //[ [ 1, undefined ] ]

function* gen() { yield {"1":1}; }
let g = gen();
let mapData = new Map(g);
console.log([...mapData]); //[ [ undefined, 1 ] ]

var myArray = ["value1"];
var mySet = new Set(myArray);
let mapData = new Map(mySet[Symbol.iterator]()); //TypeError: Iterator value value1 is not an entry object

var kvArray = [["key1", "value1"]];
var myMap = new Map(kvArray);
let mapData = new Map(myMap[Symbol.iterator]());
console.log([...mapData]); //[ [ 'key1', 'value1' ] ]
Map用set方法插數據,key相等的只插一次。把Map自定義Key的行爲去掉,就是Set,Set用add方法插值,在一個Set數據裏相等的值只插一次。Map,Set的相等判斷規則和Object.is()同樣。

遍歷Map獲得數據是數組[key, value]形式。遍歷方法通常用for…of ,Map寫法for (let [key, value] of mapData){},Set寫法for (let item of setData){};二般用迭代器,用Map,Set實例的迭代器方法,好比maporset[Symbol.iterator]() 能夠獲得這個實例的迭代器,用迭代器的next()方法能夠按FIFO順序單步獲得集合的數據。除了遍歷外,Map還能夠經過get方法,Set不知道key沒有get方法。

WeakMap,WeakSet是爲了更無腦的GC創造的,就是弱引用的Map和Set,它們不是可遍歷對象,不能遍歷。Map的鍵名和Set的值只能是Object類型。

爲了塊級詞法環境的具體實現,因而有了let和const,爲何不直接從新定義var,而搞個let,爲了One JavaScript.

let和const聲明關鍵字用來定義變量,它們的存活範圍僅限於當前運行的EC的詞法環境。在它們的詞法環境實現的時候,這些變量被建立,可是沒有任何方式得到它們,直到這些變量的詞彙綁定器計算完成。在詞彙綁定計算的時候,一個變量定義被一個詞彙綁定器和初始化器根據它的初始化賦值表達式完成賦值。完成以上步驟變量纔算建立完成。在詞彙綁定器計算的時候,若是用let聲明的變量沒有賦值表達式,詞彙綁定器會給這個變量賦值undefined.

在標準文檔中,let and const的描述比var的描述,多了but may not be accessed in any way until。就是在完成賦值表達式以前不能使用,也叫暫存死區TDZ (Temporal Dead Zone) 。有人說let和const不存在變量提高,實際上是錯誤的,只是限制accessed而已。標準明確說了The variables are created when their containing Lexical Environment is instantiated but may not be accessed。

新加的詞法環境,都存在TDZ,ECMAScript的詞法環境機制決定了標識符都會在詞法分析階段就綁定,也叫作提高,可是let,const,class,函數形參,模塊在其語句執行完成前都不能使用。固然也能夠改變提高的定義,把TDZ的狀況排除到提高的範圍外,好比提高就是指之前var那種表現,那以後新加的詞法環境確實都不存在提高問題。

https://tc39.github.io/ecma262/#sec-declarations-and-the-variable-statement
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
let是一個更嚴格的var。(忘了var吧,let值得擁有)

let繼承var的規範,若是var規範和如下規範衝突,則採用如下規範。

用let定義的全局範圍變量,不會成爲全局對象的屬性。

let能夠把變量的做用範圍限制在塊級。塊級域,用一對花括號限定的區域,一般特指流程控制語句塊 ,如 if,for等。for語句的圓括號部分和花括號部分屬於同一塊級域。

用let定義的變量,在變量所屬做用域內,不包括嵌套域,再用let定義一個同名變量將引發 TypeError。

用let定義的變量,在變量所屬做用域外部,使用它將引發 ReferenceError。

用let定義的變量,在定義語句以前,使用它將引發 ReferenceError。

const是一個更嚴格的let。

const繼承let的規範,若是let規範和如下規範衝突,則採用如下規範。

const定義的變量,必須初始化賦值,只能賦值一次,之後不能更改,這就是常量。建議常量名大寫。

若是常量的值是指向內存的地址,常量只是不能再經過賦值改變引用地址,管不着引用對象。

Note: => is not an operator, but the notation for Arrow functions.注意=>不是一個運算符,它不過是箭頭函數的特有符號。箭頭函數的核心就是=>符號,主要是精簡函數表達式的寫法,特別在函數只有一個參數和函數體只有一個分號的時候,能夠省略function和return 關鍵字,圓括號,花括號,好比var foo = x => x; 夠簡單吧,呵呵,寫起來爽,讀起來就沒那麼直觀了,就和用邏輯或代替if else的變量賦值表達式同樣,var bar = x || 1; 並且在有多個參數和函數體有多個語句的時候,就只比標準寫法省略個function關鍵字。固然還有this和arguments的特性,可是不能指定this值則更限制了它的使用範圍。開發者顯式指定this值是個好習慣也更安心,我更喜歡接近人類語言的代碼書寫方式。

一個ArrowFunction 並不能給arguments, super, this, or new.target定義本地綁定local bindings。在an ArrowFunction內arguments, super, this, or new.target的值引用必須在包裹箭頭函數的外層詞法環境中進行綁定。一般this會是句法上包裹箭頭函數的那個函數的詞法環境。箭頭函數沒有本身的詞法環境,因此多個嵌套的箭頭函數,它們的this都是最外層那個有獨立詞法環境的句法結構體的環境。

https://tc39.github.io/ecma262/#sec-arrow-function-definitions-runtime-semantics-evaluation
函數參數的新特性,Default parameters就是能夠在函數聲明的時候給圓括號裏的形參使用賦值表達式,注意在函數調用時,默認參數賦值語句要比花括號裏面的語句先執行,執行順序是從左到右。Rest parameters 剩餘參數,語法形式是spread operator …加形參名。能夠用來取代arguments。 rest parameters are Array instances。

對象中函數方法的新寫法,就是省掉冒號:和function關鍵字…

var obj = {
        // 如今再也不使用function關鍵字給對象添加方法
        // 而是直接使用屬性名做爲函數名稱。
        method(args) { ... },
        // 只需在標準函數的基礎上添加一個「*」,就能夠聲明一個生成器函數。
        *genMethod(args) { ... },
        // 藉助|get|和|set|能夠在行內定義訪問器。
        // 只是定義內聯函數,即便沒有生成器。
        // 注意經過這種方式裝載的getter不能接受參數
        get propName() { ... },
        // 注意經過這種方式裝載的setter至少接受一個參數
        set propName(arg) { ... },
        // []語法能夠用於任意支持預計算屬性名的地方,來知足上面的第4中狀況。
        // 這意味着你可使用symbol,調用函數,聯結字符串
        // 或其它能夠給property.id求值的表達式。
        // 這個語法對訪問器或生成器一樣有效,我在這裏只是舉個例子。
        [functionThatReturnsPropertyName()] (args) { ... }
    };
this 是一個與EC密切相關的特殊對象,所以,它能夠稱爲EC對象(context object)。任何對象均可以作爲上下文中的 this 的值。

this 是EC的一個屬性。this 與EC類型緊密相關,其值在進入EC階段就肯定了,而且在執行代碼階段不能被改變。

在全局EC中,this 就等於全局對象自己。在函數上下文的狀況下,對函數的每次調用,其中的 this 值多是不一樣的。函數上下文中 this 的值由調用者(caller)提供,並由調用表達式的形式肯定(函數調用的語法)。相同的函數,調用形式和調用者不一樣,this的值也可能不一樣。可使用Function.prototype.bind(),Function.prototype.call(),Function.prototype.apply()方法指定函數調用時內部的this對象。

this在嚴格模式下有特有的規則。

Template literals 模板字面量一般用來替代之前那種字符串+變量或函數表達式的拼接寫法。Tagged template literals標籤模板字面量,就是爲模板字面量提供自定義行爲處理函數Tag functions。模板字面量相關規則和形式,有點像之前String.prototype.replace()的$和replacement function 。

模塊 Modules, 新Statements,export和import,自己沒什麼好說的,反正在新標準出來以前,已經在大量使用了,去學webpack之類的東西吧。javascript

相關文章
相關標籤/搜索