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