1、ES5/ES6和babelnode
ECMAScript5,即ES5,是ECMAScript的第五次修訂,於2009年完成標準化,如今的瀏覽器已經至關於徹底實現了這個標準。
ECMAScript6,即ES6,也稱ES2015,是ECMAScript的第六次修訂,於2015年完成,而且運用的範圍逐漸開始擴大,由於其相對於ES5更加簡潔,提升了開發速率,開發者也都在陸續進行使用,可是因爲ES6還存在一些支持的問題,因此通常即便是使用ES6開發的工程,也須要使用Babel進行轉換。
Babel是一個普遍使用的ES6轉碼器,能夠將ES6代碼轉爲ES5代碼,從而在現有環境執行。這一過程叫作「源碼到源碼」編譯, 也被稱爲轉換編譯。es6
通常來講Babel做爲依賴包被引入ES6工程中,此處再也不介紹以cli方式使用的ES6,若是你須要以編程的方式來使用 Babel,可使用 babel-core 這個包。babel-core 的做用是把 js 代碼分析成 ast ,方便各個插件分析語法進行相應的處理。有些新語法在低版本 js 中是不存在的,如箭頭函數,rest 參數,函數默認值等,這種語言層面的不兼容只能經過將代碼轉爲 ast,分析其語法後再轉爲低版本 js。babel的使用過程以下:npm
$ npm install babel-core
var babel = require("babel-core");
字符串形式的 JavaScript 代碼能夠直接使用 babel.transform 來編譯。
編程
若是是文件的話,可使用異步 api:json
或者是同步 api:api
或者在development環境下可使用bable-node和bable-register的方式配置,過程以下:數組
在Node.js工程package.json包中添加以下依賴:瀏覽器
2. 配置dev腳本服務器
接下來羅列一下ES6的語法要點參考備用。babel
這兩個的用途與var相似,都是用來聲明變量的,但在實際運用中他倆都有各自的特殊用途。
首先來看下面這個例子:
使用var 兩次輸出都是obama,這是由於ES5只有全局做用域和函數做用域,沒有塊級做用域,這帶來不少不合理的場景。第一種場景就是你如今看到的內層變量覆蓋外層變量。而let則實際上爲JavaScript新增了塊級做用域。用它所聲明的變量,只在let命令所在的代碼塊內有效。
另一個var帶來的不合理場景就是用來計數的循環變量泄露爲全局變量,看下面的例子:
上面代碼中,變量i是var聲明的,在全局範圍內都有效。因此每一次循環,新的i值都會覆蓋舊值,致使最後輸出的是最後一輪的i的值。而使用let則不會出現這個問題。
const也用來聲明變量,可是聲明的是常量。一旦聲明,常量的值就不能改變。
ES6 容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)。
解構不成功時變量賦值爲undefined
存在不徹底解構的狀況以下:
解構賦值容許指定默認值。ES6 內部使用嚴格相等運算符(===),判斷一個位置是否有值。因此,只有當一個數組成員嚴格等於undefined,默認值纔會生效。
以下是解構賦值的應用實例:
以下兩種函數的定義方法在解構賦值時具有不一樣的返回值:
模板字符串(template string)是加強版的字符串,用反引號(`)標識。它能夠看成普通字符串使用,也能夠用來定義多行字符串,或者在字符串中嵌入變量。
若是在模板字符串中須要使用反引號,則前面要用反斜槓轉義。
let greeting = `\`Yo\` World!`;
大括號內部能夠放入任意的 JavaScript 表達式,能夠進行運算,以及引用對象屬性。若是大括號中的值不是字符串,將按照通常的規則轉爲字符串。好比,大括號中是一個對象,將默認調用對象的toString方法。
模板字符串它能夠緊跟在一個函數名後面,該函數將被調用來處理這個模板字符串。這被稱爲「標籤模板」功能(tagged template)。
模板字符串前面有一個標識名tag,它是一個函數。整個表達式的返回值,就是tag函數處理模板字符串後的返回值。函數tag依次會接收到多個參數。
tag函數的第一個參數是一個數組,該數組的成員是模板字符串中那些沒有變量替換的部分,也就是說,變量替換隻發生在數組的第一個成員與第二個成員之間、第二個成員與第三個成員之間,以此類推。tag函數的其餘參數,都是模板字符串各個變量被替換後的值。因爲本例中,模板字符串含有兩個變量,所以tag會接受到value1和value2兩個參數。也就是說,tag函數實際上如下面的形式調用。
tag(['Hello ', ' world ', ''], 15, 50)
ES6 引入 rest 參數(形式爲...變量名),用於獲取函數的多餘參數,這樣就不須要使用arguments對象了。rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。
arguments對象不是數組,而是一個相似數組的對象。因此爲了使用數組的方法,必須使用Array.prototype.slice.call先將其轉爲數組。rest 參數就不存在這個問題,它就是一個真正的數組,數組特有的方法均可以使用。
rest函數的實現也是基於擴展運算符,擴展運算符(spread)是三個點(...)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。
擴展運算符提供了複製數組的簡便寫法。
擴展運算符提供了數組合並的新寫法。
ES6 容許使用「箭頭」(=>)定義函數。
若是箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,而且使用return語句返回。若是箭頭函數不須要參數或須要多個參數,就使用一個圓括號表明參數部分。
因爲大括號被解釋爲代碼塊,因此若是箭頭函數直接返回一個對象,必須在對象外面加上括號,不然會報錯。
箭頭函數能夠與變量解構結合使用。
箭頭函數的一個用處是簡化回調函數。
箭頭函數有幾個使用注意點。
(1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。
(2)不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。
(3)不可使用arguments對象,該對象在函數體內不存在。若是要用,能夠用 rest 參數代替。
(4)不可使用yield命令,所以箭頭函數不能用做 Generator 函數。
上面四點中,第一點尤爲值得注意。this對象的指向是可變的,可是在箭頭函數中,它是固定的。
上面代碼中,setTimeout的參數是一個箭頭函數,這個箭頭函數的定義生效是在foo函數生成時,而它的真正執行要等到 100 毫秒後。若是是普通函數,執行時this應該指向全局對象window,這時應該輸出21。可是,箭頭函數致使this老是指向函數定義生效時所在的對象(本例是{id: 42}),因此輸出的是42。
ES6 容許直接寫入變量和函數,做爲對象的屬性和方法。這樣的書寫更加簡潔。
ES6 容許在對象之中,直接寫變量。這時,屬性名爲變量名, 屬性值爲變量的值。下面是另外一個例子。
函數的name屬性,返回函數名。對象方法也是函數,所以也有name屬性。
在對象的繼承、原型和構造函數上ES6提供了更接近傳統語言的寫法,引入了Class(類)這個概念。新的class寫法讓對象原型的寫法更加清晰、更像面向對象編程的語法,也更加通俗易懂。
上面代碼首先用class定義了一個「類」,能夠看到裏面有一個constructor方法,這就是構造方法,而this關鍵字則表明實例對象。簡單地說,constructor內定義的方法和屬性是實例對象本身的,而constructor外定義的方法和屬性則是全部實例對象能夠共享的。
Class之間能夠經過extends關鍵字實現繼承,這比ES5的經過修改原型鏈實現繼承,要清晰和方便不少。上面定義了一個Cat類,該類經過extends關鍵字,繼承了Animal類的全部屬性和方法。
super關鍵字,它指代父類的實例(即父類的this對象)。子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類沒有本身的this對象,而是繼承父類的this對象,而後對其進行加工。若是不調用super方法,子類就得不到this對象。
ES6的繼承機制,實質是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this。
ES6 一共有 5 種方法能夠遍歷對象的屬性。
(1)for...in
for...in循環遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。
(2)Object.keys(obj)
Object.keys返回一個數組,包括對象自身的(不含繼承的)全部可枚舉屬性(不含 Symbol 屬性)的鍵名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一個數組,包含對象自身的全部屬性(不含 Symbol 屬性,可是包括不可枚舉屬性)的鍵名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一個數組,包含對象自身的全部 Symbol 屬性的鍵名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一個數組,包含對象自身的全部鍵名,無論鍵名是 Symbol 或字符串,也無論是否可枚舉。
以上的 5 種方法遍歷對象的鍵名,都遵照一樣的屬性遍歷的次序規則。
ES6 引入了一種新的原始數據類型Symbol,表示獨一無二的值。它是 JavaScript 語言的第七種數據類型,前六種是:undefined、null、布爾值(Boolean)、字符串(String)、數值(Number)、對象(Object)。
Symbol 值經過Symbol函數生成。這就是說,對象的屬性名如今能夠有兩種類型,一種是原來就有的字符串,另外一種就是新增的 Symbol 類型。凡是屬性名屬於 Symbol 類型,就都是獨一無二的,能夠保證不會與其餘屬性名產生衝突。
因爲每個 Symbol 值都是不相等的,這意味着 Symbol 值能夠做爲標識符,用於對象的屬性名,就能保證不會出現同名的屬性。這對於一個對象由多個模塊構成的狀況很是有用,能防止某一個鍵被不當心改寫或覆蓋(Symbol 值做爲對象屬性名時,不能用點運算符,由於點運算符後面老是字符串,因此不會讀取mySymbol做爲標識名所指代的那個值,致使a的屬性名其實是一個字符串,而不是一個 Symbol 值)。
Object.getOwnPropertySymbols方法返回一個數組,成員是當前對象的全部用做屬性名的 Symbol 值。
Symbol能夠用於實現單例模式:
上面代碼中,能夠保證global[FOO_KEY]不會被無心間覆蓋,但仍是能夠被改寫。
ES6 提供了新的數據結構 Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。Set 自己是一個構造函數,用來生成 Set 數據結構。
Set 函數能夠接受一個數組(或者具備 iterable 接口的其餘數據結構)做爲參數,用來初始化。
Set的遍歷順序就是插入順序。這個特性有時很是有用,好比使用 Set 保存一個回調函數列表,調用時就能保證按照添加順序調用。
keys方法、values方法、entries方法返回的都是遍歷器對象,因爲 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys方法和values方法的行爲徹底一致。
ES6 提供了 Map 數據結構。它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object 結構提供了「字符串—值」的對應,Map 結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。若是你須要「鍵值對」的數據結構,Map 比 Object 更合適。
與其餘數據結構的互相轉換
(1)Map 轉爲數組
前面已經提過,Map 轉爲數組最方便的方法,就是使用擴展運算符(...)。
(2)數組 轉爲 Map
將數組傳入 Map 構造函數,就能夠轉爲 Map。
(3)Map 轉爲對象
若是全部 Map 的鍵都是字符串,它能夠轉爲對象。
(4)對象轉爲 Map
(5)Map 轉爲 JSON
Map 轉爲 JSON 要區分兩種狀況。一種狀況是,Map 的鍵名都是字符串,這時能夠選擇轉爲對象 JSON。
另外一種狀況是,Map 的鍵名有非字符串,這時能夠選擇轉爲數組 JSON。
(6)JSON 轉爲 Map
JSON 轉爲 Map,正常狀況下,全部鍵名都是字符串。
ES6 模塊不是對象,而是經過export命令顯式指定輸出的代碼,再經過import命令輸入。
上面代碼的實質是從fs模塊加載 3 個方法,其餘方法不加載。這種加載稱爲「編譯時加載」或者靜態加載,即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象。
因爲 ES6 模塊是編譯時加載,使得靜態分析成爲可能。有了它,就能進一步拓寬 JavaScript 的語法,好比引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。
除了靜態加載帶來的各類好處,ES6 模塊還有如下好處。
ES6 的模塊自動採用嚴格模式,無論你有沒有在模塊頭部加上"use strict";。嚴格模式主要有如下限制。
模塊功能主要由兩個命令構成:export和import。export命令用於規定模塊的對外接口,import命令用於輸入其餘模塊提供的功能。一個模塊就是一個獨立的文件。該文件內部的全部變量,外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。下面是一個 JS 文件,裏面使用export命令輸出變量。
上面代碼是profile.js文件,保存了用戶信息。ES6 將其視爲一個模塊,裏面用export命令對外部輸出了三個變量。export的寫法,除了像上面這樣,還有另一種。
export命令除了輸出變量,還能夠輸出函數或類(class)。一般狀況下,export輸出的變量就是原本的名字,可是可使用as關鍵字重命名。
export語句輸出的接口,與其對應的值是動態綁定關係,即經過該接口,能夠取到模塊內部實時的值。
export命令能夠出如今模塊的任何位置,只要處於模塊頂層就能夠。若是處於塊級做用域內,就會報錯,下一節的import命令也是如此。這是由於處於條件代碼塊之中,就無法作靜態優化了,違背了 ES6 模塊的設計初衷。
使用export命令定義了模塊的對外接口之後,其餘 JS 文件就能夠經過import命令加載這個模塊。
上面代碼的import命令,用於加載profile.js文件,並從中輸入變量。import命令接受一對大括號,裏面指定要從其餘模塊導入的變量名。大括號裏面的變量名,必須與被導入模塊(profile.js)對外接口的名稱相同。若是想爲輸入的變量從新取一個名字,import命令要使用as關鍵字,將輸入的變量重命名。
import { lastName as surname } from './profile.js';
import命令具備提高效果,會提高到整個模塊的頭部,首先執行。
使用import命令的時候,用戶須要知道所要加載的變量名或函數名,不然沒法加載。可是,用戶確定但願快速上手,未必願意閱讀文檔,去了解模塊有哪些屬性和方法。爲了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default命令,爲模塊指定默認輸出。
上面代碼是一個模塊文件export-default.js,它的默認輸出是一個函數。其餘模塊加載該模塊時,import命令能夠爲該匿名函數指定任意名字。
上面代碼的import命令,能夠用任意名稱指向export-default.js輸出的方法,這時就不須要知道原模塊輸出的函數名。須要注意的是,這時import命令後面,不使用大括號。
export default命令用於指定模塊的默認輸出。顯然,一個模塊只能有一個默認輸出,所以export default命令只能使用一次。因此,import命令後面纔不用加大括號,由於只可能惟一對應export default命令。本質上,export default就是輸出一個叫作default的變量或方法,而後系統容許你爲它取任意名字。
若是在一個模塊之中,先輸入後輸出同一個模塊,import語句能夠與export語句寫在一塊兒。
引擎處理import語句是在編譯時,這時不會去分析或執行if語句,因此import語句放在if代碼塊之中毫無心義,所以會報句法錯誤,而不是執行時錯誤。也就是說,import和export命令只能在模塊的頂層,不能在代碼塊之中(好比,在if代碼塊之中,或在函數之中)。
這樣的設計,當然有利於編譯器提升效率,但也致使沒法在運行時加載模塊。在語法上,條件加載就不可能實現。若是import命令要取代 Node 的require方法,這就造成了一個障礙。由於require是運行時加載模塊,import命令沒法取代require的動態加載功能。
上面的語句就是動態加載,require到底加載哪個模塊,只有運行時才知道。import語句作不到這一點。
import()加載模塊成功之後,這個模塊會做爲一個對象,看成then方法的參數。所以,可使用對象解構賦值的語法,獲取輸出接口。
上面代碼中,export1和export2都是myModule.js的輸出接口,能夠解構得到。若是模塊有default輸出接口,能夠用參數直接得到。
如上就是ES6中比較容易識別出的關鍵點,實際上在兩天半的ES6使用中也確實見到了如上的用法,感謝阮一峯老師的博客,先總結到這裏,留待後補?