JS 开发者必须知道的十个 ES6 新特性

这篇文章会给你简单介绍一下 ES6。如果你还不知道什么是 ES6 的话,它是 JavaScript 一个新的实现,如果你是一个忙碌的 JavaScript 开发者(但谁不是呢),那么继续读下去吧,看看当今最热门的语言——JavaScript 的新一代实现中,最棒的十个特性。

这是为忙碌的开发者准备的 ES6 中最棒的十个特性(无特定顺序):

  1. 默认参数
  2. 模版表达式
  3. 多行字符串
  4. 拆包表达式
  5. 改进的对象表达式
  6. 箭头函数 =&>
  7. Promise
  8. 块级作用域的letconst
  9. 模块化

注意:这个列表十分主观并且带有偏见,其他未上榜的特性并不是因为没有作用,我这么做只是单纯的希望将这份列表中的项目保持在十个。

首先,一个简单的 JavaScript 时间线,不了解历史的人也无法创造历史。

  1. 1995 年:JavaScript 以 LiveScript 之名诞生
  2. 1997 年:ECMAScript 标准确立
  3. 1999 年:ES3 发布,IE5 非常生气
  4. 2000 年-2005 年:XMLHttpRequest,熟知为AJAX,在如 Outlook Web Access(2002)、Oddpost(2002)、Gmail(2004)、Google Maps(2005)中得到了广泛的应用
  5. 2009 年:ES5 发布(这是我们目前用的最多的版本),带来了forEach / Object.keys/ Object.create(特地为 Douglas Crockford 所造,JSON 标准创建者) ,还有 JSON 标准。

历史课上完了,我们回来讲编程。

1. ES6 中的默认参数

还记得我们以前要这样子来定义默认参数:

JavaScript

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 中,我们可以把这些默认值直接放在函数签名中。

JavaScript

1

2

3

var link = function(height = 50, color = ‘red’, url = ‘http://azat.co') {

}

对了,这个语法和 Ruby 很像!

2. ES6 中的模版表达式

模版表达式在其他语言中一般是为了在模版字符串中输出变量,所以在 ES5 中,我们非得把字符串破开变成这样:

JavaScript

1

2

var name = ‘Your name is ‘ + first + ‘ ‘ + last + ‘.’

var url = ‘http://localhost:3000/api/messages/' + id

幸运的是在 ES6 中我们有了新语法,在反引号包裹的字符串中,使用${NAME}语法来表示模板字符:

JavaScript

1

2

var name = Your name is ${first} ${last}

var url = http://localhost:3000/api/messages/${id}

3. ES6 中的多行字符串

另一个好吃的语法糖就是多行字符串,以前我们的实现是像这样的:

JavaScript

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 中,只要充分利用反引号。

JavaScript

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 中的拆包表达式

拆包可能是一个比较难理解的概念,因为这里面真的是有魔法发生。假如说你有一个简单的赋值表达式,把对象中的housemouse赋值为housemouse的变量。

JavaScript

1

2

3

var data = $(‘body’).data(), // 假设 data 中有 mouse 和 house 的值

house = data.house,

mouse = data.mouse

另一个拆包的实例(Node.js):

JavaScript

1

2

3

4

5

var jsonMiddleware = require(‘body-parser’).json

var body = req.body, // body 中有用户名和密码值

username = body.username,

password = body.password

但是在 ES6 中我们可以用以下语句替换:

JavaScript

1

2

3

4

5

var { house, mouse} = $(‘body’).data() // 我们会拿到 house 和 mouse 的值的

var {jsonMiddleware} = require(‘body-parser’)

var {username, password} = req.body

甚至在数组中也能用,简直疯狂!

JavaScript

1

2

var [col1, col2] = $(‘.column’),

[line1, line2, line3, , line5] = file.split(‘n’)

习惯拆包语法可能需要一些时间,但是这绝对是糖衣炮弹。

5. ES6 中改进的对象表达式

你能用对象表达式所做的是超乎想象的!类定义的方法从 ES5 中一个美化版的 JSON,进化到 ES6 中更像类的构造。

这是一个 ES5 中典型的对象表达式,定义了一些方法和属性。

JavaScript

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成为 accountServiceES5prototype从而实现继承。

JavaScript

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()

}

我知道 accountServiceES5ObjectCreateaccountServiceES5不完全相同的。因为一个对象 accountServiceES5会有如下所示的 __proto__属性:

但对于这个示例,我们就把这两者考虑为相同的。所以在 ES6 的对象表达式中,我们把getAccounts: getAccounts简化为getAccounts,,并且我们还可以用__proto__直接设置prototype,这样听起来合理的多。(不过并不是用proto

JavaScript

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)

JavaScript

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 中就特别丑。

JavaScript

1

2

3

4

var _this = this

$(‘.btn’).click(function(event){

_this.sendData()

})

这是在 ES6 中去掉_this = this之后:

JavaScript

1

2

3

$(‘.btn’).click((event) =>{

this.sendData()

})

可惜的是,ES6 委员会觉得再加上瘦箭头(-&>)的话就对我们太好了,所以他们留下了一个老旧的function。(瘦箭头在 CoffeeScript 中的作用就像 ES5/6 中一样)

JavaScript

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

JavaScript

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数组创建一个数组的代码:

JavaScript

1

2

3

4

var ids = [‘5632953c4e345e145fdf2df8’,’563295464e345e145fdf2df9’]

var messages = ids.map(function (value) {

return ‘ID is ‘ + value // 显式返回

});

在 ES6 中会变成这样:

JavaScript

1

2

var ids = [‘5632953c4e345e145fdf2df8’,’563295464e345e145fdf2df9’]

var messages = ids.map(value => ID is ${value}) // 隐式返回

注意到我用了字符串模版吗,又一个从 CoffeeScript 中来的功能,我爱它们!

在只有一个参数的函数签名中,括号是可有可无的,但是如果多于一个参数时就要加上。

JavaScript

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实现

JavaScript

1

2

3

setTimeout(function(){

console.log(‘Yay!’)

}, 1000)

我们可以用 ES6 中的 Promise 重写:

JavaScript

1

2

3

4

5

var wait1000 = new Promise(function(resolve, reject) {

setTimeout(resolve, 1000)

}).then(function() {

console.log(‘Yay!’)

})

或者用 ES6 的箭头函数:

JavaScript

1

2

3

4

5

var wait1000 = new Promise((resolve, reject)=> {

setTimeout(resolve, 1000)

}).then(()=> {

console.log(‘Yay!’)

})

到现在为止,我们只是单纯增加了代码的行数,还明显没有带来任何好处,你说的对。但是如果我们有更多复杂的逻辑内嵌在setTimeout()中的回调时好处就来了:

JavaScript

1

2

3

4

5

6

setTimeout(function(){

console.log(‘Yay!’)

setTimeout(function(){

console.log(‘Wheeyee!’)

}, 1000)

}, 1000)

可以用 ES6 中的 Promise 重写:

JavaScript

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. 块级作用域的letconst

你可能早就听过对 ES6 中的let那些奇怪的传说,我记得我第一次到伦敦时为那些 TO LET 牌子感到非常困惑。但是 ES6 中的let和出租无关,这不算是语法糖,它很复杂。let是一个更新的var,可以让你把变量作用域限制在当前块里。我们用{}来定义块,但是在 ES5 中这些花括号起不到任何作用。

JavaScript

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来限制变量作用域为函数内。

JavaScript

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一样只有块级。为了演示,这里有定义了一堆常量,并且由于作用域的原因,这些定义都是有效的。

JavaScript

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))

依我愚见,letconst让这门语言变得更加复杂,没有这些的时候我们只有一条路可以走,但是现在可以要考虑更多的情景。;-(

9. ES6 中的类

如果你喜欢面向对象编程,那么你会特别喜欢这个特性。他让你编写和继承类时就跟在 Facebook 上发一个评论这么简单。

在 ES5 中,因为没有class关键字(但它是毫无作用的保留字),类的创建和使用是让人十分痛苦的事情。更惨的是,很多伪类的实现像pseude-classical, classical, functional让人越来越摸不着头脑,为 JavaScript 的信仰战争火上浇油。

我不会给你展示在 ES5 中怎么去编写一个类(是啦是啦从对象可以衍生出来其他的类和对象),因为有太多方法去完成。我们直接看 ES6 的示例,告诉你 ES6 的类会用prototype来实现而不是function。现在有一个baseModel类,其中我们可以定义构造函数和getName()方法。

JavaScript

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})

}

}

注意到我给optionsdata用了默认参数,而且方法名再也不用加上function或者:了。还有一个很大的区别,你不能像构造函数里面一样向this.Name指派值。怎么说呢,和函数有相同缩进的代码里,你不能向name赋值。如果有这个需要的话,在构造函数里面完成。

使用NAME extends PARENT_NAME语法,AccountModelbaseModel继承而来。

JavaScript

1

2

class AccountModel extends baseModel {

constructor(options, data) {

调用父类构造函数时,只需带上参数轻松的调用super()方法。

JavaScript

1

2

3

4

super({private: true}, [‘32113123123’, ‘524214691’]) //call the parent method with super

this.name = ‘Account Model’

this.url +=’/accounts/‘

}

想要高级一点的话,你可以像这样弄一个getter方法,这样accountsData就会变成一个属性。

JavaScript

1

2

3

4

5

get accountsData() { // 返回计算后的数据

// … make XHR

return this.data

}

}

现在你要怎么用这个魔咒,很简单,就跟让三岁小孩相信圣诞老人存在一样。

JavaScript

1

2

3

let accounts = new AccountModel(5)

accounts.getName()

console.log(‘Data is %s’, accounts.accountsData)

如果好奇输出结果的话:

JavaScript

1

2

Class name: Account Model

Data is 32113123123,524214691

10. ES6 中的模块化

你可能知道,ES6 之前 JavaScript 并没有对模块化有过原生的支持,人们想出来AMDRequireJSCommenJS等等,现在终于有importexport运算符来实现了。

ES5 中你会用script标签和IIFE(立即执行函数),或者是其他的像AMD之类的库,但是 ES6 中你可以用export来暴露你的类。我是喜欢 Node.js 的人,所以我用和 Node.js 语法一样的CommonJS,然后用Browserfy来浏览器化。现在我们有一个port变量和getAccounts方法,在 ES5 中:

JavaScript

1

2

3

4

5

6

module.exports = {

port: 3000,

getAccounts: function() {

}

}

在 ES5 的main.js中,用require('模块')来导入:

JavaScript

1

2

var service = require(‘module.js’)

console.log(service.port) // 3000

但是在 ES6 中,我们用exportimport。比如这是 ES6 中的module.js文件:

JavaScript

1

2

3

4

export var port = 3000

export function getAccounts(url) {

}

在需要引入的main.js文件中,可以用import {名称} from '模块'语法:

JavaScript

1

2

import {port, getAccounts} from ‘module’

console.log(port) // 3000

或者就直接在main.js中引入所有的变量:

JavaScript

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 对GulpGruntWebpack都有对应的插件

安装 Gulp 插件示例:

JavaScript

1

$ npm install –save-dev gulp-babel

gulpfile.js中,定义这么一个任务,将src目录下的app.js文件编译到build目录下:

JavaScript

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

JavaScript

1

$ npm install –save-dev babel-core

然后在 Node.js 中调用这个函数:

JavaScript

1

require(‘babel-core’).transform(es5Code, options)

ES6 的一些总结

ES6 中还有很多你可能都用不上(至少现在用不上)的可圈可点的特性,以下无特定顺序:

  1. Math / Number / String / Array / Object中新的方法
  2. 二进制和八进制数据类型
  3. 自动展开多余参数
  4. For of循环(又见面了 CoffeeScript)
  5. Symbols
  6. 尾部调用优化
  7. generator
  8. 更新的数据结构(如MapSet

文章作者: xkloveme
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 xkloveme !
评论
 上一篇
移动端或者pc端不允许点击返回按钮 移动端或者pc端不允许点击返回按钮
公司做移动端,支付成功之后摁返回按钮导致短信再次发送或者接口再次请求,只想让他点击页面指定的返回按钮,但是由于移动端不好监听手机的物理返回键以及微信左上角的返回按钮,经公司大牛研究好久出来下面代码 history.pushState({},
2017-08-31
下一篇 
推荐大家使用的CSS书写规范、顺序 推荐大家使用的CSS书写规范、顺序
写了这么久的CSS,但大部分前端 er 都没有按照良好的 CSS 书写规范来写 CSS 代码,这样会影响代码的阅读体验,这里设计达人网总结一个 CSS 书写规范、CSS 书写顺序供大家参考,这些是参考了国外一些文章以及我的个人经验总结出来,
2017-08-28
  目录