這篇文章會給你簡單介紹一下ES6。如果你還不知道什么是ES6的話,它是JavaScript一個新的實現,如果你是一個忙碌的JavaScript開發者(但誰不是呢),那么繼續讀下去吧,看看當今最熱門的語言——JavaScript的新一代實現中,最棒的十個特性。
這是為忙碌的開發者准備的ES6中最棒的十個特性(無特定順序):
- 默認參數
- 模版表達式
- 多行字符串
- 拆包表達式
- 改進的對象表達式
- 箭頭函數
=&>
- Promise
- 塊級作用域的
let
和const
- 類
- 模塊化
注意:這個列表十分主觀並且帶有偏見,其他未上榜的特性並不是因為沒有作用,我這么做只是單純的希望將這份列表中的項目保持在十個。
首先,一個簡單的JavaScript時間線,不了解歷史的人也無法創造歷史。
- 1995年:JavaScript以LiveScript之名誕生
- 1997年:ECMAScript標准確立
- 1999年:ES3發布,IE5非常生氣
- 2000年-2005年:
XMLHttpRequest
,熟知為AJAX
,在如Outlook Web Access(2002)、Oddpost(2002)、Gmail(2004)、Google Maps(2005)中得到了廣泛的應用 - 2009年:ES5發布(這是我們目前用的最多的版本),帶來了
forEach
/Object.keys
/Object.create
(特地為Douglas Crockford所造,JSON標准創建者) ,還有JSON標准。
歷史課上完了,我們回來講編程。
1. ES6中的默認參數
還記得我們以前要這樣子來定義默認參數:
1
2
3
4
5
6
|
var link = function (height, color, url) {
var height = height || 50
var color = color || 'red'
var url = url || 'http://azat.co'
...
}
|
這樣做一直都沒什么問題,直到參數的值為0
,因為0
在JavaScript中算是false
值,它會直接變成后面硬編碼的值而不是0
本身。當然了,誰要用0
來傳值啊(諷刺臉)?所以我們也忽略了這個瑕疵,沿用了這個邏輯,否則的話只能…..沒有否則!在ES6中,我們可以把這些默認值直接放在函數簽名中。
1
2
3
|
var link = function(height = 50, color = 'red', url = 'http://azat.co') {
...
}
|
對了,這個語法和Ruby很像!
2. ES6中的模版表達式
模版表達式在其他語言中一般是為了在模版字符串中輸出變量,所以在ES5中,我們非得把字符串破開變成這樣:
1
2
|
var name = 'Your name is ' + first + ' ' + last + '.'
var url = 'http://localhost:3000/api/messages/' + id
|
幸運的是在ES6中我們有了新語法,在反引號包裹的字符串中,使用${NAME}語法來表示模板字符:
1
2
|
var name = `Your name is ${first} ${last}`
var url = `http://localhost:3000/api/messages/${id}`
|
3. ES6中的多行字符串
另一個好吃的語法糖就是多行字符串,以前我們的實現是像這樣的:
1
2
3
4
5
6
7
8
|
var roadPoem = 'Then took the other, as just as fair,nt'
+ 'And having perhaps the better claimnt'
+ 'Because it was grassy and wanted wear,nt'
+ 'Though as for that the passing therent'
+ 'Had worn them really about the same,nt'
var fourAgreements = 'You have the right to be you.n
You can only be you when you do your best.'
|
但是在ES6中,只要充分利用反引號。
1
2
3
4
5
6
7
8
|
var roadPoem = `Then took the other, as just as fair,
And having perhaps the better claim
Because it was grassy and wanted wear,
Though as for that the passing there
Had worn them really about the same,`
var fourAgreements = `You have the right to be you.
You can only be you when you do your best.`
|
4. ES6中的拆包表達式
拆包可能是一個比較難理解的概念,因為這里面真的是有魔法發生。假如說你有一個簡單的賦值表達式,把對象中的house
的mouse
賦值為house
和mouse
的變量。
1
2
3
|
var data = $('body').data(), // 假設data中有mouse和house的值
house = data.house,
mouse = data.mouse
|
另一個拆包的實例(Node.js):
1
2
3
4
5
|
var jsonMiddleware = require('body-parser').json
var body = req.body, // body中有用戶名和密碼值
username = body.username,
password = body.password
|
但是在ES6中我們可以用以下語句替換:
1
2
3
4
5
|
var { house, mouse} = $('body').data() // 我們會拿到house和mouse的值的
var {jsonMiddleware} = require('body-parser')
var {username, password} = req.body
|
甚至在數組中也能用,簡直瘋狂!
1
2
|
var [col1, col2] = $('.column'),
[line1, line2, line3, , line5] = file.split('n')
|
習慣拆包語法可能需要一些時間,但是這絕對是糖衣炮彈。
5. ES6中改進的對象表達式
你能用對象表達式所做的是超乎想象的!類定義的方法從ES5中一個美化版的JSON,進化到ES6中更像類的構造。
這是一個ES5中典型的對象表達式,定義了一些方法和屬性。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var serviceBase = {port: 3000, url: 'azat.co'},
getAccounts = function(){return [1,2,3]}
var accountServiceES5 = {
port: serviceBase.port,
url: serviceBase.url,
getAccounts: getAccounts,
toString: function() {
return JSON.stringify(this.valueOf())
},
getUrl: function() {return 'http://' + this.url + ':' + this.port},
valueOf_1_2_3: getAccounts()
}
|
如果你想做的好看一點,我們可以用Object.create
方法來讓 serviceBase
成為 accountServiceES5
的 prototype
從而實現繼承。
1
2
3
4
5
6
7
8
9
|
var accountServiceES5ObjectCreate = Object.create(serviceBase)
var accountServiceES5ObjectCreate = {
getAccounts: getAccounts,
toString: function() {
return JSON.stringify(this.valueOf())
},
getUrl: function() {return 'http://' + this.url + ':' + this.port},
valueOf_1_2_3: getAccounts()
}
|
我知道 accountServiceES5ObjectCreate
和 accountServiceES5
是不完全相同的。因為一個對象 accountServiceES5
會有如下所示的 __proto__
屬性:
但對於這個示例,我們就把這兩者考慮為相同的。所以在ES6的對象表達式中,我們把getAccounts: getAccounts
簡化為getAccounts,
,並且我們還可以用__proto__
直接設置prototype
,這樣聽起來合理的多。(不過並不是用proto
)
1
2
3
4
5
|
var serviceBase = {port: 3000, url: 'azat.co'},
getAccounts = function(){return [1,2,3]}
var accountService = {
__proto__: serviceBase,
getAccounts,
|
還有,我們可以調用 super
和動態索引(valueOf_1_2_3
)
1
2
3
4
5
6
7
8
|
// 續上段代碼
toString() {
return JSON.stringify((super.valueOf()))
},
getUrl() {return 'http://' + this.url + ':' + this.port},
[ 'valueOf_' + getAccounts().join('_') ]: getAccounts()
};
console.log(accountService)
|
這是對老舊的對象表達式一個很大的改進!
6. ES6中的箭頭函數
這或許是我最想要的一個特性,我愛 CoffeeScript 就是因為他胖胖的箭頭(=&>
相對於-&>
),現在ES6中也有了。這些箭頭最神奇的地方在於他會讓你寫正確的代碼。比如,this
在上下文和函數中的值應當是相同的,它不會變化,通常變化的原因都是因為你創建了閉包。
使用箭頭函數可以讓我們不再用that = this
或者self = this
或者_this = this
或者.bind(this)
這樣的代碼,比如,這些代碼在ES5中就特別丑。
1
2
3
4
|
var _this = this
$('.btn').click(function(event){
_this.sendData()
})
|
這是在ES6中去掉_this = this
之后:
1
2
3
|
$('.btn').click((event) =>{
this.sendData()
})
|
可惜的是,ES6委員會覺得再加上瘦箭頭(-&>
)的話就對我們太好了,所以他們留下了一個老舊的function
。(瘦箭頭在CoffeeScript中的作用就像ES5/6中一樣)
1
2
3
4
5
6
7
8
9
10
|
var logUpperCase = function() {
var _this = this
this.string = this.string.toUpperCase()
return function () {
return console.log(_this.string)
}
}
logUpperCase.call({ string: 'es6 rocks' })()
|
在ES6中我們無需_this
1
2
3
4
5
6
|
var logUpperCase = function() {
this.string = this.string.toUpperCase()
return () => console.log(this.string)
}
logUpperCase.call({ string: 'es6 rocks' })()
|
注意,在ES6中你可以合理的把箭頭函數和舊式 function
函數混用。當箭頭函數所在語句只有一行時,它就會變成一個表達式,它會直接返回這個語句的值。但是如果你有多行語句,你就要明確的使用return
。
這是ES5中利用messages
數組創建一個數組的代碼:
1
2
3
4
|
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map(function (value) {
return 'ID is ' + value // 顯式返回
});
|
在ES6中會變成這樣:
1
2
|
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map(value => `ID is ${value}`) // 隱式返回
|
注意到我用了字符串模版嗎,又一個從CoffeeScript中來的功能,我愛它們!
在只有一個參數的函數簽名中,括號是可有可無的,但是如果多於一個參數時就要加上。
1
2
|
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map((value, index, list) => `ID of ${index} element is ${value} `) // 隱式返回
|
7. ES6中的Promise
Promise是一個有爭議的話題。現在有很多Promise實現,語法也大致相同,比如q
/ bluebird
/ deferred.js
/ vow
/ avow
/ jquery deferred
等等。其他人說我們並不需要Promise,異步,回調和generator
之類的就很好。慶幸的是,現在在ES6中終於有一個標准的Promise實現。
我們來看一個相當微不足道的延遲異步執行,用setTimeout
實現
1
2
3
|
setTimeout(function(){
console.log('Yay!')
}, 1000)
|
我們可以用ES6中的Promise重寫:
1
2
3
4
5
|
var wait1000 = new Promise(function(resolve, reject) {
setTimeout(resolve, 1000)
}).then(function() {
console.log('Yay!')
})
|
或者用ES6的箭頭函數:
1
2
3
4
5
|
var wait1000 = new Promise((resolve, reject)=> {
setTimeout(resolve, 1000)
}).then(()=> {
console.log('Yay!')
})
|
到現在為止,我們只是單純增加了代碼的行數,還明顯沒有帶來任何好處,你說的對。但是如果我們有更多復雜的邏輯內嵌在setTimeout()
中的回調時好處就來了:
1
2
3
4
5
6
|
setTimeout(function(){
console.log('Yay!')
setTimeout(function(){
console.log('Wheeyee!')
}, 1000)
}, 1000)
|
可以用ES6中的Promise重寫:
1
2
3
4
5
6
7
8
9
10
|
var wait1000 = ()=> new Promise((resolve, reject)=> {setTimeout(resolve, 1000)})
wait1000()
.then(function() {
console.log('Yay!')
return wait1000()
})
.then(function() {
console.log('Wheeyee!')
});
|
還是無法相信Promise比普通回調要好?我也不信。我想一旦知道了回調這個方法它就會在你腦中縈繞,額外的復雜的Promise也沒有必要存在了。
不論怎么說,ES6中的Promise是為會欣賞的人准備的,Promise有一個不錯的失敗-捕捉
回調機制,看看這篇文章吧,里面有更多關於Promise的信息。ES6 Promise介紹
8. 塊級作用域的let
和const
你可能早就聽過對ES6中的let
那些奇怪的傳說,我記得我第一次到倫敦時為那些TO LET牌子感到非常困惑。但是ES6中的let
和出租無關,這不算是語法糖,它很復雜。let
是一個更新的var
,可以讓你把變量作用域限制在當前塊里。我們用{}
來定義塊,但是在ES5中這些花括號起不到任何作用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function calculateTotalAmount (vip) {
var amount = 0
if (vip) {
var amount = 1
}
{ // 讓塊來的更瘋狂
var amount = 100
{
var amount = 1000
}
}
return amount
}
console.log(calculateTotalAmount(true))
|
運行結果將會是1000
。天啊!這是多大的一個Bug。在ES6中,我們用let
來限制變量作用域為函數內。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function calculateTotalAmount (vip) {
var amount = 0 // 或許應該用let, 但你可以混用
if (vip) {
let amount = 1 // 第一個數量為 0
}
{ // 更多的塊
let amount = 100 // 第一個數量為 0
{
let amount = 1000 // 第一個數量為 0
}
}
return amount
}
console.log(calculateTotalAmount(true))
|
運行結果是0
,因為在if
塊中也有let
。如果什么都沒有的話(amount=1
),那么結果將會是1
。
說到const
,事情就簡單多了。他僅僅產生是一個不可變的變量,並且他的作用域也像let
一樣只有塊級。為了演示,這里有定義了一堆常量,並且由於作用域的原因,這些定義都是有效的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function calculateTotalAmount (vip) {
const amount = 0
if (vip) {
const amount = 1
}
{ // 更多的塊
const amount = 100
{
const amount = 1000
}
}
return amount
}
console.log(calculateTotalAmount(true))
|
依我愚見,let
和const
讓這門語言變得更加復雜,沒有這些的時候我們只有一條路可以走,但是現在可以要考慮更多的情景。;-(
9. ES6中的類
如果你喜歡面向對象編程,那么你會特別喜歡這個特性。他讓你編寫和繼承類時就跟在Facebook上發一個評論這么簡單。
在ES5中,因為沒有class
關鍵字(但它是毫無作用的保留字),類的創建和使用是讓人十分痛苦的事情。更慘的是,很多偽類的實現像pseude-classical, classical, functional讓人越來越摸不着頭腦,為JavaScript的信仰戰爭火上澆油。
我不會給你展示在ES5中怎么去編寫一個類(是啦是啦從對象可以衍生出來其他的類和對象),因為有太多方法去完成。我們直接看ES6的示例,告訴你ES6的類會用prototype
來實現而不是function
。現在有一個baseModel
類,其中我們可以定義構造函數和getName()
方法。
1
2
3
4
5
6
7
8
9
10
11
12
|
class baseModel {
constructor(options = {}, data = []) { // class constructor
this.name = 'Base'
this.url = 'http://azat.co/api'
this.data = data
this.options = options
}
getName() { // class method
console.log(`Class name: ${this.name}`)
}
}
|
注意到我給options
和data
用了默認參數,而且方法名再也不用加上function
或者:
了。還有一個很大的區別,你不能像構造函數里面一樣向this.Name
指派值。怎么說呢,和函數
有相同縮進的代碼里,你不能向name
賦值。如果有這個需要的話,在構造函數里面完成。
使用NAME extends PARENT_NAME
語法,AccountModel
從baseModel
繼承而來。
1
2
|
class AccountModel extends baseModel {
constructor(options, data) {
|
調用父類構造函數時,只需帶上參數輕松的調用super()
方法。
1
2
3
4
|
super({private: true}, ['32113123123', '524214691']) //call the parent method with super
this.name = 'Account Model'
this.url +='/accounts/'
}
|
想要高級一點的話,你可以像這樣弄一個getter
方法,這樣accountsData
就會變成一個屬性。
1
2
3
4
5
|
get accountsData() { // 返回計算后的數據
// ... make XHR
return this.data
}
}
|
現在你要怎么用這個魔咒,很簡單,就跟讓三歲小孩相信聖誕老人存在一樣。
1
2
3
|
let accounts = new AccountModel(5)
accounts.getName()
console.log('Data is %s', accounts.accountsData)
|
如果好奇輸出結果的話:
1
2
|
Class name: Account Model
Data is 32113123123,524214691
|
10. ES6中的模塊化
你可能知道,ES6之前JavaScript並沒有對模塊化有過原生的支持,人們想出來AMD
,RequireJS
,CommenJS
等等,現在終於有import
和export
運算符來實現了。
ES5中你會用script
標簽和IIFE(立即執行函數)
,或者是其他的像AMD
之類的庫,但是ES6中你可以用export
來暴露你的類。我是喜歡Node.js的人,所以我用和Node.js語法一樣的CommonJS
,然后用Browserfy來瀏覽器化。現在我們有一個port
變量和getAccounts
方法,在ES5中:
1
2
3
4
5
6
|
module.exports = {
port: 3000,
getAccounts: function() {
...
}
}
|
在ES5的main.js
中,用require('模塊')
來導入:
1
2
|
var service = require('module.js')
console.log(service.port) // 3000
|
但是在ES6中,我們用export
和import
。比如這是ES6中的module.js
文件:
1
2
3
4
|
export var port = 3000
export function getAccounts(url) {
...
}
|
在需要引入的main.js
文件中,可以用import {名稱} from '模塊'
語法:
1
2
|
import {port, getAccounts} from 'module'
console.log(port) // 3000
|
或者就直接在main.js
中引入所有的變量:
1
2
|
import * as service from 'module'
console.log(service.port) // 3000
|
個人來說,我覺得這樣的模塊化有些搞不懂。確實,這樣會更傳神一些 。但是Node.js中的模塊不會馬上就改過來,瀏覽器和服務器的代碼最好是用同樣的標准,所以目前我還是會堅持CommonJS/Node.js
的方式。
目前來說瀏覽器對ES6的支持還遙遙無期(本文寫作時),所以你需要一些像jspm這樣的工具來用ES6的模塊。
想要了解更多ES6中的模塊化和例子的話,來看這篇文章,不管怎么說,寫現代化的JavaScript吧!
怎么樣可以在今天就用上ES6(Babel)
ES6標准已經敲定,但還未被所有瀏覽器支持(Firefox的ES6功能一覽),如果想馬上就用上ES6,需要一個像Babel這樣的編譯器。你可以把他當獨立工具用,也可以將他集成到構建系統里,Babel對Gulp
,Grunt
和Webpack
都有對應的插件。
安裝Gulp插件示例:
1
|
$ npm install --save-dev gulp-babel
|
在gulpfile.js
中,定義這么一個任務,將src
目錄下的app.js
文件編譯到build
目錄下:
1
2
3
4
5
6
7
8
|
var gulp = require('gulp'),
babel = require('gulp-babel')
gulp.task('build', function () {
return gulp.src('src/app.js')
.pipe(babel())
.pipe(gulp.dest('build'))
})
|
Node.js和ES6
對於Node.js,你可以用構建工具或者直接用獨立模塊babel-core
:
1
|
$ npm install --save-dev babel-core
|
然后在Node.js中調用這個函數:
1
|
require('babel-core').transform(es5Code, options)
|
ES6的一些總結
ES6中還有很多你可能都用不上(至少現在用不上)的可圈可點的特性,以下無特定順序:
Math
/Number
/String
/Array
/Object
中新的方法- 二進制和八進制數據類型
- 自動展開多余參數
For of
循環(又見面了CoffeeScript)Symbols
- 尾部調用優化
generator
- 更新的數據結構(如
Map
和Set
)