發展歷史
2000年,ECMAScript 4.0開始醞釀。這個版本最後沒有通過,但是它的大部分內容被ECMAScript6繼承了。因此,ECMAScript6制定的起點其實是2000年。
2007年10月,ECMAScript 4.0草案發布,本來預計2008年8月發布正式版本。但是,各方對於是否通過這個標準,發生了嚴重分歧。以Yahoo、Microsoft、Google為首的大公司,反對JavaScript的大幅升級,主張小幅改動;以JavaScript創造者Brendan Eich為首的Mozilla公司,則堅持當前的草案。
2008年7月,由於對於下一個版本應該包括哪些功能,各方分歧太大,爭論過於激烈,ECMA開會決定,中止ECMAScript 4.0的開發,將其中涉及現有功能改善的一小部分,發布為ECMAScript 3.1,而將其他激進的構想擴大範圍,放入以後的版本,由於會議的氣氛,該版本的項目代號起名為Harmony(和諧)。會後不久,ECMAScript 3.1就改名為ECMAScript 5。
2009年12月,ECMAScript 5.0正式發布。Harmony項目則一分為二,一些較為可行的構想定名為 JavaScript.next繼續開發,後來演變成ECMAScript 6;一些不是很成熟的構想,則被視為JavaScript.next.next,在更遠的將來再考慮推出。
2011年,ECMAScript 5.1發布後開始6.0版的制定。
2013年3月,ECMAScript 6草案凍結,不再添加新功能。新的功能構想將被放到ECMAScript 7。
2013年12月,ECMAScript 6草案發布。然後是12個月的討論期,聽取各方反饋。由於這個版本引入的語法功能太多,而且制定過程當中,還有很多組織和個人不斷提交新功能。標準委員會最終決定,標準在每年的6月份正式發布一次,作為當年的正式版本。接下來的時間,就在這個版本的基礎上做改動,直到下一年的6月份,草案就自然變成了新一年的版本。
2015年6月,ECMAScript 6(ES6)正式通過,成為國際標準,正式名稱是“ECMAScript 2015”(簡稱ES2015)。
2016年6月,小幅修訂的“ECMAScript 2016”(簡稱ES2016或ES7)標準發布,相當於ES6.1版,因為兩者的差異非常小(只新增了數組實例的includes方法和指數運算符),基本上是同一個標準 。
新增功能
聲明命令
1. let命令
ES6新增了let命令,用來聲明變數。它的用法類似於var,但是所聲明的變數,只在let命令所在的代碼塊內有效。下面代碼在代碼塊之中,分別用let和var聲明了兩個變數。然後在代碼塊之外調用這兩個變數,結果let聲明的變數報錯,var聲明的變數返回了正確的值。這表明,let聲明的變數只在它所在的代碼塊有效。
for循環的計數器,就很合適使用let命令,計數器i只在for循環體內有效,在循環體外引用就會報錯:
下面的代碼如果使用var,最後輸出的是10。因為變數i是var命令聲明的,在全局範圍內都有效,每一次循環,變數i的值都會發生改變,而循環內被賦給數組a的函式內部的console.log(i),裡面的i指向的就是全局的i。也就是說,所有數組a的成員裡面的i,指向的都是同一個i,導致運行時輸出的是最後一輪的i的值,也就是10。
如果使用let,聲明的變數僅在塊級作用域內有效,最後輸出的是6。因為變數i是let聲明的,當前的i只在本輪循環有效,所以每一次循環的i其實都是一個新的變數,所以最後輸出的是6。由於JavaScript引擎內部會記住上一輪循環的值,初始化本輪的變數i時,就在上一輪循環的基礎上進行計算:
let不允許在相同作用域內,重複聲明同一個變數,不能在函式內部相同模組範圍重新聲明參數。
另外,for循環還有一個特別之處,就是設定循環變數的那部分是一個單獨的父作用域,而循環體內部是子作用域:
var命令會發生“變數提升”現象,即變數可以在聲明之前使用,值為undefined。為了糾正這種現象,let命令改變了語法行為,它所聲明的變數一定要在聲明後使用,否則報錯。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱TDZ)。
let實際上為JavaScript新增了塊級作用域,在{}被包圍的範圍外,不受內層的let變數影響(但會受var的“變數提升”影響):
2. const命令
const聲明一個唯讀的常量。一旦聲明,常量的值就不能改變,且聲明時必須立即初始化,不能留到以後賦值。const的作用域與let命令相同:只在聲明所在的塊級作用域內有效。
const實際上保證的,並不是變數的值不得改動,而是變數指向的那個記憶體地址不得改動。對於簡單類型的數據(數值、字元串、布爾值),值就保存在變數指向的那個記憶體地址,因此等同於常量。但對於複合類型的數據(主要是對象和數組),變數指向的記憶體地址,保存的只是一個指針,const只能保證這個指針是固定的,至於它指向的數據結構是不是可變的,就完全不能控制了。因此,將一個對象聲明為常量必須非常小心。
如果真的想將對象或對象屬性凍結,應該使用Object.freeze方法 。
3.Class命令
ES6 提供了更接近傳統語言的寫法,引入了Class(類)這個概念(類的數據類型就是函式,類本身就指向構造函式),作為對象的模板。通過class關鍵字,可以定義類。class可以看作只是一個語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已:
構造函式的prototype屬性,在ES6的類上面繼續存在。事實上,類的所有方法都定義在類的prototype屬性上面。但類的內部所有定義的方法,都是不可枚舉的(non-enumerable):
類的屬性名,可以採用表達式:
與函式一樣,類也可以使用表達式的形式定義。下面代碼使用表達式定義了一個類。需要注意的是,這個類的名字是MyClass而不是Me,Me只在 Class 的內部代碼可用,指代當前類:
類相當於實例的原型,所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用,這就稱為“靜態方法”。
如果靜態方法包含this關鍵字,這個this指的是類,而不是實例。靜態方法可以與非靜態方法重名,父類的靜態方法,可以被子類繼承:
4.import命令
import雖然屬於聲明命令,但它是和export命令配合使用的。export命令用於規定模組的對外接口,import命令用於輸入其他模組提供的功能。
一個模組就是一個獨立的檔案。該檔案內部的所有變數,外部無法獲取。如果外部能夠讀取模組內部的某個變數、函式或類,就必須使用export關鍵字輸出。export輸出的變數就是本來的名字,但是可以使用as關鍵字重命名:
export語句輸出的接口,與其對應的值是動態綁定關係,即通過該接口,可以取到模組內部實時的值。這一點與CommonJS規範完全不同,CommonJS模組輸出的是值的快取。export命令可以出現在模組的任何位置,只要處於模組頂層就可以。如果處於塊級作用域內,就會報錯:
使用export命令定義了模組的對外接口以後,其他JS檔案就可以通過import命令載入這個模組,變數名必需與被導入模組(profile.js)對外接口的名稱相同。import命令可以使用as關鍵字,將輸入的變數重命名。除了指定載入某個輸出值,還可以使用整體載入,即用*指定一個對象,所有輸出值都載入在這個對象上面。
import命令輸入的變數都是唯讀的,因為它的本質是輸入接口。也就是說,不允許在載入模組的腳本裡面,改寫接口。但是,如果是一個對象,改寫對象的屬性是允許的。並且由於import是靜態執行,所以不能使用表達式和變數,這些只有在運行時才能得到結果的語法結構。
注意,import命令具有提升效果,會提升到整個模組的頭部,首先執行。import可以不導入模組中的任何內容,只運行模組中的全局代碼。如果多次執行同一模組的import語句,那么只會執行一次其全局代碼,但變數均會正常引入(相當於合併處理)。
除了用大括弧引入變數,import還可以直接自定義引入默認變數:
解構賦值
ES6允許按照一定模式,從數組和對象中提取值,對變數進行賦值,這被稱為解構(Destructuring)。本質上,這種寫法屬於“模式匹配”,只要等號兩邊的模式相同,左邊的變數就會被賦予對應的值。
解構賦值允許指定默認值,只有當一個數組成員嚴格等於undefined,默認值才會生效:
解構不僅可以用於數組,還可以用於對象。對象的解構與數組有一個重要的不同。數組的元素是按次序排列的,變數的取值由它的位置決定;而對象的屬性沒有次序,變數必須與屬性同名,才能取到正確的值。
解構賦值用途示例:
兼容問題
不同瀏覽器的不同版本對ES6的支持度不同,而Babel是一個廣泛使用的ES6轉碼器,可以將ES6代碼轉為ES5代碼,從而在現有環境執行,例如:
上面的原始代碼用了箭頭函式,Babel 將其轉為普通函式,就能在不支持箭頭函式的JavaScript環境執行了 。
另外,使用Google公司的Traceur轉碼器,也可以將ES6代碼轉為ES5代碼,需要提前引入其js庫:
第一個是載入Traceur的庫檔案,第二個和第三個是將這個庫檔案用於瀏覽器環境。然後就可以載入ES6的用戶腳本了,如:
注意,自定義的script標籤的type屬性的值是module,而不是text/javascript。這是Traceur編譯器識別ES6代碼的標誌,編譯器會自動將所有type=module的代碼編譯為ES5,然後再交給瀏覽器執行。
如果想對Traceur的行為有精確控制,可以採用下面參數配置的寫法:
上面代碼中,首先生成Traceur的全局對象window.System,然後System.import方法可以用來載入ES6。載入的時候,需要傳入一個配置對象metadata,該對象的traceurOptions屬性可以配置支持ES6功能。如果設為experimental:true,就表示除了ES6以外,還支持一些實驗性的新功能 。