过去几年 JavaScript 发生了很大的变化。下面的 12 个新功能现在就可以用起来了。
JavaScript 历史
新补充的语言叫 ECMAScript 6,也叫 ES6 或 ES2015+。
JavaScript 自 1995 年面世以来,一直在缓慢地改进着。每隔几年都会有新的补充。1997 年成立的 ECMAScript 引领着 JavaScript 的发展。已发布的版本有 ES3、 ES5、 ES6 等。
JavaScript 进化史
ES3 与 ES5 之间隔了 10 年,而ES5 与 ES6 之间隔了 6 年。改进的新模式是每年都渐进式地做一些小改动,而不是像 ES6 一样一次性地进行大量的更改。
浏览器支持
所有的现代浏览器和环境都已经支持 ES6 了。
来源:https://kangax.github.io/compat-table/es6/
Chrome、MS Edge、Firefox、Safari、Node和其它很多环境都已经嵌入程序以支持 JavaScript ES6 的大部分功能。所以从本教程学到的所有功能都可以直接使用。
来开始学习 ECMAScript 6 吧!
ES6 核心功能
可以在浏览器控制台上尝试下面的代码。
所以不要照单全收,试试每个 ES5 和 ES6 示例。我们开始吧。
块级作用域变量
在 ES6 中,我们使用 let/const,而非 var 来声明变量。
var 有什么缺点呢?
var 的问题是变量会泄露到其它代码块,比如 for 循环或是 if 块。
test(false) 应该返回 outer,但并不是,得到的值是 undefined。
为什么?
因为即使 if 块没有执行,第 4 行的表达式 var x 也被提升了。
1 | /* |
ECMAScript 2015帮助解决了这个问题:
1 | //ES6 |
用 let 替代 var 以便按预期的那样去执行代码。如果没有调用 if 块,变量 x 就不会提升到块外。
Let 提升 和“temporal dead zone”
- 在 ES6 中,let 会将变量提升到块顶部(而 非 像 ES5 是函数顶部)。
- 但* 是在变量声明前引用变量会造成 ReferenceError。
- let 是块作用域的,不可以在声明前使用。
- “Temporal dead zone”是块开始直到变量被声明的这段区域。
IIFE
在介绍 IIFE 之前,我们来看个例子:
1 | //ES5 |
如你所见,private 会发生泄漏。需要使用 IIFE(立即执行函数表达式)将其包起来:
1 | //ES5 |
如果看过 jQuery/lodash 或是其它开源项目的代码,你会注意到它们都利用了 IIFE,以避免污染全局环境,而只在全局下定义 _、$或是 jQuery。
ES6 更工整,不再需要使用 IIFE,只要用块和 let 就可以了:
1 | ES6 |
Const
如果不希望变量的值再改变,可以使用 const。
总之:用 let 和 const 代替 var。
使用 const 进行引用;避免使用 var。
如果必须重新指定引用,可以用 let 代替 const。
文本模板
遇到文本模板时,不必再用嵌套连接了。比如:
1 | //ES5 |
现在可以用 反引号(`) 和字符串插值 ${}:
1 | //ES6 |
多行字符串
不必像这样再连接 + n 字符串了:
1 | //ES5 |
ES6 中同样可以用反引号解决:
1 | ES6 |
两段代码会得到完全相同的结果。
解构赋值
ES6 解构非常简明并且好用。看看下面的例子:
获取数组元素
1 | //ES5 |
等同于:
1 | //ES6 |
调换值
1 | //ES5 |
等同于
1 | ES6 |
返回多个值的解构
1 | ES5 |
在第3行,也可以像这样用数组返回(并保存序列):
1 | return [left, right, top, bottom]; |
但之后调用时需要考虑返回数据的顺序。
1 | var left = data[0]; |
ES6 中调用时只会选择需要的数据(第 6 行):
1 | //ES6 |
注意:第3行用到了一些其它的 ES6 功能。可以将 { left: left } 简化为 { left }。看看和 ES5 的版本相比,现在多简洁啊~很酷不是吗?
参数匹配解构
1 | //ES5 |
等同于(但更简洁):
1 | //ES6 |
深度匹配
1 | ES5 |
等同于(但更简洁):
1 | ES6 |
也叫对象解构。
如你所见,解构很有用,并有助于形成好的编码风格。
最佳实践:
- 使用数组解构获取元素或调换变量,这样就不用创建临时引用了。
- 对于多返回值的情况,不要用数组解构,用对象解构。
类和对象
ES6 用“类”替代“构造函数”。
在 JavaScript 中,每个对象都有原型对象。所有 JavaScript 对象都从原型上继承方法和属性。
ES5 以面向对象编程(OOP)的方式创建对象,是利用构造函数实现的:
1 | ES5 |
ES6 提供了语法糖,可以用 class
、constructor
等新的关键字、更少的样板代码实现相同的效果。同样可以看到相比于constructor.prototype.speak = function ()
,用 speak()
定义方法更加清晰:
1 | ES6 |
可以看到两种方式(ES5/6)的结果和使用方式相同。
最佳实践:
- 最好使用 class 语法,避免直接操作 prototype。原因是这样代码更加简明易懂。
- 避免出现空的构造器。如果没有指明,类会有默认的构造器的。
继承
基于前面的 Animal 类,现在想要拓展 Animal,定义一个 Lion 类。
ES5 原型继承的方式有些复杂。
1 | ES5 |
在此我没有详细解读所有的代码,但是需要注意:
- 第 3 行,明确地用参数调用 Animal 构造器。
- 7-8 行,将 Lion 原型赋值为 Animal的原型。
- 11 行,从父类 Animal 调用了 speak 方法。
ES6 提供了新的关键字 extends 和 super。
1 | ES6 |
效果相同,但是相比于 ES5,ES6 代码更易读,完胜
最佳实践:
- 使用内置的 extends 实现继承。
原生 Promise
用 promise 替代回调地狱
1 | ES5 |
这个函数接收一个回调,在 done 后执行。我们想要先后执行两次,所以在回调中又一次调用了 printAfterTimeout。
如果需要 3 或 4 次回调,代码很快就一团糟了。那么用 promise 如何实现呢:
1 | ES6 |
promise 中可以用 then 在某个函数完成后执行新的代码,而不必再嵌套函数。
箭头函数
ES6 没有移除函数表达式,但是新增了箭头函数。
ES5 中,this 的指向有问题:
1 | ES5 |
在函数内,需要用临时变量指向 this 或者使用 bind 绑定。ES6 中可以使用箭头函数。
For…of
最开始用 for ,然后使用 forEach,而现在可以用 for…of:
1 | ES6 |
ES6 的 for…of 也可以用来迭代。
默认参数
之前需要检测变量是否定义了,而现在可以指定 default parameters 的值。或许你之前像下面这样写过?
1 | ES5 |
这可能是检测变量有值或指定默认值的惯用模式,但也存在一些问题:
- 第 8 行,我们传的值是 0, 0 但是得到的是 0, -1
- 第 9 行,传进去 false 但是得到的是 true。
如果默认参数是布尔值或将值设为 0,是没有用的。想知道为什么?我会在下面的 ES6 示例后说明。
有了 ES6,现在可以用更少的代码实现更好的效果了。
1 | ES6 |
注意第 5 行和第 6 行我们拿到了想要的值。ES5 的示例不好用,是因为先要检测 undefined 的值,而 false、 null、 undefined 和 0 都是假的值。我们可以加些代码:
1 | ES5 |
现在当检测 undefined 值时就符合我们的要求了。
展开操作符
之前使用 arguments,而现在可以用展开操作符。
ES5 中处理不定参数很麻烦:
1 | ES5 |
现在可以用展开操作符 … 达到相同的目的。
1 | ES6 |
之前用 apply(),现在可以方便地使用展开操作符 … 了:
提示:apply() 可以将数组转化为一系列参数。例如 Math.max() 接收一系列参数,但如果想应用于数组的话可以用 apply 帮助实现。
如上所述,apply 可以将数组当作参数序列进行传递:
1 | ES5 |
ES6 可以用展开操作符:
1 | ES6 |
之前用 concat 合并数组,现在也可以用展开操作符:
1 | ES5 |
ES6 可以用展开操作符展开嵌套的数组:
1 | ES6 |
总结
JavaScript 已经发生了许多改变。本文包含了大部分的核心功能,这些是每个 JavaScript 程序员都应该知道的。本文还涉及了一些最佳实践,可以使你的代码更加简明易懂。
如果你觉得还有一些其它的必会功能,请在下方留言,方便我更新本文。