现在ES6在很多项目中大量使用。最近我也花时间看了一下《Understanding ECMAScript6》的中文电子书。在这里总结了一些在实际开发中常用的新特性。
块级作用域
在ES6之前,JS只有一种变量声明方式——使用 var
关键字声明的变量。这种声明变量的方式,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内,则视为在全局作用域的顶部)。这就是所谓的变量提升 ( hoisting )。
ES6 引入了块级作用域,让变量的生命周期更加可控。
块级声明
块级声明也就是让所声明的变量在指定块的作用域外无法被访问。块级作用域(又被称为词法作用域)在如下情况被创建:
- 在一个函数内部
- 在一个代码块(由一对花括号包裹)内部
let声明
let声明会将变量的作用域限制在当前代码块中。由于 let 声明并不会被提升到当前代码块的顶部,因此你需要手动将 let 声明放置到顶部,以便让变量在整个代码块内部可用。例如:1 2 3 4 5 6 7 8 9 10 11
| function getValue(condition) { if (condition) { let value = "blue"; // 其他代码 return value; } else { // value 在此处不可用 return null; } // value 在此处不可用 }
|
注意事项
如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行 let 声明就会导致抛出错误。例如:
1 2 3
| var count = 30; let count = 40; // 语法错误
|
另一方面,在嵌套的作用域内使用 let 声明一个同名的新变量,则不会抛出错误,以下代码对此进行了演示:
1 2 3 4 5 6 7
| var count = 30; // 不会抛出错误 if (condition) { let count = 40; // 其他代码 }
|
常量声明
在 ES6 中里也可以使用 const
语法进行声明。使用 const
声明的变量会被认为是常量( constant )。const
用法与 let
类似,但有一个重要的区别,const
声明的变量的值在被设置完成后就不能再被改变。正因为如此,所有的 const 变量都需要在声明时进行初始化。
1 2 3 4 5 6 7 8
| const maxItems = 30; // 有效的常量 const name; // 语法错误:未进行初始化 const minItems = 5; minItems = 6; //抛出错误
|
模板字符串
模板字符串(template string)是增强版的字符串,使用反引号( ` )来包裹普通字符串。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| // 普通字符串 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表达式,比如:变量名、运算、函数调用,以及引用对象属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| //普通变量名 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 或更早的版本中,我们可能会使用下述模式来创建带有参数默认值的函数:
1 2 3 4 5 6 7
| function add(x, y) { x = x || 20; y = y || 30; return x + y; } console.log(add()); // 50
|
这种写法有一个缺点:如果参数x
或者y
赋值了,但是对应的布尔值为false
,则该赋值不起作用。
在这种情况下,更安全的替代方法是使用typeof
来检测参数的类型,示例如下:
1 2 3 4 5 6
| function add(x, y) { x = (typeof x !== "undefined") ? x : 20; y = (typeof y !== "undefined") ? x : 30; //... }
|
下面来看看ES6函数参数默认值的写法:
1 2 3
| function add(x = 20, y = 30) { return x + y; }
|
可以看到,ES6 的写法比 ES5 简洁许多,而且非常自然。
rest参数和扩展运算符
关于这两部分内容可以看这里
箭头函数
ES6 最有意思的一个新部分就是箭头函数( arrow function )。箭头函数使用“箭头”(=>)来定义。
先来看看箭头函数与传统的函数写法的区别:
1 2 3 4 5 6 7 8 9 10 11
| // 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语句返回。
1 2 3 4 5
| const foo = () => { const a = 20; const b = 30; return a + b; }
|
箭头函数的一个用处是简化回调函数。
1 2 3 4 5 6 7
| // 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属性。
对象字面量语法的扩展
属性和方法的简写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| // 属性的简写 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语言定义对象的属性,有两种方法。
1 2 3 4 5 6 7 8 9 10
| var person = {}, lastName = "last name"; // 方法一 person["first name"] = "Nicholas"; // 方法二 person[lastName] = "Zakas"; console.log(person["first name"]); // "Nicholas" console.log(person[lastName]); // "Zakas"
|
但是,如果使用字面量方式定义对象(使用大括号),在ES5中只能使用方法一定义属性。
1 2 3 4 5
| var person = { "first name": "Nicholas" }; console.log(person["first name"]); // "Nicholas"
|
在ES6中,需计算属性名是对象字面量语法的一部分,它用的也是方括号表示法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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)。
对象解构
对象解构语法在赋值语句的左侧使用了对象字面量,例如:
1 2 3 4 5 6 7 8 9
| 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
。例如:
1 2 3 4 5 6 7 8 9 10
| let node = { type: "Identifier", name: "foo" }; let { type, name, value } = node; console.log(type); // "Identifier" console.log(name); // "foo" console.log(value); // undefined
|
我们可以选择性地定义一个默认值,以便在指定属性不存在时使用该值。就像这样:
1 2 3 4 5 6 7 8 9 10
| 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允许我们在给本地变量赋值时使用一个不同的名称。就像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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"
|
数组结构
数组解构的语法看起来与对象解构非常相似,只是将对象字面量替换成了数组字面量。直接看例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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"
|
字符串结构
字符串也可以进行结构赋值。
1 2 3 4 5 6 7
| 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"
|
参数结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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 命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| // 导出数据 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 命令
1 2 3 4 5 6 7 8 9 10 11
| // 导入单个 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
关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // 导出默认值一共有三种写法 // 第一种 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 };
|
导入默认值得方式也有所不同
1 2 3
| import sum from "./example.js"; // 与前面不同的是,这里没有了大括号。 console.log(sum(1, 2)); // 3
|
后记
上面只是总结得只是一部分ES6的常用特性,其实还有Promise
,Class
等,因篇幅原因,这些可能留到以后再写。