现在ES6在很多项目中大量使用。最近我也花时间看了一下《Understanding ECMAScript6》的中文电子书。在这里总结了一些在实际开发中常用的新特性。
块级作用域
在ES6之前,JS只有一种变量声明方式——使用 var
关键字声明的变量。这种声明变量的方式,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内,则视为在全局作用域的顶部)。这就是所谓的变量提升 ( hoisting )。
ES6 引入了块级作用域,让变量的生命周期更加可控。
块级声明
块级声明也就是让所声明的变量在指定块的作用域外无法被访问。块级作用域(又被称为词法作用域)在如下情况被创建:
在一个函数内部
在一个代码块(由一对花括号包裹)内部
let声明
let声明会将变量的作用域限制在当前代码块中。由于 let 声明并不会被提升到当前代码块的顶部,因此你需要手动将 let 声明放置到顶部,以便让变量在整个代码块内部可用。例如:
function getValue(condition) {
if (condition) {
let value = "blue";
// 其他代码
return value;
} else {
// value 在此处不可用
return null;
}
// value 在此处不可用
}
注意事项
如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行 let 声明就会导致抛出错误。例如:
var count = 30;
let count = 40; // 语法错误
另一方面,在嵌套的作用域内使用 let 声明一个同名的新变量,则不会抛出错误,以下代码对此进行了演示:
var count = 30;
// 不会抛出错误
if (condition) {
let count = 40;
// 其他代码
}
常量声明
在 ES6 中里也可以使用 const
语法进行声明。使用 const
声明的变量会被认为是常量( constant )。const
用法与 let
类似,但有一个重要的区别,const
声明的变量的值在被设置完成后就不能再被改变。正因为如此,所有的 const 变量都需要在声明时进行初始化。
const maxItems = 30; // 有效的常量
const name; // 语法错误:未进行初始化
const minItems = 5;
minItems = 6; //抛出错误
模板字符串
模板字符串(template string)是增强版的字符串,使用反引号( ` )来包裹普通字符串。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
// 普通字符串
let message = `Hello world!`;
console.log(message); // Hello world!
//在字符串中包含反引号,只需使用反斜杠( \ )转义即可
let message = `\`Hello\` world!`;
console.log(message); // `Hello` world!
// 多行字符串(只需在想要的位置包含换行即可)
let message = `Multiline
string`;
console.log(message); // "Multiline
// string"
console.log(message.length); // 16
//反引号之内的所有空白符都是字符串的一部分,因此需要留意缩进。
let message = `Multiline
string`;
console.log(message); // "Multiline
// string"
console.log(message.length); // 31
// 字符串中嵌入变量
var name = "Bob", time = "today";
console.log(`Hello ${name}, how are you ${time}?`) // Hello Bob, how are you today?
替换位
模板字符串替换位的标识是 ${}
。大括号内部可以放入任意的JavaScript表达式,比如:变量名、运算、函数调用,以及引用对象属性。
//普通变量名
var name = "Nicholas";
var message = `Hello, ${name}.`;
console.log(message); // Hello, Nicholas.
//计算
var x = 1;
var y = 2;
console.log(`${x} + ${y} = ${x + y}`) // 1 + 2 = 3
console.log(`${x} + ${y * 2} = ${x + y * 2}`) // 1 + 4 = 5
//函数调用
function fn() {
return "Hello World";
}
console.log(`foo ${fn()} bar`) // foo Hello World bar
//对象属性
var obj = {x: 1, y: 2};
console.log(`${obj.x + obj.y}`)
函数
函数参数的默认值
在 ES5 或更早的版本中,我们可能会使用下述模式来创建带有参数默认值的函数:
function add(x, y) {
x = x || 20;
y = y || 30;
return x + y;
}
console.log(add()); // 50
这种写法有一个缺点:如果参数x
或者y
赋值了,但是对应的布尔值为false
,则该赋值不起作用。
在这种情况下,更安全的替代方法是使用typeof
来检测参数的类型,示例如下:
function add(x, y) {
x = (typeof x !== "undefined") ? x : 20;
y = (typeof y !== "undefined") ? x : 30;
//...
}
下面来看看ES6函数参数默认值的写法:
function add(x = 20, y = 30) {
return x + y;
}
可以看到,ES6 的写法比 ES5 简洁许多,而且非常自然。
rest参数和扩展运算符
关于这两部分内容可以看这里
箭头函数
ES6 最有意思的一个新部分就是箭头函数( arrow function )。箭头函数使用“箭头”(=>)来定义。
先来看看箭头函数与传统的函数写法的区别:
// ES6
var f = () => 5;
// ES5
var f = function () { return 5 };
// ES6
var sum = (num1, num2) => num1 + num2;
// ES5
var sum = function(num1, num2) {
return num1 + num2;
};
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
const foo = () => {
const a = 20;
const b = 30;
return a + b;
}
箭头函数的一个用处是简化回调函数。
// ES5
[1,2,3].map(function (x) {
return x * x;
});
// ES6
[1,2,3].map(x => x * x);
箭头函数可以替换函数表达式,但是不能替换函数声明
在使用箭头函数时要注意如下几点:
不能更改
this
:this的值在函数内部不能被修改,在函数的整个生命周期内其值会
保持不变。没有arguments对象:既然箭头函数没有arguments绑定,你必须依赖于具名参数或
剩余参数来访问函数的参数。不能被使用
new
调用: 箭头函数没有[[Construct]]方法,因此不能被用为构造函
数,使用new
调用箭头函数会抛出错误。没有原型: 既然不能对箭头函数使用
new
,那么它也不需要原型,也就是没有
prototype属性。
对象字面量语法的扩展
属性和方法的简写
// 属性的简写
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
// 方法的简写
var o = {
method() {
return "Hello!";
}
};
// 等同于
var o = {
method: function() {
return "Hello!";
}
};
需计算属性名
JavaScript语言定义对象的属性,有两种方法。
var person = {},
lastName = "last name";
// 方法一
person["first name"] = "Nicholas";
// 方法二
person[lastName] = "Zakas";
console.log(person["first name"]); // "Nicholas"
console.log(person[lastName]); // "Zakas"
但是,如果使用字面量方式定义对象(使用大括号),在ES5中只能使用方法一定义属性。
var person = {
"first name": "Nicholas"
};
console.log(person["first name"]); // "Nicholas"
在ES6中,需计算属性名是对象字面量语法的一部分,它用的也是方括号表示法。
var lastName = "last name";
var person = {
"first name": "Nicholas",
[lastName]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person[lastName]); // "Zakas"
// 方括号内也可以是表达式
var suffix = " name";
var person = {
["first" + suffix]: "Nicholas",
["last" + suffix]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person["last name"]); // "Zakas"
// 也可以用来表示方法名
var obj = {
['h' + 'ello']() {
console.log('hi');
}
};
obj.hello() // hi
解构赋值
解构赋值也是ES6中非常常用的一个特性。
按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
对象解构
对象解构语法在赋值语句的左侧使用了对象字面量,例如:
let node = {
type: "Identifier",
name: "foo"
};
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
代码中,node.type
的值被存储到type
本地变量中,node.name
的值则存储到name
变量中。
当使用解构赋值语句时,如果所指定的本地变量在对象中没有找到同名属性,那么该变量会被赋值为undefined
。例如:
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined
我们可以选择性地定义一个默认值,以便在指定属性不存在时使用该值。就像这样:
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value = true } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // true
上面的示例都使用了对象中的属性名作为本地变量的名称。但ES6允许我们在给本地变量赋值时使用一个不同的名称。就像这样:
let node = {
type: "Identifier",
name: "foo"
};
let { type: localType, name: localName } = node;
console.log(localType); // "Identifier"
console.log(localName); // "foo"
// 我们也可以给变量别名加默认值
let node = {
type: "Identifier"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName); // "bar"
数组结构
数组解构的语法看起来与对象解构非常相似,只是将对象字面量替换成了数组字面量。直接看例子:
let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
// 也可以在解构模式中忽略一些项
let colors = [ "red", "green", "blue" ];
let [ , , thirdColor ] = colors;
console.log(thirdColor); // "blue"
// 也可以添加默认值
let colors = [ "red" ];
let [ firstColor, secondColor = "green" ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
字符串结构
字符串也可以进行结构赋值。
const [a, b, c, d, e] = 'hello';
console.log(a) // "h"
console.log(b) // "e"
console.log(c) // "l"
console.log(d) // "l"
console.log(e) // "o"
参数结构
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
// 参数解构也可以有默认
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
模块
模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
export 命令
// 导出数据
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// 导出函数
export function sum(num1, num2) {
return num1 + num1;
}
// 导出类
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
// export还可以像下面这样写,放在大括号内统一导出
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
export {color, name, magicNumber};
// 重命名导出
function sum(num1, num2) {
return num1 + num1;
}
export {sum as add} // 这里sum函数被作为add导出
import 命令
// 导入单个
import { color } from "./example.js";
// 导入多个
import { color, name, sum } from "./example.js";
// 重命名导入
import { color as redColor } from "./example.js";
// 整体导入
import * as example from "./example.js";
导出/导入默认值
导出默认值要使用default
关键字
// 导出默认值一共有三种写法
// 第一种
export default function(num1, num2) {
return num1 + num2;
}
// 第二种
function sum(num1, num2) {
return num1 + num2;
}
export default sum;
// 第三种
function sum(num1, num2) {
return num1 + num2;
}
export { sum as default };
导入默认值得方式也有所不同
import sum from "./example.js"; // 与前面不同的是,这里没有了大括号。
console.log(sum(1, 2)); // 3
后记
上面只是总结得只是一部分ES6的常用特性,其实还有Promise
,Class
等,因篇幅原因,这些可能留到以后再写。