ES6(ECMAScript 6)終于在2015年6月正式發(fā)布了。距離上一次正式公開(kāi)的ES5(于2009年發(fā)布的)已經(jīng)相距了不短的時(shí)間了。其實(shí)在ES6正式發(fā)布之前的一段時(shí)間內(nèi),ECMA組織已經(jīng)不再向ES6中添加新特性了,所以其實(shí)在ES6正式發(fā)布之前,業(yè)界已經(jīng)有許多對(duì)ES6的相關(guān)實(shí)踐了。比如阮一峰的ECMAScript 6入門其實(shí)就是早于ES6的正式發(fā)布時(shí)間的。
本文將主要基于lukehoban/es6features,參考眾多的博客文章資料,對(duì)新出的ES6中新增的特性做一個(gè)簡(jiǎn)介。后續(xù)本系列將會(huì)產(chǎn)出針對(duì)ES6不同特性的文章。
下面的表格給出了ES6包含的所有特性,
新增特性 | 關(guān)鍵詞 | 用法 | 描述 |
---|---|---|---|
箭頭操作符 | Arrows | v => console.log(v)
|
類似于部分強(qiáng)類型語(yǔ)言中的lambda表達(dá)式 |
類的支持 | Classes | - | 原生支持類,讓javascript的OOP編碼更加地道 |
增強(qiáng)的對(duì)象字面量 | enhanced object literals | - | 增強(qiáng)對(duì)象字面量 |
字符串模板 | template strings | ${num}
|
原生支持字符串模板,不再需要第三方庫(kù)的支持 |
解構(gòu)賦值 | destructuring | [x, y] = ['hello', 'world']
|
使用過(guò)python的話,你應(yīng)該很熟悉這個(gè)語(yǔ)法 |
函數(shù)參數(shù)擴(kuò)展 | default, rest, spread | - | 函數(shù)參數(shù)可以使用默認(rèn)值、不定參數(shù)以及拓展參數(shù)了 |
let、const | let、const | - | javascript中可以使用塊級(jí)作用域和聲明常量了 |
for…of遍歷 | for…of | for (v of someArray) { ... }
|
又多了一種折騰數(shù)組、Map等數(shù)據(jù)結(jié)構(gòu)的方法了 |
迭代器和生成器 | iterators, generator, iterables | - | ES6較為難以理解的新東西,后面會(huì)有相關(guān)文章 |
Unicode | unicode | - | 原生的unicode更加完美的支持 |
模塊和模塊加載 | modules, modules loader | - | ES6中開(kāi)始支持原生模塊化啦 |
map, set, weakmap, weakset | - | - | 新的數(shù)據(jù)結(jié)構(gòu) |
監(jiān)控代理 | proxies | - | 我們可以監(jiān)聽(tīng)對(duì)象發(fā)生了哪些事,并可以自定義對(duì)應(yīng)的操作 |
Symbols | - | - | 我們可以使用symbol來(lái)創(chuàng)建一個(gè)不同尋常的key |
Promises | - | - | 這家伙經(jīng)常在討論異步處理流程時(shí)被提到 |
新的API | math, number, string, array, object | - | 原生的功能性API就是方便些 |
內(nèi)置對(duì)象可以被繼承 | subclassable built-ins | - | 可以基于內(nèi)置對(duì)象,比如Array,來(lái)生成一個(gè)類 |
二進(jìn)制、八進(jìn)制字面量 | - | - | 可以直接在es6中使用二進(jìn)制或者八進(jìn)制字面量了 |
Reflect API | - | - | 反射API? |
尾調(diào)用 | tail calls | - | ES6中會(huì)自動(dòng)幫你做一些尾遞歸方面的優(yōu)化 |
ok,上面就是es6中涉及到的所有的新增特性。下面我們針對(duì)每一個(gè)topic,舉一個(gè)sample example加以說(shuō)明,不過(guò)并不會(huì)深入闡述。
何為箭頭操作符?其實(shí)箭頭操作符其實(shí)就是使用=>
語(yǔ)法來(lái)代替函數(shù)。如果你熟悉C#或者Java之類的強(qiáng)類型語(yǔ)音,你應(yīng)該知道lambda語(yǔ)法,其實(shí)箭頭操作符做的事情跟lambda表達(dá)式有異曲同工之妙。具體的用法看下面的例子,
var arr = [1, 2, 3];
// 傳統(tǒng)寫(xiě)法
arr.forEach(function (v) {
console.log(v);
});
// 使用箭頭操作符
arr.forEach( v => console.log(v));
在傳統(tǒng)寫(xiě)法中,我們需要寫(xiě)一個(gè)匿名的函數(shù),然后給這個(gè)函數(shù)傳入?yún)?shù),在函數(shù)體中對(duì)此參數(shù)做相關(guān)處理。而使用箭頭操作符后,它簡(jiǎn)化了函數(shù)的編寫(xiě)(其實(shí)是不需要再寫(xiě)匿名函數(shù)了)。
箭頭操作符的左側(cè)可以接收一個(gè)簡(jiǎn)單的參數(shù),也可以使用一個(gè)參數(shù)列表。右側(cè)進(jìn)行具體的操作或者是返回值。下面的示例展示了箭頭操作符更一般的用法,
// 在表達(dá)式中使用
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);
var pairs = evens.map(v => ({even: v, odd: v + 1}))
// 在申明中使用
nums.forEach(v => {
if (v % 5 === 0) fives.push(v);
});
// 使用this指針
var bob = {
_name: "Bob",
_friends: [],
printFriends() {
this._friends.forEach(f => consol.log(this._name + " knows " + f));
}
}
ES6中提供了類的原生支持,引入了class
關(guān)鍵字,其實(shí)它是一種實(shí)現(xiàn)OOP編程思想的語(yǔ)法糖。它是基于原型鏈的。
其實(shí)javascript本身就是面向?qū)ο蟮恼Z(yǔ)音,只不過(guò)沒(méi)有Java之類的語(yǔ)言那么學(xué)院派,它更加靈活。ES6中提供的類其實(shí)就是對(duì)原型模型的包裝。
ES6提供原生class
支持后,類方面的操作,比如類的創(chuàng)建、繼承等等更加直觀了。并且父類方法的調(diào)用、實(shí)例化、靜態(tài)方法和構(gòu)造函數(shù)等OOP概念都更加形象化了。
我們來(lái)看一些示例,看在ES6中到底如何使用學(xué)院化的使用類,
// 類的定義
class Animal {
// ES6中的構(gòu)造器,相當(dāng)于構(gòu)造函數(shù)
constructor(name) {
this.name = name;
}
// 實(shí)例方法
sayName() {
console.log('My Name is ' + this.name);
}
}
// 類的繼承
class Programmer extends Animal {
constructor(name) {
// 直接調(diào)用父類構(gòu)造器進(jìn)行初始化
super(name);
}
// 子類自己的實(shí)例方法
program() {
console.log('I\'am coding...');
}
// 靜態(tài)方法
static LEVEL() {
console.log('LEVEL BAD!');
}
}
// 一些測(cè)試
var doggy=new Animal('doggy'),
larry=new Programmer('larry');
doggy.sayName(); // ‘My name is doggy’
larry.sayName(); // ‘My name is larry’
larry.program(); // ‘I'm coding...’
Programmer.LEVEL(); // ‘LEVEL BAD!’
可以看到,ES6中我們可以完全用Java中的思想來(lái)寫(xiě)創(chuàng)建類、繼承類、實(shí)例方法、初始化等等。
在ES6中,對(duì)象字面量被增強(qiáng)了,寫(xiě)法更賤geek,同時(shí)可以在定義對(duì)象的時(shí)候做的事情更多了。比如,
function
關(guān)鍵字了具體的我們看下面的示例代碼,
var human = {
breathe() {
console.log('breathing...');
}
};
function sleep() {
console.log('sleeping...');
}
var worker = {
__proto__: human, // 設(shè)置原型,相當(dāng)于繼承human
company: 'code',
sleep, // 相當(dāng)于 `sleep: sleep`
work() {
console.log('working...');
}
};
human.breathe(); // `breathing...`
worker.sleep(); // `sleeping...`
worker.work(); // `working...`
ES6中提供原生的字符串模板支持。其語(yǔ)法很簡(jiǎn)單使用使用反引號(hào)`來(lái)創(chuàng)建字符串模板,字符串模板中可以使用${placeholder}來(lái)生成占位符。
如果你有使用后端模板的經(jīng)驗(yàn),比如smarty
之類的,那么你將對(duì)此不會(huì)太陌生。讓我們來(lái)看段示例,
var num = Math.random();
console.log(`your random num is ${num}`);
字符串模板其實(shí)并不是很難的東西,相對(duì)比較容易理解。個(gè)人覺(jué)得其目前主要的使用場(chǎng)景就是改變了動(dòng)態(tài)生成字符串的方式,不再像以前那樣使用字符串拼接的方式。
如果你熟悉python等語(yǔ)言,你對(duì)解構(gòu)賦值的概念應(yīng)該很了解。解構(gòu)的含義簡(jiǎn)單點(diǎn)就是自動(dòng)解析數(shù)組或者對(duì)象中值,解析出來(lái)之后一次賦值給一系列的變量。
利用這個(gè)特性,我們可以讓一個(gè)函數(shù)返回一個(gè)數(shù)組,然后利用解構(gòu)賦值得到數(shù)組中的每一個(gè)元素。讓我們來(lái)看一些例子。
function getVal() {
return [1, 2];
}
var [x, y] = getValue();
var [name, age] = ['larry', 26];
console.log('x: ' + x + ', y: ' + y); // x: 1, y: 2
console.log('name: ' + name + ', age: ' + age); // name: larry, age: 26
// 交換兩個(gè)變量的值
var [a, b] = [1, 2];
[a, b] = [b, a];
console.log('a: ' + a + ', b: ' + b);
ES6中對(duì)函數(shù)參數(shù)作了很大的擴(kuò)展,包括
默認(rèn)參數(shù),即我們可以為函數(shù)的參數(shù)設(shè)置默認(rèn)值了??赡苣阍谀承﹋s庫(kù)的源碼經(jīng)常會(huì)看到類似下面這樣的操作,
function foo(name) {
name = name || 'larry';
console.log(name);
}
上面的或操作其實(shí)就是為了給參數(shù)name
設(shè)置默認(rèn)值。
而在ES6中,我們可以采用更加方便的方式,如下,
function foo(name = 'larry') {
console.log(name);
}
foo(); // 'larry'
foo('gejiawen'); // 'gejiawen'
不定參數(shù)的意思是,我們的函數(shù)接受的參數(shù)是不確定的,由具體的調(diào)用時(shí)決定到底會(huì)有幾個(gè)參數(shù)。其實(shí)在ES6之前的規(guī)范中我們大可以使用arguments
變量來(lái)達(dá)到不定參數(shù)的判斷,但是無(wú)疑這一方案過(guò)于復(fù)雜。
不定參數(shù)的用法是這樣的,我們?cè)诤瘮?shù)參數(shù)中使用...x
這樣的方式來(lái)代表所有的參數(shù)。其中x
的作用就是指代所有的不定參數(shù)集合(其實(shí)它就是一個(gè)數(shù)組)。讓我們來(lái)看個(gè)例子,
function add(...x) {
return x.reduce((m, n) => m + n);
}
console.log(add(1, 2, 3)); // 6
console.log(add(1, 2, 3, 4, 5)); // 15
關(guān)于拓展參數(shù),其實(shí)拓展參數(shù)其實(shí)一種語(yǔ)法糖,它允許傳遞一個(gè)數(shù)組(或者類數(shù)組)作為函數(shù)的參數(shù),而不需要apply
操作,函數(shù)內(nèi)部可以自動(dòng)將其當(dāng)前不定參數(shù)解析,然后可以獲得每一個(gè)數(shù)組(類數(shù)組)元素作為實(shí)際參數(shù)。看下面的示例,
function foo(x, y, z) {
return x + y + z;
}
console.log(foo(...[1, 2, 3])); // 6
let
和const
關(guān)鍵字ES6中新增了兩個(gè)關(guān)鍵字,分別為let
和const
。
let
用于聲明對(duì)象,作用類似var
,但是let
聲明的變量擁有更狹隘的作用域,脫離特定的作用域后變量的生命周期就終結(jié)了。這將有效的避免javascript中由于隱式變量作用域提升而導(dǎo)致的各種bug,甚至是內(nèi)存泄露等等問(wèn)題。
const
用于聲明常量。
讓我們來(lái)看一些例子。
for (let i = 0; i < 2; i++) {
console.log(i); // 0, 1
}
console.log(i); // undefined, 嚴(yán)格模式下會(huì)報(bào)錯(cuò)
for...of
遍歷ES6中新增了一種遍歷數(shù)組和對(duì)象的方法,叫做for...of
。他與for...in
用法相似??聪旅娴睦樱?/p>
var arr = [1, 2, 3];
for (let v of arr) {
console.log(v); // 1,2,3
}
for (let i in arr) {
console.log(arr[i]); // 1,2,3
}
從上面的示例代碼中,我們可以看出,for...of
遍歷時(shí)提供的是value,而for...in
遍歷提供的是key。
ES6中新增的迭代器和生成器的相關(guān)概念相對(duì)來(lái)說(shuō)有點(diǎn)復(fù)雜,后面將會(huì)產(chǎn)出專門的文章針對(duì)這個(gè)topic進(jìn)行闡述。
Promises是處理異步操作的一種模式,之前在很多三方庫(kù)中有實(shí)現(xiàn),比如jQuery的deferred
對(duì)象。當(dāng)你發(fā)起一個(gè)異步請(qǐng)求,并綁定了.when()
,.done()
等事件處理程序時(shí),其實(shí)就是在應(yīng)用promise模式。下面是一段示例代碼,
//創(chuàng)建promise
var promise = new Promise(function(resolve, reject) {
// 進(jìn)行一些異步或耗時(shí)操作
if ( /*如果成功 */ ) {
resolve("Stuff worked!");
} else {
reject(Error("It broke"));
}
});
//綁定處理程序
promise.then(function(result) {
//promise成功的話會(huì)執(zhí)行這里
console.log(result); // "Stuff worked!"
}, function(err) {
//promise失敗會(huì)執(zhí)行這里
console.log(err); // Error: "It broke"
});
關(guān)于promise更多的詳細(xì)內(nèi)容,后面將會(huì)產(chǎn)出專門的文章針對(duì)這個(gè)topic進(jìn)行闡述。
ES6中對(duì)Unicode的支持更加完善了,可以使用使用正則表達(dá)式的u
標(biāo)記對(duì)unicode字符串進(jìn)行匹配。
// same as ES5.1
"????".length == 2
// new RegExp behaviour, opt-in ‘u’
"????".match(/./u)[0].length == 2
// new form
"\u{20BB7}"=="????"=="\uD842\uDFB7"
// new String ops
"????".codePointAt(0) == 0x20BB7
// for-of iterates code points
for(var c of "????") {
console.log(c);
}
在ES6中,javascript從語(yǔ)言層面開(kāi)始支持module機(jī)制了。在ES6之前,各種模塊化JS代碼的機(jī)制和規(guī)范早就已經(jīng)大行其道,比如CommonJS、AMC、CMD等。不過(guò)這些都是一些第三方的規(guī)范并不是javascript官方的標(biāo)準(zhǔn)規(guī)范。
ES6中定義的module機(jī)制的用法如下,
// point.js文件
module "point" {
export class Point {
constructor (x, y) {
public x = x;
public y = y;
}
}
}
// app.js文件
module point from "/point.js";
import Point from "point"
var origin = new Point(0, 0);
console.log(origin.x, origin.y); // 0, 0
其中app.js
中的前兩句的作用是聲明導(dǎo)入模塊,并且指定需要導(dǎo)入的接口。其實(shí)這兩句可以合并成一句,import Point from "/point.js"
或者是這樣import * as Point from "/point.js"
。不過(guò),如果合并成一句,在使用導(dǎo)入模塊中的暴露接口時(shí),需要這么來(lái)使用,
var origin = new Point.Point(0, 0);
Map
,Set
, WeakMap
, WeakSet
這四個(gè)數(shù)據(jù)結(jié)構(gòu)是ES6中新增的集合類型。同時(shí)提供了更加方便的獲取屬性值的方法,不用像以前一樣用hasOwnProperty
來(lái)檢查某個(gè)屬性是屬于原型鏈上的還是當(dāng)前對(duì)象的。同時(shí),在進(jìn)行屬性值添加與獲取時(shí)有專門的get
,set
魔術(shù)方法。
看下面的示例代碼,
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;
// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;
有時(shí)候我們會(huì)把對(duì)象作為另一個(gè)對(duì)象的鍵用來(lái)存放屬性值,普通集合類型,比如一個(gè)簡(jiǎn)單的對(duì)象會(huì)阻止垃圾回收器對(duì)這些作為屬性鍵存在的對(duì)象的回收,有造成內(nèi)存泄漏的危險(xiǎn)。而WeakMap
,WeakSet
則更加安全些,這些作為屬性鍵的對(duì)象如果沒(méi)有別的變量在引用它們,則會(huì)被回收釋放掉,具體還看下面的例子。
// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });//因?yàn)樘砑拥絯s的這個(gè)臨時(shí)對(duì)象沒(méi)有其他變量引用它,所以ws不會(huì)保存它的值,也就是說(shuō)這次添加其實(shí)沒(méi)有意思
對(duì)Math
,Number
,String
還有Object
等添加了許多新的API。下面的示例代碼對(duì)這些新的API進(jìn)行了簡(jiǎn)單展示。
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".includes("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].find(x => x == 3); // 3
[1, 2, 3].findIndex(x => x == 2); // 1
[1, 2, 3, 4, 5].copyWithin(3, 0); // [1, 2, 3, 1, 2]
["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) });
我們知道對(duì)象其實(shí)是鍵值對(duì)的集合,而鍵通常來(lái)說(shuō)是字符串。在ES6中,除了字符串外,我們還可以用symbol這種值來(lái)做為對(duì)象的鍵。Symbol
是一種基本類型,像數(shù)字,字符串還有布爾一樣,它不是一個(gè)對(duì)象。
Symbol
通過(guò)調(diào)用symbol函數(shù)產(chǎn)生,它接收一個(gè)可選的名字參數(shù),該函數(shù)返回的symbol是唯一的。之后就可以用這個(gè)返回值做為對(duì)象的鍵了。Symbol
還可以用來(lái)創(chuàng)建私有屬性,外部無(wú)法直接訪問(wèn)由symbol做為鍵的屬性值。
看下面的演示代碼,
(function() {
// 創(chuàng)建symbol
var key = Symbol("key");
function MyClass(privateData) {
this[key] = privateData;
}
MyClass.prototype = {
doStuff: function() {
... this[key] ...
}
};
})();
var c = new MyClass("hello")
c["key"] === undefined // 無(wú)法訪問(wèn)該屬性,因?yàn)槭撬接械?/code>
更多建議: