JavaScript ECMAScript 6 介紹
ECMAScript 6 是JavaScript的下一代標準,正處在快速開發之中,大部分已經完成了,預計將在2014年正式發佈。Mozilla將在這個標準的基礎上,推出JavaScript 2.0。
ECMAScript 6的目標,是使得JavaScript可以用來編寫複雜的套用程式、函式庫和程式碼的自動產生器(code generator)。
最新的瀏覽器已經部分支持ECMAScript 6 的語法,可以通過《ECMAScript 6 瀏覽器兼容表》查看瀏覽器支持情況。
下面對ECMAScript 6新增的語法特性逐一介紹。由於ECMAScript 6的正式標準還未出台,所以以下內容隨時可能發生變化,不一定是最後的版本。
使用ECMAScript 6的方法
目前,V8引擎已經部署了ECMAScript 6的部分特性。使用node.js 0.11版,就可以體驗這些特性。
node.js 0.11版的一種比較方便的使用方法,是使用版本管理工具nvm。下載nvm以後,進入項目目錄,執行下面的命令,註冊nvm。
source nvm.sh
然後,指定node執行版本。
nvm use 0.11
最後,用--harmony參數進入node執行環境,就可以在命令行下體驗ECMAScript 6了。
node --harmony
另外,可以使用Google的Traceur(在線轉換工具),將ES6程式碼編譯為ES5。
# 安裝
npm install -g traceur
# 執行ES6文件
traceur /path/to/es6
# 將ES6文件轉為ES5文件
traceur --script /path/to/es6 --out /path/to/es5
資料類型
let命令
(1)概述
ECMAScript 6新增了let命令,用來聲明變數。它的用法類似於var,但是所聲明的變數,只在let命令所在的程式碼塊內有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b //1
上面程式碼在程式碼塊之中,分別用let和var聲明了兩個變數。然後在程式碼塊之外呼叫這兩個變數,結果let聲明的變數報錯,var聲明的變數返回了正確的值。這表明,let聲明的變數只在它所在的程式碼塊有效。
下面的程式碼如果使用var,最後輸出的是9。
var a = [];
for (var i = 0; i < 10; i++) {
var c = i;
a[i] = function () {
console.log(c);
};
}
a[6](); // 9
如果使用let,聲明的變數僅在塊級作用域內有效,最後輸出的是6。
var a = [];
for (var i = 0; i < 10; i++) {
let c = i;
a[i] = function () {
console.log(c);
};
}
a[6](); // 6
注意,let不允許在相同作用域內,重複聲明同一個變數。
// 報錯
{
let a = 10;
var a = 1;
}
// 報錯
{
let a = 10;
let a = 1;
}
(2)塊級作用域
let實際上為JavaScript新增了塊級作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函式有兩個程式碼塊,都聲明了變數n,執行後輸出5。這表示外層程式碼塊不受內層程式碼塊的影響。如果使用var定義變數n,最後輸出的值就是10。
塊級作用域的出現,實際上使得獲得廣泛套用的立即執行函式(IIFE)不再必要了。
// IIFE寫法
(function () {
var tmp = ...;
...
}());
// 塊級作用域寫法
{
let tmp = ...;
...
}
(3)不存在變數提升
需要注意的是,let聲明的變數不存在「變數提升」現象。
console.log(x);
let x = 10;
上面程式碼執行後會報錯,表示x沒有定義。如果用var聲明x,就不會報錯,輸出結果為undefined。
const命令
const也用來聲明變數,但是聲明的是常數。一旦聲明,常數的值就不能改變。
const PI = 3.1415;
PI
// 3.1415
PI = 3;
PI
// 3.1415
const PI = 3.1;
PI
// 3.1415
上面程式碼表明改變常數的值是不起作用的。需要注意的是,對常數重新賦值不會報錯,只會默默地失敗。
const的作用域與var命令相同:如果在全域環境聲明,常數就在全域環境有效;如果在函式內聲明,常數就在函式體內有效。
Set資料結構
ES6提供了新的資料結構Set。它類似於陣列,但是成員的值都是唯一的,沒有重複的值。
Set本身是一個建構式,用來產生Set資料結構。
var s = new Set();
[2,3,5,4,5,2,2].map(x => s.add(x))
for (i of s) {console.log(i)}
// 2 3 4 5
上面程式碼表示,set資料結構不會增加重複的值。
set資料結構有以下屬性和方法:
size:返回成員總數。
add(value):增加某個值。
delete(value):刪除某個值。
has(value):返回一個布爾值,表示該值是否為set的成員。
clear():清除所有成員。
s.add("1").add("2").add("2");
// 注意「2」被加入了兩次
s.size // 2
s.has("1") // true
s.has("2") // true
s.has("3") // false
s.delete("2");
s.has("2") // false
Map資料結構
ES6還提供了map資料結構。它類似於物件,就是一個鍵值對的集合,但是「鍵」的範圍不限於字串,甚至物件也可以當作鍵。
var m = new Map();
o = {p: "Hello World"};
m.set(o, "content")
console.log(m.get(o))
// "content"
上面程式碼將一個物件當作m的一個鍵。
Map資料結構有以下屬性和方法。
size:返回成員總數。
set(key, value):設置一個鍵值對。
get(key):讀取一個鍵。
has(key):返回一個布爾值,表示某個鍵是否在Map資料結構中。
delete(key):刪除某個鍵。
clear():清除所有成員。
var m = new Map();
m.set("edition", 6) // 鍵是字串
m.set(262, "standard") // 鍵是數值
m.set(undefined, "nah") // 鍵是undefined
var hello = function() {console.log("hello");}
m.set(hello, "Hello ES6!") // 鍵是函式
m.has("edition") // true
m.has("years") // false
m.has(262) // true
m.has(undefined) // true
m.has(hello) // true
m.delete(undefined)
m.has(undefined) // false
m.get(hello) // Hello ES6!
m.get("edition") // 6
rest(...)運算子
(1)基本用法
ES6引入rest運算子(...),用於獲取函式的多餘參數,這樣就不需要使用arguments.length了。rest運算子後面是一個陣列變數,該變數將多餘的參數放入陣列中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
上面程式碼的add函式是一個求和函式,利用rest運算子,可以向該函式傳入任意數目的參數。
下面是一個利用rest運算子改寫陣列push方法的例子。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, "a1", "a2", "a3", "a4");
(2)將陣列轉為參數序列
rest運算子不僅可以用於函式定義,還可以用於函式呼叫。
function f(s1, s2, s3, s4, s5) {
console.log(s1 + s2 + s3 + s4 +s5);
}
var a = ["a2", "a3", "a4", "a5"];
f("a1", ...a)
// a1a2a3a4a5
從上面的例子可以看出,rest運算子的另一個重要作用是,可以將陣列轉變成正常的參數序列。利用這一點,可以簡化求出一個陣列最大元素的寫法。
// ES5
Math.max.apply(null, [14, 3, 77])
// ES6
Math.max(...[14, 3, 77])
// 等同於
Math.max(14, 3, 77);
上面程式碼表示,由於JavaScript不提供求陣列最大元素的函式,所以只能套用Math.max函式,將陣列轉為一個參數序列,然後求最大值。有了rest運算子以後,就可以直接用Math.max了。
遍歷器(Iterator)
遍歷器(Iterator)是一種協議,任何物件都可以部署遍歷器協議,從而使得for...of循環可以遍歷這個物件。
遍歷器協議規定,任意物件只要部署了next方法,就可以作為遍歷器,但是next方法必須返回一個包含value和done兩個屬性的物件。其中,value屬性當前遍歷位置的值,done屬性是一個布爾值,表示遍歷是否結束。
function makeIterator(array){
var nextIndex = 0;
return {
next: function(){
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
}
}
var it = makeIterator(['a', 'b']);
it.next().value // 'a'
it.next().value // 'b'
it.next().done // true
下面是一個無限執行的遍歷器的例子。
function idMaker(){
var index = 0;
return {
next: function(){
return {value: index++, done: false};
}
}
}
var it = idMaker();
it.next().value // '0'
it.next().value // '1'
it.next().value // '2'
// ...
generator 函式
上一部分的遍歷器,用來依次取出集合中的每一個成員,但是某些情況下,我們需要的是一個內部狀態的遍歷器。也就是說,每呼叫一次遍歷器,物件的內部狀態發生一次改變(可以理解成發生某些事件)。ECMAScript 6 引入了generator函式,作用就是返回一個內部狀態的遍歷器,主要特徵是函式內部使用了yield語句。
當呼叫generator函式的時候,該函式並不執行,而是返回一個遍歷器(可以理解成暫停執行)。以後,每次呼叫這個遍歷器的next方法,就從函式體的頭部或者上一次停下來的地方開始執行(可以理解成恢復執行),直到遇到下一個yield語句為止,並返回該yield語句的值。
ECMAScript 6草案定義的generator函式,需要在function關鍵字後面,加一個星號。然後,函式內部使用yield語句,定義遍歷器的每個成員。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
}
yield有點類似於return語句,都能返回一個值。區別在於每次遇到yield,函式返回緊跟在yield後面的那個表達式的值,然後暫停執行,下一次從該位置繼續向後執行,而return語句不具備位置記憶的功能。
上面程式碼定義了一個generator函式helloWorldGenerator,它的遍歷器有兩個成員「hello」和「world」。呼叫這個函式,就會得到遍歷器。
var hw = helloWorldGenerator();
執行遍歷器的next方法,則會依次遍歷每個成員。
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: undefined, done: true }
hw.next()
// Error: Generator has already finished
// at GeneratorFunctionPrototype.next (native)
// at repl:1:3
// at REPLServer.defaultEval (repl.js:129:27)
// ...
上面程式碼一共呼叫了四次next方法。
第一次呼叫:函式開始執行,直到遇到第一句yield語句為止。next方法返回一個物件,它的value屬性就是當前yield語句的值hello,done屬性的值false,表示遍歷還沒有結束。
第二次呼叫:函式從上次yield語句停下的地方,一直執行到下一個yield語句。next方法返回一個物件,它的value屬性就是當前yield語句的值world,done屬性的值false,表示遍歷還沒有結束。
第三次呼叫:函式從上次yield語句停下的地方,一直執行到函式結束。next方法返回一個物件,它的value屬性就是函式最後的返回值,由於上例的函式沒有return語句(即沒有返回值),所以value屬性的值為undefined,done屬性的值true,表示遍歷已經結束。
第四次呼叫:由於此時函式已經執行完畢,next方法直接拋出一個錯誤。
遍歷器的本質,其實是使用yield語句暫停執行它後面的操作,當呼叫next方法時,再繼續往下執行,直到遇到下一個yield語句,並返回該語句的值,如果直到執行結束。
如果next方法帶一個參數,該參數就會被當作上一個yield語句的返回值。
function* f() {
for(var i=0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
上面程式碼先定義了一個可以無限執行的generator函式f,如果next方法沒有參數,正常情況下返回一個遞增的i;如果next方法有參數,則上一次yield語句的返回值將會等於該參數。如果該參數為true,則會重置i的值。
generator函式的這種暫停執行的效果,意味著可以把異步操作寫在yield語句裡面,等到呼叫next方法時再往後執行。這實際上等同於不需要寫回調函式了,因為異步操作的後續操作可以放在yield語句下面,反正要等到呼叫next方法時再執行。所以,generator函式的一個重要實際意義就是用來處理異步操作,改寫回調函式。
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
上面程式碼表示,第一次呼叫loadUI函式時,該函式不會執行,僅返回一個遍歷器。下一次對該遍歷器呼叫next方法,則會顯示登錄視窗,並且異步加載資料。再一次使用next方法,則會隱藏登錄視窗。可以看到,這種寫法的好處是所有登錄視窗的邏輯,都被封裝在一個函式,按部就班非常清晰。
下面是一個利用generator函式,實現斐波那契數列的例子。
function* fibonacci() {
var previous = 0, current = 1;
while (true) {
var temp = previous;
previous = current;
current = temp + current;
yield current;
}
}
for (var i of fibonacci()) {
console.log(i);
}
// 1, 2, 3, 5, 8, 13, ...,
下面是利用for...of語句,對斐波那契數列的另一種實現。
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
從上面程式碼可見,使用for...of語句時不需要使用next方法。
這裡需要注意的是,yield語句執行的時候是同步執行,而不是異步執行(否則就失去了取代回調函式的設計目的了)。實際操作中,一般讓yield語句返回Promises物件。
var Q = require('q');
function delay(milliseconds) {
var deferred = Q.defer();
setTimeout(deferred.resolve, milliseconds);
return deferred.promise;
}
function *f(){
yield delay(100);
};
上面程式碼yield語句返回的就是一個Promises物件。
如果有一系列任務需要全部完成後,才能進行下一步操作,yield語句後面可以跟一個陣列。下面就是一個例子。
function *f() {
var urls = [
'http://example.com/',
'http://twitter.com/',
'http://bbc.co.uk/news/'
];
var arrayOfPromises = urls.map(someOperation);
var arrayOfResponses = yield arrayOfPromises;
this.body = "Results";
for (var i = 0; i < urls.length; i++) {
this.body += '\n' + urls[i] + ' response length is '
+ arrayOfResponses[i].body.length;
}
};
原生物件的擴展
ES6對JavaScript的原生物件,進行了擴展,提供了一系列新的屬性和方法。
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false
Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
"abcde".contains("cd") // true
"abc".repeat(3) // "abcabcabc"
Array.from(document.querySelectorAll('*')) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1,2,3].findIndex(x => x == 2) // 1
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"
Object.assign(Point, { origin: new Point(0,0) })
語法糖
ECMAScript 6提供了很多JavaScript語法的便捷寫法。
二進制和八進製表示法
ES6提供了二進制和八進制數值的新的寫法,分別用前綴0b和0o表示。
0b111110111 === 503 // true
0o767 === 503 // true
增強的物件寫法
ES6允許直接寫入變數和函式,作為物件的屬性和方法。這樣的書寫更加簡潔。
var Person = {
name: '張三',
//等同於birth: birth
birth,
// 等同於hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
箭頭函式(arrow)
(1)定義
ES6允許使用「箭頭」(=>)定義函式。
var f = v => v;
上面的箭頭函式等同於:
var f = function(v) {
return v;
};
如果箭頭函式不需要參數或需要多個參數,就使用一個圓括號代表參數部分。
var f = () => 5;
// 等同於
var f = function (){ return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
return num1 + num2;
};
如果箭頭函式的程式碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return語句返回。
var sum = (num1, num2) => { return num1 + num2; }
由於大括號被解釋為程式碼塊,所以如果箭頭函式直接返回一個物件,必須在物件外面加上括號。
var getTempItem = id => ({ id: id, name: "Temp" });
(2)範例:回調函式的簡化
箭頭函式的一個用處是簡化回調函式。
// 正常函式寫法
[1,2,3].map(function (x) {
return x * x;
});
// 箭頭函式寫法
[1,2,3].map(x => x * x);
另一個例子是
// 正常函式寫法
var result = values.sort(function(a, b) {
return a - b;
});
// 箭頭函式寫法
var result = values.sort((a, b) => a - b);
(3)注意點
箭頭函式有幾個使用注意點。
函式體內的this物件,綁定定義時所在的物件,而不是使用時所在的物件。
不可以當作建構式,也就是說,不可以使用new命令,否則會拋出一個錯誤。
不可以使用arguments物件,該物件在函式體內不存在。
關於this物件,下面的程式碼將它綁定定義時的物件。
var handler = {
id: "123456",
init: function() {
document.addEventListener("click",
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log("Handling " + type + " for " + this.id);
}
};
上面程式碼的init和doSomething方法中,都使用了箭頭函式,它們中的this都綁定handler物件。否則,doSomething方法內部的this物件就指向全域物件,執行時會報錯。
函式參數的預設值
ECMAScript 6 允許為函式的參數設置預設值。
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
var p = new Point();
// p = { x:0, y:0 }
模板字串
模板字串(template string)是增強版的字串,即可以當作普通字串使用,也可以在字串中嵌入變數。它用反引號(`)標識。
// 普通字串
`In JavaScript '\n' is a line-feed.`
// 多行字串
`In JavaScript this is
not legal.`
// 字串中嵌入變數
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
var x = 1;
var y = 2;
console.log(`${ x } + ${ y } = ${ x + y}`)
// "1 + 2 = 3"
for...of循環
JavaScript原有的for...in循環,只能獲得物件的鍵名,不能直接獲取鍵值。ES6提供for...of循環,允許遍歷獲得鍵值。
var arr = ["a", "b", "c", "d"];
for (a in arr) {
console.log(a);
}
// 0
// 1
// 2
// 3
for (a of arr) {
console.log(a);
}
// a
// b
// c
// d
上面程式碼表明,for...in循環讀取鍵名,for...of循環讀取鍵值。
for...of循環還可以遍歷物件。
var es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (e in es6) {
console.log(e);
}
// edition
// committee
// standard
var engines = Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
// Gecko
// Trident
// Webkit
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
上面程式碼一共包含三個例子,第一個是for...in循環的例子,後兩個是for...of循環的例子。最後一個例子是同時遍歷物件的鍵名和鍵值。
陣列推導
(1)基本用法
ES6提供簡潔寫法,允許直接通過現有陣列產生新陣列,這被稱為陣列推導(array comprehension)。
var a1 = [1, 2, 3, 4];
var a2 = [i * 2 for (i of a1)];
a2 // [2, 4, 6, 8]
上面程式碼表示,通過for...of結構,陣列a2直接在a1的基礎上產生。
陣列推導可以替代map和filter方法。
[for (i of [1, 2, 3]) i * i];
// 等價於
[1, 2, 3].map(function (i) { return i * i });
[i for (i of [1,4,2,3,-8]) if (i < 3)];
// 等價於
[1,4,2,3,-8].filter(function(i) { return i < 3 });
上面程式碼說明,模擬map功能只要單純的for...of循環就行了,模擬filter功能除了for...of循環,還必須加上if語句。
(2)多重推導
新引入的for...of結構,可以直接跟在表達式的前面或後面,甚至可以在一個陣列推導中,使用多個for...of結構。
var a1 = ["x1", "y1"];
var a2 = ["x2", "y2"];
var a3 = ["x3", "y3"];
[(console.log(s + w + r)) for (s of a1) for (w of a2) for (r of a3)];
// x1x2x3
// x1x2y3
// x1y2x3
// x1y2y3
// y1x2x3
// y1x2y3
// y1y2x3
// y1y2y3
上面程式碼在一個陣列推導之中,使用了三個for...of結構。
需要注意的是,陣列推導的方括號構成了一個單獨的作用域,在這個方括號中聲明的變數類似於使用let語句聲明的變數。
(3)字串推導
由於字串可以視為陣列,因此字串也可以直接用於陣列推導。
[c for (c of 'abcde') if (/[aeiou]/.test(c))].join('') // 'ae'
[c+'0' for (c of 'abcde')].join('') // 'a0b0c0d0e0'
上面程式碼使用了陣列推導,對字串進行處理。
上一部分的陣列推導有一個缺點,就是新陣列會立即在記憶體中產生。這時,如果原陣列是一個很大的陣列,將會非常耗費記憶體。
多變數賦值
ES6允許簡潔地對多變數賦值。正常情況下,將陣列元素賦值給多個變數,只能一次次分開賦值。
var a = 1;
var b = 2;
var c = 3;
ES6允許寫成下面這樣。
var [a, b, c] = [1, 2, 3];
本質上,這種寫法屬於模式匹配,只要等號兩邊的模式相同,左邊的變數就會被賦予對應的值。下面是一些嵌套陣列的例子。
var [foo, [[bar], baz]] = [1, [[2], 3]]
var [,,third] = ["foo", "bar", "baz"]
var [head, ...tail] = [1, 2, 3, 4]
它還可以接受預設值。
var [missing = true] = [];
console.log(missing)
// true
var { x = 3 } = {};
console.log(x)
// 3
它不僅可以用於陣列,還可以用於物件。
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
foo // "lorem"
bar // "ipsum"
var o = {
p1: [
"Hello",
{ p2: "World" }
]
};
var { a: [p1, { p2 }] } = o;
console.log(p1)
// "Hello"
console.log(p2)
// "World"
這種寫法的用途很多。
(1)交換變數的值。
[x, y] = [y, x];
(2)從函式返回多個值。
function example() {
return [1, 2, 3];
}
var [a, b, c] = example();
(3)函式參數的定義。
function f({p1, p2, p3}) {
// ...
}
(4)函式參數的預設值。
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
資料結構
class結構
(1)基本用法
ES6提供了「類」(class)。此前,一般用建構式模擬「類」。
// ES5
var Language = function(config) {
this.name = config.name;
this.founder = config.founder;
this.year = config.year;
};
Language.prototype.summary = function() {
return this.name+"由"+this.founder+"在"+this.year+"創造";
};
// ES6
class Language {
constructor(name, founder, year) {
this.name = name;
this.founder = founder;
this.year = year;
}
summary() {
return this.name+"由"+this.founder+"在"+this.year+"創造";
}
}
在上面程式碼中,ES6用constructor方法,代替ES5的建構式。
(2)繼承
ES6的class結構還允許使用extends關鍵字,表示繼承。
class MetaLanguage extends Language {
constructor(x, y, z, version) {
super(x, y, z);
this.version = version;
}
summary() {
//...
super.summary();
}
}
上面程式碼的super方法,表示呼叫父類的建構式。
module定義
(1)基本用法
ES6允許定義模組。也就是說,允許一個JavaScriptscripts文件呼叫另一個scripts文件。
假設有一個circle.js,它是一個單獨模組。
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
然後,main.js引用這個模組。
// main.js
import { area, circumference } from 'circle';
console.log("圓面積:" + area(4));
console.log("圓周長:" + circumference(14));
另一種寫法是整體加載circle.js。
// main.js
module circle from 'circle';
console.log("圓面積:" + circle.area(4));
console.log("圓周長:" + circle.circumference(14));
(2)模組的繼承
一個模組也可以繼承另一個模組。
// circleplus.js
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
加載上面的模組。
// main.js
module math from "circleplus";
import exp from "circleplus";
console.log(exp(math.pi);
(3)模組的預設方法
還可以為模組定義預設方法。
// circleplus.js
export default function(x) {
return Math.exp(x);
}
ECMAScript 7
2013年3月,ECMAScript 6的草案封閉,不再接受新功能了。新的功能將被加入ECMAScript 7。根據JavaScript創造者Brendan Eich的設想,ECMAScript 7將使得JavaScript更適於開發複雜的套用程式和函式庫。
ECMAScript 7可能包括的功能有:
Object.observe:物件與網頁元素的雙向綁定,只要其中之一發生變化,就會自動反映在另一者上。
Multi-Threading:多線程支持。目前,Intel和Mozilla有一個共同的研究項目RiverTrail,致力於讓JavaScript多線程執行。預計這個項目的研究成果會被納入ECMAScript標準。
Traits:它將是「類」功能(class)的一個替代。通過它,不同的物件可以分享同樣的特性。
其他可能包括的功能還有:更精確的數值計算、改善的記憶體回收、增強的跨站點安全、類型化的更貼近硬件的(Typed, Low-level)操作、國際化支持(Internationalization Support)、更多的資料結構等等。
ECMAScript 6的目標,是使得JavaScript可以用來編寫複雜的套用程式、函式庫和程式碼的自動產生器(code generator)。
最新的瀏覽器已經部分支持ECMAScript 6 的語法,可以通過《ECMAScript 6 瀏覽器兼容表》查看瀏覽器支持情況。
下面對ECMAScript 6新增的語法特性逐一介紹。由於ECMAScript 6的正式標準還未出台,所以以下內容隨時可能發生變化,不一定是最後的版本。
使用ECMAScript 6的方法
目前,V8引擎已經部署了ECMAScript 6的部分特性。使用node.js 0.11版,就可以體驗這些特性。
node.js 0.11版的一種比較方便的使用方法,是使用版本管理工具nvm。下載nvm以後,進入項目目錄,執行下面的命令,註冊nvm。
source nvm.sh
然後,指定node執行版本。
nvm use 0.11
最後,用--harmony參數進入node執行環境,就可以在命令行下體驗ECMAScript 6了。
node --harmony
另外,可以使用Google的Traceur(在線轉換工具),將ES6程式碼編譯為ES5。
# 安裝
npm install -g traceur
# 執行ES6文件
traceur /path/to/es6
# 將ES6文件轉為ES5文件
traceur --script /path/to/es6 --out /path/to/es5
資料類型
let命令
(1)概述
ECMAScript 6新增了let命令,用來聲明變數。它的用法類似於var,但是所聲明的變數,只在let命令所在的程式碼塊內有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b //1
上面程式碼在程式碼塊之中,分別用let和var聲明了兩個變數。然後在程式碼塊之外呼叫這兩個變數,結果let聲明的變數報錯,var聲明的變數返回了正確的值。這表明,let聲明的變數只在它所在的程式碼塊有效。
下面的程式碼如果使用var,最後輸出的是9。
var a = [];
for (var i = 0; i < 10; i++) {
var c = i;
a[i] = function () {
console.log(c);
};
}
a[6](); // 9
如果使用let,聲明的變數僅在塊級作用域內有效,最後輸出的是6。
var a = [];
for (var i = 0; i < 10; i++) {
let c = i;
a[i] = function () {
console.log(c);
};
}
a[6](); // 6
注意,let不允許在相同作用域內,重複聲明同一個變數。
// 報錯
{
let a = 10;
var a = 1;
}
// 報錯
{
let a = 10;
let a = 1;
}
(2)塊級作用域
let實際上為JavaScript新增了塊級作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函式有兩個程式碼塊,都聲明了變數n,執行後輸出5。這表示外層程式碼塊不受內層程式碼塊的影響。如果使用var定義變數n,最後輸出的值就是10。
塊級作用域的出現,實際上使得獲得廣泛套用的立即執行函式(IIFE)不再必要了。
// IIFE寫法
(function () {
var tmp = ...;
...
}());
// 塊級作用域寫法
{
let tmp = ...;
...
}
(3)不存在變數提升
需要注意的是,let聲明的變數不存在「變數提升」現象。
console.log(x);
let x = 10;
上面程式碼執行後會報錯,表示x沒有定義。如果用var聲明x,就不會報錯,輸出結果為undefined。
const命令
const也用來聲明變數,但是聲明的是常數。一旦聲明,常數的值就不能改變。
const PI = 3.1415;
PI
// 3.1415
PI = 3;
PI
// 3.1415
const PI = 3.1;
PI
// 3.1415
上面程式碼表明改變常數的值是不起作用的。需要注意的是,對常數重新賦值不會報錯,只會默默地失敗。
const的作用域與var命令相同:如果在全域環境聲明,常數就在全域環境有效;如果在函式內聲明,常數就在函式體內有效。
Set資料結構
ES6提供了新的資料結構Set。它類似於陣列,但是成員的值都是唯一的,沒有重複的值。
Set本身是一個建構式,用來產生Set資料結構。
var s = new Set();
[2,3,5,4,5,2,2].map(x => s.add(x))
for (i of s) {console.log(i)}
// 2 3 4 5
上面程式碼表示,set資料結構不會增加重複的值。
set資料結構有以下屬性和方法:
size:返回成員總數。
add(value):增加某個值。
delete(value):刪除某個值。
has(value):返回一個布爾值,表示該值是否為set的成員。
clear():清除所有成員。
s.add("1").add("2").add("2");
// 注意「2」被加入了兩次
s.size // 2
s.has("1") // true
s.has("2") // true
s.has("3") // false
s.delete("2");
s.has("2") // false
Map資料結構
ES6還提供了map資料結構。它類似於物件,就是一個鍵值對的集合,但是「鍵」的範圍不限於字串,甚至物件也可以當作鍵。
var m = new Map();
o = {p: "Hello World"};
m.set(o, "content")
console.log(m.get(o))
// "content"
上面程式碼將一個物件當作m的一個鍵。
Map資料結構有以下屬性和方法。
size:返回成員總數。
set(key, value):設置一個鍵值對。
get(key):讀取一個鍵。
has(key):返回一個布爾值,表示某個鍵是否在Map資料結構中。
delete(key):刪除某個鍵。
clear():清除所有成員。
var m = new Map();
m.set("edition", 6) // 鍵是字串
m.set(262, "standard") // 鍵是數值
m.set(undefined, "nah") // 鍵是undefined
var hello = function() {console.log("hello");}
m.set(hello, "Hello ES6!") // 鍵是函式
m.has("edition") // true
m.has("years") // false
m.has(262) // true
m.has(undefined) // true
m.has(hello) // true
m.delete(undefined)
m.has(undefined) // false
m.get(hello) // Hello ES6!
m.get("edition") // 6
rest(...)運算子
(1)基本用法
ES6引入rest運算子(...),用於獲取函式的多餘參數,這樣就不需要使用arguments.length了。rest運算子後面是一個陣列變數,該變數將多餘的參數放入陣列中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
上面程式碼的add函式是一個求和函式,利用rest運算子,可以向該函式傳入任意數目的參數。
下面是一個利用rest運算子改寫陣列push方法的例子。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, "a1", "a2", "a3", "a4");
(2)將陣列轉為參數序列
rest運算子不僅可以用於函式定義,還可以用於函式呼叫。
function f(s1, s2, s3, s4, s5) {
console.log(s1 + s2 + s3 + s4 +s5);
}
var a = ["a2", "a3", "a4", "a5"];
f("a1", ...a)
// a1a2a3a4a5
從上面的例子可以看出,rest運算子的另一個重要作用是,可以將陣列轉變成正常的參數序列。利用這一點,可以簡化求出一個陣列最大元素的寫法。
// ES5
Math.max.apply(null, [14, 3, 77])
// ES6
Math.max(...[14, 3, 77])
// 等同於
Math.max(14, 3, 77);
上面程式碼表示,由於JavaScript不提供求陣列最大元素的函式,所以只能套用Math.max函式,將陣列轉為一個參數序列,然後求最大值。有了rest運算子以後,就可以直接用Math.max了。
遍歷器(Iterator)
遍歷器(Iterator)是一種協議,任何物件都可以部署遍歷器協議,從而使得for...of循環可以遍歷這個物件。
遍歷器協議規定,任意物件只要部署了next方法,就可以作為遍歷器,但是next方法必須返回一個包含value和done兩個屬性的物件。其中,value屬性當前遍歷位置的值,done屬性是一個布爾值,表示遍歷是否結束。
function makeIterator(array){
var nextIndex = 0;
return {
next: function(){
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
}
}
var it = makeIterator(['a', 'b']);
it.next().value // 'a'
it.next().value // 'b'
it.next().done // true
下面是一個無限執行的遍歷器的例子。
function idMaker(){
var index = 0;
return {
next: function(){
return {value: index++, done: false};
}
}
}
var it = idMaker();
it.next().value // '0'
it.next().value // '1'
it.next().value // '2'
// ...
generator 函式
上一部分的遍歷器,用來依次取出集合中的每一個成員,但是某些情況下,我們需要的是一個內部狀態的遍歷器。也就是說,每呼叫一次遍歷器,物件的內部狀態發生一次改變(可以理解成發生某些事件)。ECMAScript 6 引入了generator函式,作用就是返回一個內部狀態的遍歷器,主要特徵是函式內部使用了yield語句。
當呼叫generator函式的時候,該函式並不執行,而是返回一個遍歷器(可以理解成暫停執行)。以後,每次呼叫這個遍歷器的next方法,就從函式體的頭部或者上一次停下來的地方開始執行(可以理解成恢復執行),直到遇到下一個yield語句為止,並返回該yield語句的值。
ECMAScript 6草案定義的generator函式,需要在function關鍵字後面,加一個星號。然後,函式內部使用yield語句,定義遍歷器的每個成員。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
}
yield有點類似於return語句,都能返回一個值。區別在於每次遇到yield,函式返回緊跟在yield後面的那個表達式的值,然後暫停執行,下一次從該位置繼續向後執行,而return語句不具備位置記憶的功能。
上面程式碼定義了一個generator函式helloWorldGenerator,它的遍歷器有兩個成員「hello」和「world」。呼叫這個函式,就會得到遍歷器。
var hw = helloWorldGenerator();
執行遍歷器的next方法,則會依次遍歷每個成員。
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: undefined, done: true }
hw.next()
// Error: Generator has already finished
// at GeneratorFunctionPrototype.next (native)
// at repl:1:3
// at REPLServer.defaultEval (repl.js:129:27)
// ...
上面程式碼一共呼叫了四次next方法。
第一次呼叫:函式開始執行,直到遇到第一句yield語句為止。next方法返回一個物件,它的value屬性就是當前yield語句的值hello,done屬性的值false,表示遍歷還沒有結束。
第二次呼叫:函式從上次yield語句停下的地方,一直執行到下一個yield語句。next方法返回一個物件,它的value屬性就是當前yield語句的值world,done屬性的值false,表示遍歷還沒有結束。
第三次呼叫:函式從上次yield語句停下的地方,一直執行到函式結束。next方法返回一個物件,它的value屬性就是函式最後的返回值,由於上例的函式沒有return語句(即沒有返回值),所以value屬性的值為undefined,done屬性的值true,表示遍歷已經結束。
第四次呼叫:由於此時函式已經執行完畢,next方法直接拋出一個錯誤。
遍歷器的本質,其實是使用yield語句暫停執行它後面的操作,當呼叫next方法時,再繼續往下執行,直到遇到下一個yield語句,並返回該語句的值,如果直到執行結束。
如果next方法帶一個參數,該參數就會被當作上一個yield語句的返回值。
function* f() {
for(var i=0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
上面程式碼先定義了一個可以無限執行的generator函式f,如果next方法沒有參數,正常情況下返回一個遞增的i;如果next方法有參數,則上一次yield語句的返回值將會等於該參數。如果該參數為true,則會重置i的值。
generator函式的這種暫停執行的效果,意味著可以把異步操作寫在yield語句裡面,等到呼叫next方法時再往後執行。這實際上等同於不需要寫回調函式了,因為異步操作的後續操作可以放在yield語句下面,反正要等到呼叫next方法時再執行。所以,generator函式的一個重要實際意義就是用來處理異步操作,改寫回調函式。
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
上面程式碼表示,第一次呼叫loadUI函式時,該函式不會執行,僅返回一個遍歷器。下一次對該遍歷器呼叫next方法,則會顯示登錄視窗,並且異步加載資料。再一次使用next方法,則會隱藏登錄視窗。可以看到,這種寫法的好處是所有登錄視窗的邏輯,都被封裝在一個函式,按部就班非常清晰。
下面是一個利用generator函式,實現斐波那契數列的例子。
function* fibonacci() {
var previous = 0, current = 1;
while (true) {
var temp = previous;
previous = current;
current = temp + current;
yield current;
}
}
for (var i of fibonacci()) {
console.log(i);
}
// 1, 2, 3, 5, 8, 13, ...,
下面是利用for...of語句,對斐波那契數列的另一種實現。
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
從上面程式碼可見,使用for...of語句時不需要使用next方法。
這裡需要注意的是,yield語句執行的時候是同步執行,而不是異步執行(否則就失去了取代回調函式的設計目的了)。實際操作中,一般讓yield語句返回Promises物件。
var Q = require('q');
function delay(milliseconds) {
var deferred = Q.defer();
setTimeout(deferred.resolve, milliseconds);
return deferred.promise;
}
function *f(){
yield delay(100);
};
上面程式碼yield語句返回的就是一個Promises物件。
如果有一系列任務需要全部完成後,才能進行下一步操作,yield語句後面可以跟一個陣列。下面就是一個例子。
function *f() {
var urls = [
'http://example.com/',
'http://twitter.com/',
'http://bbc.co.uk/news/'
];
var arrayOfPromises = urls.map(someOperation);
var arrayOfResponses = yield arrayOfPromises;
this.body = "Results";
for (var i = 0; i < urls.length; i++) {
this.body += '\n' + urls[i] + ' response length is '
+ arrayOfResponses[i].body.length;
}
};
原生物件的擴展
ES6對JavaScript的原生物件,進行了擴展,提供了一系列新的屬性和方法。
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false
Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
"abcde".contains("cd") // true
"abc".repeat(3) // "abcabcabc"
Array.from(document.querySelectorAll('*')) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1,2,3].findIndex(x => x == 2) // 1
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"
Object.assign(Point, { origin: new Point(0,0) })
語法糖
ECMAScript 6提供了很多JavaScript語法的便捷寫法。
二進制和八進製表示法
ES6提供了二進制和八進制數值的新的寫法,分別用前綴0b和0o表示。
0b111110111 === 503 // true
0o767 === 503 // true
增強的物件寫法
ES6允許直接寫入變數和函式,作為物件的屬性和方法。這樣的書寫更加簡潔。
var Person = {
name: '張三',
//等同於birth: birth
birth,
// 等同於hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
箭頭函式(arrow)
(1)定義
ES6允許使用「箭頭」(=>)定義函式。
var f = v => v;
上面的箭頭函式等同於:
var f = function(v) {
return v;
};
如果箭頭函式不需要參數或需要多個參數,就使用一個圓括號代表參數部分。
var f = () => 5;
// 等同於
var f = function (){ return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
return num1 + num2;
};
如果箭頭函式的程式碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return語句返回。
var sum = (num1, num2) => { return num1 + num2; }
由於大括號被解釋為程式碼塊,所以如果箭頭函式直接返回一個物件,必須在物件外面加上括號。
var getTempItem = id => ({ id: id, name: "Temp" });
(2)範例:回調函式的簡化
箭頭函式的一個用處是簡化回調函式。
// 正常函式寫法
[1,2,3].map(function (x) {
return x * x;
});
// 箭頭函式寫法
[1,2,3].map(x => x * x);
另一個例子是
// 正常函式寫法
var result = values.sort(function(a, b) {
return a - b;
});
// 箭頭函式寫法
var result = values.sort((a, b) => a - b);
(3)注意點
箭頭函式有幾個使用注意點。
函式體內的this物件,綁定定義時所在的物件,而不是使用時所在的物件。
不可以當作建構式,也就是說,不可以使用new命令,否則會拋出一個錯誤。
不可以使用arguments物件,該物件在函式體內不存在。
關於this物件,下面的程式碼將它綁定定義時的物件。
var handler = {
id: "123456",
init: function() {
document.addEventListener("click",
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log("Handling " + type + " for " + this.id);
}
};
上面程式碼的init和doSomething方法中,都使用了箭頭函式,它們中的this都綁定handler物件。否則,doSomething方法內部的this物件就指向全域物件,執行時會報錯。
函式參數的預設值
ECMAScript 6 允許為函式的參數設置預設值。
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
var p = new Point();
// p = { x:0, y:0 }
模板字串
模板字串(template string)是增強版的字串,即可以當作普通字串使用,也可以在字串中嵌入變數。它用反引號(`)標識。
// 普通字串
`In JavaScript '\n' is a line-feed.`
// 多行字串
`In JavaScript this is
not legal.`
// 字串中嵌入變數
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
var x = 1;
var y = 2;
console.log(`${ x } + ${ y } = ${ x + y}`)
// "1 + 2 = 3"
for...of循環
JavaScript原有的for...in循環,只能獲得物件的鍵名,不能直接獲取鍵值。ES6提供for...of循環,允許遍歷獲得鍵值。
var arr = ["a", "b", "c", "d"];
for (a in arr) {
console.log(a);
}
// 0
// 1
// 2
// 3
for (a of arr) {
console.log(a);
}
// a
// b
// c
// d
上面程式碼表明,for...in循環讀取鍵名,for...of循環讀取鍵值。
for...of循環還可以遍歷物件。
var es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (e in es6) {
console.log(e);
}
// edition
// committee
// standard
var engines = Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
// Gecko
// Trident
// Webkit
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
上面程式碼一共包含三個例子,第一個是for...in循環的例子,後兩個是for...of循環的例子。最後一個例子是同時遍歷物件的鍵名和鍵值。
陣列推導
(1)基本用法
ES6提供簡潔寫法,允許直接通過現有陣列產生新陣列,這被稱為陣列推導(array comprehension)。
var a1 = [1, 2, 3, 4];
var a2 = [i * 2 for (i of a1)];
a2 // [2, 4, 6, 8]
上面程式碼表示,通過for...of結構,陣列a2直接在a1的基礎上產生。
陣列推導可以替代map和filter方法。
[for (i of [1, 2, 3]) i * i];
// 等價於
[1, 2, 3].map(function (i) { return i * i });
[i for (i of [1,4,2,3,-8]) if (i < 3)];
// 等價於
[1,4,2,3,-8].filter(function(i) { return i < 3 });
上面程式碼說明,模擬map功能只要單純的for...of循環就行了,模擬filter功能除了for...of循環,還必須加上if語句。
(2)多重推導
新引入的for...of結構,可以直接跟在表達式的前面或後面,甚至可以在一個陣列推導中,使用多個for...of結構。
var a1 = ["x1", "y1"];
var a2 = ["x2", "y2"];
var a3 = ["x3", "y3"];
[(console.log(s + w + r)) for (s of a1) for (w of a2) for (r of a3)];
// x1x2x3
// x1x2y3
// x1y2x3
// x1y2y3
// y1x2x3
// y1x2y3
// y1y2x3
// y1y2y3
上面程式碼在一個陣列推導之中,使用了三個for...of結構。
需要注意的是,陣列推導的方括號構成了一個單獨的作用域,在這個方括號中聲明的變數類似於使用let語句聲明的變數。
(3)字串推導
由於字串可以視為陣列,因此字串也可以直接用於陣列推導。
[c for (c of 'abcde') if (/[aeiou]/.test(c))].join('') // 'ae'
[c+'0' for (c of 'abcde')].join('') // 'a0b0c0d0e0'
上面程式碼使用了陣列推導,對字串進行處理。
上一部分的陣列推導有一個缺點,就是新陣列會立即在記憶體中產生。這時,如果原陣列是一個很大的陣列,將會非常耗費記憶體。
多變數賦值
ES6允許簡潔地對多變數賦值。正常情況下,將陣列元素賦值給多個變數,只能一次次分開賦值。
var a = 1;
var b = 2;
var c = 3;
ES6允許寫成下面這樣。
var [a, b, c] = [1, 2, 3];
本質上,這種寫法屬於模式匹配,只要等號兩邊的模式相同,左邊的變數就會被賦予對應的值。下面是一些嵌套陣列的例子。
var [foo, [[bar], baz]] = [1, [[2], 3]]
var [,,third] = ["foo", "bar", "baz"]
var [head, ...tail] = [1, 2, 3, 4]
它還可以接受預設值。
var [missing = true] = [];
console.log(missing)
// true
var { x = 3 } = {};
console.log(x)
// 3
它不僅可以用於陣列,還可以用於物件。
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
foo // "lorem"
bar // "ipsum"
var o = {
p1: [
"Hello",
{ p2: "World" }
]
};
var { a: [p1, { p2 }] } = o;
console.log(p1)
// "Hello"
console.log(p2)
// "World"
這種寫法的用途很多。
(1)交換變數的值。
[x, y] = [y, x];
(2)從函式返回多個值。
function example() {
return [1, 2, 3];
}
var [a, b, c] = example();
(3)函式參數的定義。
function f({p1, p2, p3}) {
// ...
}
(4)函式參數的預設值。
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
資料結構
class結構
(1)基本用法
ES6提供了「類」(class)。此前,一般用建構式模擬「類」。
// ES5
var Language = function(config) {
this.name = config.name;
this.founder = config.founder;
this.year = config.year;
};
Language.prototype.summary = function() {
return this.name+"由"+this.founder+"在"+this.year+"創造";
};
// ES6
class Language {
constructor(name, founder, year) {
this.name = name;
this.founder = founder;
this.year = year;
}
summary() {
return this.name+"由"+this.founder+"在"+this.year+"創造";
}
}
在上面程式碼中,ES6用constructor方法,代替ES5的建構式。
(2)繼承
ES6的class結構還允許使用extends關鍵字,表示繼承。
class MetaLanguage extends Language {
constructor(x, y, z, version) {
super(x, y, z);
this.version = version;
}
summary() {
//...
super.summary();
}
}
上面程式碼的super方法,表示呼叫父類的建構式。
module定義
(1)基本用法
ES6允許定義模組。也就是說,允許一個JavaScriptscripts文件呼叫另一個scripts文件。
假設有一個circle.js,它是一個單獨模組。
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
然後,main.js引用這個模組。
// main.js
import { area, circumference } from 'circle';
console.log("圓面積:" + area(4));
console.log("圓周長:" + circumference(14));
另一種寫法是整體加載circle.js。
// main.js
module circle from 'circle';
console.log("圓面積:" + circle.area(4));
console.log("圓周長:" + circle.circumference(14));
(2)模組的繼承
一個模組也可以繼承另一個模組。
// circleplus.js
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
加載上面的模組。
// main.js
module math from "circleplus";
import exp from "circleplus";
console.log(exp(math.pi);
(3)模組的預設方法
還可以為模組定義預設方法。
// circleplus.js
export default function(x) {
return Math.exp(x);
}
ECMAScript 7
2013年3月,ECMAScript 6的草案封閉,不再接受新功能了。新的功能將被加入ECMAScript 7。根據JavaScript創造者Brendan Eich的設想,ECMAScript 7將使得JavaScript更適於開發複雜的套用程式和函式庫。
ECMAScript 7可能包括的功能有:
Object.observe:物件與網頁元素的雙向綁定,只要其中之一發生變化,就會自動反映在另一者上。
Multi-Threading:多線程支持。目前,Intel和Mozilla有一個共同的研究項目RiverTrail,致力於讓JavaScript多線程執行。預計這個項目的研究成果會被納入ECMAScript標準。
Traits:它將是「類」功能(class)的一個替代。通過它,不同的物件可以分享同樣的特性。
其他可能包括的功能還有:更精確的數值計算、改善的記憶體回收、增強的跨站點安全、類型化的更貼近硬件的(Typed, Low-level)操作、國際化支持(Internationalization Support)、更多的資料結構等等。
留言
張貼留言