这篇文章会给你简单介绍一下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
)