1. 尝试ES6
这里有三种简单地方式用于ES6编程:
Web浏览器:使用Babel REPL,可以将ES6编译成ES5的平台,并且并不需要安装。
命令行:使用
babel-node
,可以执行ES6的Node.js版本(会在内部编译es5)。需要通过npm
安装。各种js引擎:根据ES6语法兼容表,找出被支持的ES6功能。
对于第一点和第二点,这有更多细节。
1.1 Babel REPL
Babel REPL主要有四个部分:
1、左上角包含es6源代码
2、左下角可以查看es6中的语法错误
3、右上角是es6被编译成es5的源代码
4、右下角是通过console.log()
的输出结果
1.2 babel-node
babel-node可以通过npm
安装:
$ npm install --global babel
跟node一样,可以命令开启REPL交互:
$ babel-node
一旦开启REPL,就可以去执行ES6代码:
> let arr = [1, 2, 3];
> arr.map(x => x * x)
[ 1, 4, 9 ]
注意:babel-node目前不支持多行输入
Babel官网上有管多关于Babel CLI的工具。
2. 从var到let/const
es6有两种新的方式来声明变量:
1、let
用于声明块级作用于变量
2、const
用于声明常量,其值不能被改变。
let
或const
可以用来替代var
声明变量,但是不能盲目使用,因为不同的作用域会改变代码的行为。例如:
var x = 3;
function func(randomize) {
if (randomize) {
var x = Math.random(); // (A) scope: whole function
return x;
}
return x; // accesses the x from line A
}
func(false); // undefined
func()
会意外地返回undefined
。你可以重写这部分代码,就知道为什么返回undefined
了:
var x = 3;
function func(randomize) {
var x;
if (randomize) {
x = Math.random();
return x;
}
return x;
}
func(false); // undefined
如果用let
代替之前的var
,就会得到不同的结果:
let x = 3;
function func(randomize) {
if (randomize) {
let x = Math.random();
return x;
}
return x;
}
func(false); // 3
因此,盲目地用let
或const
代替var
是有风险的,我的建议是:
1、只在新代码中使用let
或const
2、丢弃旧代码或仔细认证
更多信息:es6中的变量和作用域
3. 从IIFEs到代码块
在es5中,如果要维护本地变量,不得不使用IIFE:
(function () { // open IIFE
var tmp = ···;
···
}()); // close IIFE
console.log(tmp); // ReferenceError
而在es6中,则只需要代码块和let
声明:
{ // open block
let tmp = ···;
···
} // close block
console.log(tmp); // ReferenceError
更多信息:避免IIFEs
4. 从字符串拼接到模板常量
在es6中,对于字符串插值和多行字符串,Javscript能得到其字面值。
4.1 字符串插值
在es5中,是将结果放在字符串中进行拼接:
function printCoord(x, y) {
console.log('('+x+', '+y+')');
}
es6中,通过字符串字面模板,可以在字符串中插入变量值:
function printCoord(x, y) {
console.log(`(${x}, ${y})`);
}
4.2 多行字符串
模板字面量也能输出多行字符串。
例如,在es5中,输出多行字符串,得这样:
var HTML5_SKELETON =
'<!doctype html>\n' +
'<html>\n' +
'<head>\n' +
' <meta charset="UTF-8">\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n' +
'</body>\n' +
'</html>\n';
如果通过反斜线转义新行,代码看起来会舒服点(但还是要显示的添加新行):
var HTML5_SKELETON = '\
<!doctype html>\n\
<html>\n\
<head>\n\
<meta charset="UTF-8">\n\
<title></title>\n\
</head>\n\
<body>\n\
</body>\n\
</html>';
而es6得模板字面量能跨多行:
const HTML5_SKELETON = `
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
</body>
</html>`;
更多信息:字面量模板
5. 从函数表达式到箭头函数
在es5中,在函数表达式中必须小心使用this
。在示例中,我创建了辅助变量_this_
,以便于我能再line B处使用:
function UiComponent {
var _this = this; // (A)
var button = document.getElementById('myButton');
button.addEventListener('click', function () {
console.log('CLICK');
_this.handleClick(); // (B)
});
}
UiComponent.prototype.handleClick = function () {
···
};
在es6中,可以使用箭头函数,它不会影响this
:
class UiComponent {
constructor() {
let button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('CLICK');
this.handleClick(); // (A)
});
}
handleClick() {
···
}
}
对于只返回结果的短小回调函数,箭头函数是非常便利的。
在es5中,如下的回调是相对冗长的:
var arr = [1, 2, 3];
var squares = arr.map(function (x) { return x * x });
在es6中,箭头函数会更简洁:
let arr = [1, 2, 3];
let squares = arr.map(x => x * x);
在定义参数时,如果只有一个参数,括号是可以省略的。因而,(x) => x x and x => x x 都是允许的。
更多信息:箭头函数
6. 处理多个返回值
一些函数或方法能通过数组或对象返回多个值。在es5中,总是需要创建中间变量来访问返回值。es6中,可以使用解构。
6.1 通过数组返回多个值
exec()
返回类数组对象的捕获组。es5中,当要访问捕获组的值时,需要一个中间变量:
var matchObj =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
var year = matchObj[1];
var month = matchObj[2];
var day = matchObj[3];
es6中,解构使代码更简单:
let [, year, month, day] =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
空得逗号表示跳过数组的第一个元素。
6.2 通过对象返回多个值
Object.getOwnPropertyDescriptor()
会返回一个属性描述符对象。
es5中,仍需要一个中间变量来访问你感兴趣的对象属性值:
var obj = { foo: 123 };
var propDesc = Object.getOwnPropertyDescriptor(obj, 'foo');
var writable = propDesc.writable;
var configurable = propDesc.configurable;
console.log(writable, configurable); // true true
es6,可以使用解构:
let obj = { foo: 123 };
let {writable, configurable} =
Object.getOwnPropertyDescriptor(obj, 'foo');
console.log(writable, configurable); // true true
{writable, configurable}
的值如下:
{ writable: writable, configurable: configurable }
更多信息:解构
7. 从for到forEach再到for-of
先说es6,一般会这样迭代数组:
var arr = ['a', 'b', 'c'];
for (var i=0; i<arr.length; i++) {
var elem = arr[i];
console.log(elem);
}
也可以使用Array
的forEach
:
arr.forEach(function (elem) {
console.log(elem);
});
for
循环的优点是可以中断,forEach
的优点是简洁。
而es6的for-of
循环则结合了两者的优点:
let arr = ['a', 'b', 'c'];
for (let elem of arr) {
console.log(elem);
}
for-of
循环也能通过数组的entries
方法和解构返回数组的索引和对应的值:
for (let [index, elem] of arr.entries()) {
console.log(index+'. '+elem);
}
更多信息:for-of循环
8. 参数默认值
es5中,指定参数默认值得这样:
function foo(x, y) {
x = x || 0;
y = y || 0;
···
}
es6有个更简洁的语法:
function foo(x=0, y=0) {
···
}
es6语法还一个优点:参数默认值只能被undefined
触发,而在es5中,则会被任何z为false
的值触发。
更多信息:参数默认值
9. 命名参数
在Javascript中,命名参数的普遍方式是对象字面量:
selectEntries({ start: 0, end: -1 });
其等价实现:
function selectEntries(options) {
var start = options.start || 0;
var end = options.end || -1;
var step = options.step || 1;
···
}
es6中,解构是语法更简单:
function selectEntries({ start=0, end=-1, step=1 }) {
···
}
9.1 使参数可选
es5中使参数可选的做法是这样的:
function selectEntries(options) {
options = options || {}; // (A)
var start = options.start || 0;
var end = options.end || -1;
var step = options.step || 1;
···
}
es6中,可以指定{}
为参数的默认值:
function selectEntries({ start=0, end=-1, step=1 } = {}) {
···
}
更多信息::模拟命名参数
10. 从arguments到参数扩展
es5中,若想让方法或函数接受任意个数的参数,就必须使用指定的arguments
变量:
function logAllArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
es6中,可以使用...
操作达到同样地效果:
function logAllArguments(...args) {
for (let arg of args) {
console.log(arg);
}
}
还有更nice的语法:
function format(pattern, ...args) {
···
}
而es5中的处理则相对笨拙:
function format() {
var pattern = arguments[0];
var args = arguments.slice(1);
···
}
更多信息:Rest parameters
11. 从apply到散布操作符(...)
es5中,apply()
会将数组转会成参数,es6中使用散布操作符达到同样地目的。
11.1 Math.max()
es5-->apply()
:
> Math.max.apply(null, [-1, 5, 11, 3])
11
es6-->spread operator:
> Math.max(...[-1, 5, 11, 3])
11
11.2 Array.prototype.push()
es5-->apply()
:
var arr1 = ['a', 'b'];
var arr2 = ['c', 'd'];
arr1.push.apply(arr1, arr2);
// arr1 is now ['a', 'b', 'c', 'd']
es6-->spread operator:
let arr1 = ['a', 'b'];
let arr2 = ['c', 'd'];
arr1.push(...arr2);
// arr1 is now ['a', 'b', 'c', 'd']
更多信息:spread operator
12. 从concat()到(...)
ES5 – concat():
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];
console.log(arr1.concat(arr2, arr3));
// [ 'a', 'b', 'c', 'd', 'e' ]
ES6 – spread operator:
let arr1 = ['a', 'b'];
let arr2 = ['c'];
let arr3 = ['d', 'e'];
console.log([...arr1, ...arr2, ...arr3]);
// [ 'a', 'b', 'c', 'd', 'e' ]
更多信息:spread operator
13. 从构造器到类
对于构造器语法,es6的类则更简便。
13.1 基本类
es5中实现一个基本类如下:
function Person(name) {
this.name = name;
}
Person.prototype.describe = function () {
return 'Person called '+this.name;
};
es6中,类提供了更简洁的语法:
class Person {
constructor(name) {
this.name = name;
}
describe() {
return 'Person called '+this.name;
}
}
13.2 派生类
es5实现了类的派生,下面是实现派生类的一种规范方法:
function Employee(name, title) {
Person.call(this, name); // super(name)
this.title = title;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.describe = function () {
return Person.prototype.describe.call(this) // super.describe()
+ ' (' + this.title + ')';
};
es6内置了类派生语法,要借助extends
关键字:
class Employee extends Person {
constructor(name, title) {
super(name);
this.title = title;
}
describe() {
return super.describe() + ' (' + this.title + ')';
}
}
更多信息:类
14. 从自定义error到Error派生
跟上面有点类似。es5中自定义error:
function MyError() {
// Use Error as a function
var superInstance = Error.apply(null, arguments);
copyOwnPropertiesFrom(this, superInstance);
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
es6通过派生实现:
class MyError extends Error {
}
更多信心:Subclassing built-in constructors
15. 从对象字面量的函数表达式和方法定义
语法上的差别。es5实现:
var obj = {
foo: function () {
···
},
bar: function () {
this.foo();
}, // trailing comma is legal in ES5
}
es6:
let obj = {
foo() {
···
},
bar() {
this.foo();
},
}
更多信息:方法定义
16. 从对象到图
es5中利用对象来实现图的数据结构,需要将对象的prototype
指向null
,并保证__proto__
上没有对应的键。
var dict = Object.create(null);
function countWords(word) {
var escapedWord = escapeKey(word);
if (escapedWord in dict) {
dict[escapedWord]++;
} else {
dict[escapedWord] = 1;
}
}
function escapeKey(key) {
if (key.indexOf('__proto__') === 0) {
return key+'%';
} else {
return key;
}
}
es6则内置了Map数据结构;
let map = new Map();
function countWords(word) {
let count = map.get(word) || 0;
map.set(word, count + 1);
}
更多信息:Maps and Sets
17. 从CommonJS模块到es6 模块
es5中,模块系统是基于AMD或CommocJS语法。es6内置了模块语法,但并没有得到Javascript引擎良好支持。
17.1 多个导出
在CommonJS中,可以这样实现:
//------ lib.js ------
var sqrt = Math.sqrt;
function square(x) {
return x * x;
}
function diag(x, y) {
return sqrt(square(x) + square(y));
}
module.exports = {
sqrt: sqrt,
square: square,
diag: diag,
};
//------ main1.js ------
var square = require('lib').square;
var diag = require('lib').diag;
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
es6的语法是酱紫的:
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//------ main1.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
或者作为一个对象导入:
//------ main2.js ------
import * as lib from 'lib'; // (A)
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
17.2 单个导出
Node.js继承了CommonJS的语法,能从模块导出单个值:
//------ myFunc.js ------
module.exports = function () { ··· };
//------ main1.js ------
var myFunc = require('myFunc');
myFunc();
es6通过export default
实现:
//------ myFunc.js ------
export default function () { ··· } // no semicolon!
//------ main1.js ------
import myFunc from 'myFunc';
myFunc();
更多信息:Modules
相关文章:ECMAScript 6新特性介绍
译文出处:Getting started with ECMAScript 6