這篇文章會給你簡單介紹一下ES6。若是你還不知道什麼是ES6的話,它是JavaScript一個新的實現,若是你是一個忙碌的JavaScript開發者(但誰不是呢),那麼繼續讀下去吧,看看當今最熱門的語言——JavaScript的新一代實現中,最棒的十個特性。javascript
這是爲忙碌的開發者準備的ES6中最棒的十個特性(無特定順序):html
=&>
let
和const
注意:這個列表十分主觀而且帶有偏見,其餘未上榜的特性並非由於沒有做用,我這麼作只是單純的但願將這份列表中的項目保持在十個。java
首先,一個簡單的JavaScript時間線,不瞭解歷史的人也沒法創造歷史。jquery
XMLHttpRequest
,熟知爲AJAX
,在如Outlook Web Access(2002)、Oddpost(2002)、Gmail(2004)、Google Maps(2005)中獲得了普遍的應用forEach
/ Object.keys
/ Object.create
(特意爲Douglas Crockford所造,JSON標準建立者) ,還有JSON標準。歷史課上完了,咱們回來說編程。git
還記得咱們之前要這樣子來定義默認參數: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中,咱們能夠把這些默認值直接放在函數簽名中。github
1
2
3
|
var link = function(height = 50, color = 'red', url = 'http://azat.co') {
...
}
|
對了,這個語法和Ruby很像!npm
模版表達式在其餘語言中通常是爲了在模版字符串中輸出變量,因此在ES5中,咱們非得把字符串破開變成這樣:編程
1
2
|
var name = 'Your name is ' + first + ' ' + last + '.'
var url = 'http://localhost:3000/api/messages/' + id
|
幸運的是在ES6中咱們有了新語法,在反引號包裹的字符串中,使用${NAME}語法來表示模板字符:json
1
2
|
var name = `Your name is ${first} ${last}`
var url = `http://localhost:3000/api/messages/${id}`
|
另外一個好吃的語法糖就是多行字符串,之前咱們的實現是像這樣的:
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.`
|
拆包多是一個比較難理解的概念,由於這裏面真的是有魔法發生。假如說你有一個簡單的賦值表達式,把對象中的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')
|
習慣拆包語法可能須要一些時間,可是這絕對是糖衣炮彈。
你能用對象表達式所作的是超乎想象的!類定義的方法從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)
|
這是對老舊的對象表達式一個很大的改進!
這或許是我最想要的一個特性,我愛 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} `) // 隱式返回
|
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介紹
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
讓這門語言變得更加複雜,沒有這些的時候咱們只有一條路能夠走,可是如今能夠要考慮更多的情景。;-(
若是你喜歡面向對象編程,那麼你會特別喜歡這個特性。他讓你編寫和繼承類時就跟在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
|
你可能知道,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標準已經敲定,但還未被全部瀏覽器支持(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,你能夠用構建工具或者直接用獨立模塊babel-core
:
1
|
$ npm install --save-dev babel-core
|
而後在Node.js中調用這個函數:
1
|
require('babel-core').transform(es5Code, options)
|
ES6中還有不少你可能都用不上(至少如今用不上)的可圈可點的特性,如下無特定順序:
Math
/ Number
/ String
/ Array
/ Object
中新的方法For of
循環(又見面了CoffeeScript)Symbols
generator
Map
和Set
)