# ECMAScript 6
# 简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版本以后的 JavaScript 的下一代标准,涵盖了 ES2015,ES2016,ES2017 等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准.
# 新特性
以下是 ES6 的一些主要新特性:
特性 | 描述 |
---|---|
let 和 const 命令 | 块级作用域,取代了 var 命令。 |
解构赋值 | 方便地从数组或对象中取出数据赋值给变量。 |
箭头函数 | 简化了函数的书写,同时还可以避免 this 指向问题。 |
模板字符串 | 可以使用反引号 `` 来创建字符串,支持多行字符串、插值表达式等。 |
对象属性简写 | 对象属性名可以直接用变量名,而不用再写成 “变量名:变量值” 的形式。 |
对象方法简写 | 对象方法可以直接写成 “方法名 (){}” 的形式。 |
class 关键字 | 引入了 class 关键字,使得 JavaScript 有了类的概念。 |
模块化支持 | 引入了 import 和 export 关键字,使得 JavaScript 有了模块化的支持。 |
# 兼容性
各大浏览器对 ES6 的支持可以查看 kangax.github.io/compat-table/es6/ 。随着时间的推移,支持度已经越来越高了,超过 90% 的 ES6 语法特性都实现了。
# Babel
Babel 是一个 JavaScript 编译器,主要用于在当前和旧的浏览器或环境中,将 ES6 代码转为 ES5 代码:https://babeljs.io/
# 参考文献
# 变量与常量
# let
let
用来声明标变量,它的用法类似于 var
,但是所声明的变量,只在 let
命令所在的代码块内有效。
- 块级作用域
var a = 1; | |
if (a > 0) { | |
var b = 2; | |
let c = 3; | |
} | |
console.log(b); // 2 | |
console.log(c); // ReferenceError: c is not defined |
- 不存在变量提升
console.log(c); // ReferenceError: Cannot access 'c' before initialization | |
let c = 3; |
- 不允许重复声明
let c = 3; | |
let c = 4; // SyntaxError: Identifier 'c' has already been declared |
# const
声明一个只读的 常量
const PI = 3.1415; | |
PI = 3; // TypeError:Assignment to constant variable. |
一旦声明,变量就必须被初始化,不能留到以后赋值。
const PI; // SyntaxError: Missing initializer in const declaration |
声明的数组、对象本身是可变的,依然可以为其添加新属性,不能改变的是变量的引用地址。
const person = { | |
name:'张三' | |
} | |
person.age = 18; | |
console.log(person); // {name: ' 张三 ', age: 18} |
# let 和 var 的区别
var
是函数作用域,而let
是块作用域。
在函数中声明了 var
,整个函数内都是有效的,比如说在 for
循环内定义的一个 var
变量,实际上在 for
循环以外也是可以访问的。而 let
由于是块作用域,所以在 for
循环内定义的变量,在其外面是不可被访问的,且不会污染全局变量,避免变量名冲突和提高代码可读性。
let
不能在定义之前访问该变量,但是var
可以。
let
必须先声明,再使用。而 var
先使用后声明也行,只不过直接使用但没有定义的时候,其值是 undefined
。 var
有一个变量提升的过程,当整个函数作用域被创建的时候,实际上 var
定义的变量都会被创建,并且如果此时没有初始化的话,则默认为初始化一个 undefined
。
# 模板字符串
ES6 的模板字符串是一种增强版的字符串,使用反引号(`)(Tab 键上方的按键)。
- 可以方便地嵌入变量,不需要使用字符串拼接。
// ES5 | |
var name = '张三'; | |
console.log('你好,' + name + '!'); | |
// ES6 | |
let name = '张三'; | |
console.log(`你好,${name}!`); |
- 可以方便地定义多行字符串,不需要使用转义字符。
// ES5 | |
var str = '这是一段\n多行\n字符串。'; | |
console.log(str); | |
// ES6 | |
let str = `这是一段 | |
多行 | |
字符串。`; | |
console.log(str); |
- 可以方便地嵌入表达式,不需要使用字符串拼接和转义字符。
// ES5 | |
var a = 1, b = 2; | |
console.log('a + b = ' + (a + b)); | |
// ES6 | |
let a = 1, b = 2; | |
console.log(`a + b = ${a + b}`); |
- 可以方便地格式化字符串,不需要使用字符串拼接和转义字符。
// ES5 | |
var name = '张三', age = 18; | |
console.log('姓名:' + name + ',年龄:' + age); | |
// ES6 | |
let name = '张三', age = 18; | |
console.log(`姓名:${name},年龄:${age}`); |
# 函数的扩展
# 箭头函数
箭头函数是一种更简洁的函数定义方式,可以省略 function
关键字和 return
关键字,同时 this
指向的是定义时的那个对象,如果有对象嵌套的情况,则 this
绑定到最近的一层对象上。
# 基础语法
// ES5 | |
var arr = [1, 2, 3]; | |
var newArr = arr.map(function(item) { | |
return item * 2; | |
}); | |
console.log(newArr); | |
// ES6 | |
let arr = [1, 2, 3]; | |
let newArr = arr.map(item => item * 2); | |
console.log(newArr); // [ 2 , 4 , 6 ] |
当函数只有一个参数时,可以省略掉括号;当函数中有且仅有一个表达式时,可以省略大括号。
// ES6 | |
let sayHello = name => console.log(`你好,${name}!`); | |
sayHello('张三'); // 你好,张三! | |
let sum = (a, b) => a + b; | |
console.log(sum(1, 2)); // 3 |
# 注意事项
- 箭头函数没有自己的
this
对象,它的this
指向是继承自外部作用域的this
。
const obj = { | |
name: '张三', | |
sayName: function() { | |
setTimeout(() => { | |
console.log(this.name); | |
}, 1000); | |
} | |
}; | |
obj.sayName(); // 输出 "张三",因为箭头函数中的 this 指向是继承自外部作用域的 this,即 obj 对象。 |
- 箭头函数不能用作构造函数,也就是说,不能使用
new
命令,否则会抛出一个错误。
const Person = (name) => { | |
this.name = name; | |
}; | |
const person = new Person('张三'); // 抛出错误:Person is not a constructor |
- 箭头函数没有
arguments
对象(包含传递给函数的每个参数),但是可以用rest
参数(用于获取函数的多余参数)代替。
const sum = (...args) => { | |
let result = 0; | |
for (let arg of args) { | |
result += arg; | |
} | |
return result; | |
}; | |
console.log(sum(1, 2, 3)); // 6 |
- 箭头函数不能用作
generator
函数。generator
函数是一种异步编程解决方案,语法行为与传统函数完全不同。generator
函数可以通过yield
关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案
const generator = function* () { | |
yield 1; | |
}; | |
const arrowGenerator = () => { | |
yield 1; // 抛出错误:Unexpected identifier | |
}; |
# 默认参数
默认参数可以为函数的参数设置默认值,如果调用时没有传递参数,则使用默认值。
// ES5 | |
function sayHello(name) { | |
name = name || '张三'; | |
console.log('你好,' + name + '!'); | |
} | |
sayHello(); | |
// ES6 | |
function sayHello(name = '张三') { | |
console.log(`你好,${name}!`); | |
} | |
sayHello(); |
# Rest 参数
Rest 参数(剩余参数)可以将多个参数合并成一个数组,方便处理。
# 基础语法
// ES5 | |
function sum() { | |
var args = Array.prototype.slice.call(arguments); | |
var result = 0; | |
args.forEach(function(item) { | |
result += item; | |
}); | |
return result; | |
} | |
console.log(sum(1, 2, 3)); | |
// ES6 | |
function sum(...args) { | |
let result = 0; | |
args.forEach(item => { | |
result += item; | |
}); | |
return result; | |
} | |
console.log(sum(1, 2, 3)); // 6 |
# 注意事项
- 一个函数只能有一个 Rest 参数,如果有 Rest 参数,那么它必须是最后一个参数。
function f(a, b, ...c) { | |
// ... | |
} |
- Rest 参数只包含那些没有对应形参的实参
function f(a, b, ...c) { | |
console.log(c.length); | |
} | |
fun1(); // 0 | |
fun1(5, 6, 7); // 1 | |
fun1(5, 6, 7, 8, 9); // 3 |
# 数组的扩展
# 扩展运算符
可以将一个数组或对象展开分割成多个参数,方便传递给函数。
# 基础语法
// ES5 | |
var arr1 = [1, 2, 3]; | |
var arr2 = [4, 5, 6]; | |
var newArr = arr1.concat(arr2); | |
console.log(newArr); | |
// ES6 | |
let arr1 = [1, 2, 3]; | |
let arr2 = [4, 5, 6]; | |
let newArr = [...arr1, ...arr2]; | |
console.log(newArr); // [ 1 , 2 , 3 , 4 , 5 , 6 ] |
# 注意事项
- 在数组或函数参数中使用展开语法时,扩展运算符只能用于可迭代对象
可迭代对象:数组、字符串、Set、Map、arguments 等。如果使用扩展运算符展开一个对象,该对象必须实现了迭代器接口。
// 数组 | |
const arr = [1, 2, 3]; | |
console.log(...arr); // 1 2 3 | |
// 字符串 | |
const str = 'hello'; | |
console.log(...str); // h e l l o | |
// Set | |
const set = new Set([1, 2, 3]); | |
console.log(...set); // 1 2 3 | |
// Map | |
const map = new Map([['a', 1], ['b', 2], ['c', 3]]); | |
console.log(...map); // [ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ] | |
// arguments | |
function test() { | |
console.log(...arguments); | |
} | |
test(1, 2, 3); // 1 2 3 |
- 扩展运算符只能处于最后一个
const arr = [1, 2]; | |
const [a, ...rest] = [...arr]; // 正确示例:扩展运算符只出现在最后一个位置 | |
const [...rest2, a] = [...arr]; // 错误示例:扩展运算符不在最后一个位置 |
# Array.from()
从一个类似数组或可迭代对象中创建一个新的,浅拷贝的数组实例
# 基础语法
// 将一个字符串转为真正的数组 | |
console.log(Array.from('hello')); // ['h', 'e', 'l', 'l', 'o'] | |
// 将一个可遍历对象转为真正的数组 | |
let set = new Set(['a', 'b']); | |
console.log(Array.from(set)); // ['a', 'b'] |
Array.from () 方法的第二个参数可以接受一个函数,用来对每个元素进行处理
const obj = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; | |
const arr = Array.from(obj, (x) => x.toUpperCase()); | |
console.log(arr); // ["A", "B", "C"] |
# Array.of()
Array.of () 方法用于将一组值,转换为数组,可以用来替代 Array () 或者 new Array (),并且不存在由于参数个数的不同而导致的重载,它的行为非常统一。
# 基础语法
console.log(Array.of(1, 2, 3)); // [1, 2, 3] |
# 注意事项
Array 方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时, Array () 才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array() // [] | |
Array(3) // [, , ,] | |
Array(3, 11, 8) // [3, 11, 8] |
# find () 和 findIndex ()
Array.prototype.find () 和 Array.prototype.findIndex () 方法都是用于查找数组中符合条件的元素。
# 基础语法
let arr = [1, 2, 3]; | |
console.log(arr.find(x => x > 1)); // 2 | |
console.log(arr.findIndex(x => x > 1)); // 1 |
# 注意事项
find () 方法返回数组中第一个满足条件的元素,如果没有满足条件的元素,则返回 undefined;
const arr = [1, 2, 3, 4, 5]; | |
const found = arr.find(element => element > 5); | |
console.log(found); // undefined |
findIndex () 方法返回数组中第一个满足条件的元素的索引,如果没有满足条件的元素,则返回 -1。
const arr = [1, 2, 3, 4, 5]; | |
const found = arr.findIndex(element => element > 5); | |
console.log(found); // -1 |
# fill()
fill () 方法用于将一个固定值替换数组的元素。
# 基础语法
let arr = new Array(3).fill(0); | |
console.log(arr); // [0, 0, 0] |
# 注意事项
fill () 方法有三个参数:value,start 和 end。value 参数是必需的,表示要填充的值;start 参数是可选的,表示开始填充的位置,默认值为 0;end 参数也是可选的,表示停止填充的位置,默认值为数组的长度。
const arr = [1, 2, 3, 4, 5]; | |
console.log(arr.fill(0, 2, 4)); // [1, 2, 0, 0, 5] |
# copyWithin()
数组实例的 copyWithin()
方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length) |
- target(必需):表示从哪个位置开始替换元素。如果为负值,表示倒数。
- start(可选):表示从哪个位置开始复制元素,默认为 0。如果为负值,表示从末尾开始计算。
- end(可选):表示复制到哪个位置结束,默认等于数组长度。如果为负值,表示从末尾开始计算。
# 基础语法
复制倒数两个元素,并将它们复制到前面开头的位置
let arr = [1, 2, 3, 4, 5]; | |
console.log(arr.copyWithin(0, -2)); // [4, 5, 3, 4, 5] |
二个位置开始复制两个元素,并将它们复制到第四个位置
let arr = [1, 2, 3, 4, 5]; | |
arr.copyWithin(3, 1, 3); | |
console.log(arr); // [1, 2, 3, 2, 3] |
# 对象扩的展
# 属性初始化器简写
# 基础语法
这个特性允许在对象字面量中省略冗长的属性赋值语句。
let a = 1; | |
let b = 2; | |
let obj = {a, b}; | |
console.log(obj); // {a: 1, b: 2} |
除了属性简写,方法也可以简写。
let obj = { | |
foo() { | |
console.log('Hello, world!'); | |
} | |
}; | |
obj.foo(); // Hello, world! | |
// 等同于 | |
let obj = { | |
foo: function() { | |
console.log('Hello, world!'); | |
} | |
}; | |
obj.foo(); // Hello, world! |
# 注意事项
简写的对象方法不能用作构造函数,会报错。
let obj = { | |
foo() { | |
console.log('Hello, world!'); | |
} | |
}; | |
let bar = new obj.foo(); // TypeError: obj.foo is not a constructor |
# 属性名表达式
# 基础语法
这个特性允许使用表达式作为对象的属性名。
let obj = { | |
['prop_' + (() => 42)()]: 42 | |
}; | |
console.log(obj); // {prop_42: 42} |
let propKey = 'foo'; | |
let obj = { | |
[propKey]: true, | |
['a' + 'bc']: 123 | |
}; | |
console.log(obj); // {foo: true, abc: 123} |
# 注意事项
属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串 [object Object] ,这一点要特别小心。
const keyA = {a: 1}; | |
const keyB = {b: 2}; | |
const myObject = { | |
[keyA]: 'valueA', | |
[keyB]: 'valueB' | |
}; | |
myObject // Object {[object Object]: "valueB"} |
# 解构赋值
解构赋值是一种在 ES6 中引入的语法,它允许我们从数组或对象中提取值并将其赋给变量。
# 数组的解构
const name = ['张三', '李四', '王五', '赵六']; | |
let [zhang, li, wang, zhao] = name; // 数组的解构赋值 | |
console.log(zhang); // 张三 | |
console.log(li); // 李四 | |
console.log(wang); // 王五 | |
console.log(zhao); // 赵六 |
# 对象的解构
const obj = { | |
name: '张三', | |
age: 18, | |
sex: function(){ | |
console.log('男'); | |
} | |
}; | |
let {name,age,sex} = obj; // 对象解构赋值 | |
console.log(name); // 张三 | |
console.log(age); //18 | |
sex(); // 男 |
# Object.assign()
这个方法用于对象的合并,将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回目标对象。
let obj1 = {a: 1}; | |
let obj2 = {b: 2}; | |
let obj3 = Object.assign({}, obj1, obj2); | |
console.log(obj3); // {a: 1, b: 2} |
# Object.is()
这个方法用于比较两个值是否相同。与 === 运算符不同之处在于,+0 和 -0 被视为相同的值,NaN 等于自身。
console.log(Object.is(0, -0)); // false | |
console.log(Object.is(NaN, NaN)); // true |
# Object.getOwnPropertySymbols()
这个方法返回一个给定对象自身的所有 Symbol 属性的数组。
const sym = Symbol('foo'); | |
const obj = {[sym]: 1}; | |
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(foo)] |
# Object.setPrototypeOf()
这个方法设置一个指定的对象的原型 (即,内部 [[Prototype]] 属性)到另一个对象或 null。
let obj1 = {}; | |
let obj2 = {x: 10}; | |
Object.setPrototypeOf(obj1, obj2); | |
console.log(obj1.x); // 10 |
# Symbol 类型
Symbol 是 ES6 引入的一种新的原始数据类型,表示独一无二的值。它是 JavaScript 语言的第 7 种数据类型,前 6 种分别是 Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)和对象(Object)
Symbol 值通过 Symbol 函数生成。也就是说,对象的属性名现在可以有两种类型:一种是原来就有的字符串,另一种就是新增的 Symbol 类型。只要属性名属于 Symbol 类型,就是独一无二的,可以保证不会与其他属性名产生冲突
# 基础语法
const obj = {}; | |
const a = Symbol('a'); | |
const b = Symbol('b'); | |
obj[a] = 'Hello'; | |
obj[b] = 'World'; | |
console.log(obj[a]); // 输出 "Hello" | |
console.log(obj[b]); // 输出 "World" |
# 注意事项
- Symbol 是基本数据类型,不是对象,所以不能使用 new 关键字。
// 错误的写法 | |
const sym = new Symbol(); // TypeError: Symbol is not a constructor | |
// 正确的写法 | |
const sym = Symbol(); |
- Symbol 函数的参数只是一个描述,不会影响到 Symbol 的值。
const sym1 = Symbol('foo'); | |
const sym2 = Symbol('foo'); | |
console.log(sym1 === sym2); // 输出 "false" |
- Symbol 类型的值可以作为对象属性名,但是不能用点运算符访问。
const obj = {}; | |
const sym = Symbol('a'); | |
obj[sym] = 'Hello'; | |
console.log(obj[sym]); // 输出 "Hello" | |
console.log(obj.sym); // 输出 "undefined" |
- Symbol 类型的值可以用于定义类的私有属性。
class MyClass { | |
#prop = 10; | |
getProp() { | |
return this.#prop; | |
} | |
} | |
const obj = new MyClass(); | |
console.log(obj.getProp()); // 输出 "10" | |
console.log(obj.#prop); // SyntaxError: Private field '#prop' must be declared in an enclosing class |
# Set 与 Map 数据结构
# Set
Set 是一种类似于数组的数据结构,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,用来生成 Set 数据结构。
- Set 内部判断两个值是否不同,使用的算法叫做 “Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是 NaN 等于自身,而精确相等运算符认为 NaN 不等于自身。
- Set 实例的属性和方法:
- Set.prototype.constructor:构造函数,默认就是 Set 函数。
- Set.prototype.size:返回 Set 实例的成员总数。
- Set.prototype.add (value):添加某个值,返回 Set 结构本身。
- Set.prototype.delete (value):删除某个值,返回一个布尔值,表示删除是否成功。
- Set.prototype.has (value):返回一个布尔值,表示该值是否为 Set 的成员。
- Set.prototype.clear ():清除所有成员,没有返回值。
- Array.from 方法可以将 Set 结构转为数组。
- 遍历操作:
- keys ():返回键名的遍历器
- values ():返回键值的遍历器
- entries ():返回键值对的遍历器
- forEach ():使用回调函数遍历每个成员
# 基础语法
const s = new Set(); | |
s.add(1).add(2).add(3); | |
console.log(s.size); // 输出 "3" | |
console.log(s.has(2)); // 输出 "true" | |
s.delete(2); | |
console.log(s.has(2)); // 输出 "false" |
# 使用案例
可以使用 ES6 中的 Set 对象来去重。Set 对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set 中的元素只会出现一次,即 Set 中的元素是唯一的。可以使用 Array.from () 方法将 Set 转换为数组,从而实现去重。
let arr = [1, 2, 3, 4, 5, 5, 4, 3, 2, 1]; | |
let uniqueArr = Array.from(new Set(arr)); | |
console.log(uniqueArr); // [1, 2, 3, 4, 5] |
# Map
Map 是一种类似于对象的数据结构,也是键值对的集合,但是键不限于字符串,各种类型的值(包括对象)都可以当作键。Map 本身也是一个构造函数,用来生成 Map 数据结构。
- Map 对象是一组键值对的结构,具有极快的查找速度。
- Map 本身是一个构造函数,用来生成 Map 数据结构。Map 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
- Map.prototype.size 属性返回 Map 实例的成员总数。
- Map.prototype.set (key, value):设置键名 key 对应的键值为 value,然后返回整个 Map 结构。如果 key 已经有值,则键值会被更新,否则就新生成该键。
- Map.prototype.get (key):读取 key 对应的键值,如果找不到 key,返回 undefined。
- Map.prototype.has (key):返回一个布尔值,表示某个键是否在当前 Map 对象之中。
- Map.prototype.delete (key):删除某个键,返回 true。如果删除失败,返回 false。
- Map.prototype.clear ():清除所有成员,没有返回值。
# 基础语法
const m = new Map(); | |
const o = { p: 'Hello World' }; | |
m.set(o, 'content'); | |
console.log(m.get(o)); // 输出 "content" |
# 使用案例
我们可以使用 Map 对象来存储商品名称和价格之间的映射关系。然后,通过遍历 Map 对象来输出所有商品的名称和价格。
const map = new Map(); | |
map.set('apple', 0.67); | |
map.set('banana', 0.34); | |
map.set('pear', 0.54); | |
for (const [key, value] of map) { | |
console.log(key, value); | |
} |
这个案例中,我们首先创建了一个空的 Map 对象,然后通过 set 方法添加了三个键值对。接着,我们通过 for…of 循环遍历了整个 Map 对象,并输出了每个键值对的键和值。
# 迭代器 Iterator
ES6 中的 Iterator 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:
一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三是 ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费。
for…of 循环可以使用任何具有 Iterator 接口的数据结构进行遍历操作。在 ES6 中,有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被 for…of 循环遍历;有些则不行(比如对象)。对于后者,我们需要自己给它们加上这个接口。
# 基础语法
在这个例子中,我们首先使用 arr[Symbol.iterator]()
方法获取到数组的迭代器对象,然后使用 for…of 循环遍历迭代器对象中的元素。
let arr = [1, 2, 3]; | |
let iter = arr[Symbol.iterator](); | |
for (let i of iter) { | |
console.log(i); | |
} | |
// 1 | |
// 2 | |
// 3 |
# 生成器 Generator
ES6 的 Generator 函数是一种异步编程解决方案,语法行为与传统函数完全不同。Generator 函数是一个状态机,封装了多个内部状态,执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。调用遍历器对象的 next 方法,使得指针移向下一个状态,并返回该状态的值
# 基础语法
在 Generator 函数内部,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
function* helloWorldGenerator() { | |
yield 'hello'; | |
yield 'world'; | |
return 'ending'; | |
} | |
var hw = helloWorldGenerator(); | |
hw.next() // { value: 'hello', done: false } | |
hw.next() // { value: 'world', done: false } | |
hw.next() // { value: 'ending', done: true } | |
hw.next() // { value: undefined, done: true } |
以上代码定义了一个 Generator 函数 helloWorldGenerator,它内部有两个 yield 表达式(hello 和 world),即该函数有三个状态:hello、world 和 return 语句(结束执行)。调用该 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(遍历器对象),我们需要调用遍历器对象的 next 方法,使得指针移向下一个状态,并返回该状态的值。
# 使用案例
# 异步操作的同步化表达
由于 Generator 可以暂停函数执行,返回任意表达式的值,这使得 Generator 有多种应用场景。可以把异步操作写在 yield 表达式里面,等到调用 next 方法时再往后执行。
function* asyncFunc() { | |
let result1 = yield asyncOperation1(); | |
let result2 = yield asyncOperation2(result1); | |
let result3 = yield asyncOperation3(result2); | |
return result3; | |
} |
这个代码示例中,asyncFunc 是一个 Generator 函数,它包含了三个异步操作。在调用 asyncFunc 时,会返回一个遍历器对象。调用遍历器对象的 next 方法时,会执行 asyncOperation1 (),并返回一个 Promise 对象。当 Promise 对象状态变为 resolved 时,会将异步操作的结果赋值给 result1,并继续执行 Generator 函数。接下来会执行 asyncOperation2 (result1),并将结果赋值给 result2。最后会执行 asyncOperation3 (result2),并将结果赋值给 result3。最终返回 result3。
# 控制流管理
可以使用 Generator 函数控制函数的执行流程,甚至可以让函数执行到一半暂停下来,等到满足某个条件后再继续执行。
function* controlFlow() { | |
let value = yield step1(); | |
if (value === 'step1') { | |
value = yield step2(); | |
} | |
if (value === 'step2') { | |
value = yield step3(); | |
} | |
return value; | |
} |
这个代码示例中,controlFlow 是一个 Generator 函数,它包含了三个步骤。在调用 controlFlow 时,会返回一个遍历器对象。调用遍历器对象的 next 方法时,会执行 step1 (),并返回一个字符串。如果字符串等于’step1’,则会执行 step2 (),并返回一个字符串。如果字符串等于’step2’,则会执行 step3 (),并返回一个字符串。最终返回最后一次调用 next 方法时的返回值。
# 前端路由
前端路由就是根据 URL 的不同,返回不同的内容。使用 Generator 函数可以实现前端路由的功能。
function* router() { | |
let path = location.pathname; | |
if (path === '/home') { | |
yield homePage(); | |
} else if (path === '/about') { | |
yield aboutPage(); | |
} else if (path === '/contact') { | |
yield contactPage(); | |
} else { | |
yield notFoundPage(); | |
} | |
} |
这个代码示例中,router 是一个 Generator 函数,它根据 URL 的不同返回不同的内容。在调用 router 时,会返回一个遍历器对象。根据 location.pathname 的不同,会调用不同的函数,并返回相应的内容。
# 抽奖活动
假定某公司的年会上有一个抽奖活动,总共 6 个人可以抽 6 次,每抽一次,抽奖机会就会递减。使用 Generator 函数可以实现该功能
function* draw(count) { | |
while (count > 0) { | |
count--; | |
yield `还剩余${count}次机会!`; | |
} | |
} |
这个代码示例中,draw 是一个 Generator 函数,它模拟了一个抽奖活动。在调用 draw 时,需要传入抽奖次数 count。在每次调用遍历器对象的 next 方法时,会将 count 减 1,并返回一个字符串。
# 状态机
Generator 函数可以看作是一个状态机,封装了多个内部状态,执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。
function* stateMachine() { | |
let state = 'A'; | |
while (true) { | |
switch (state) { | |
case 'A': | |
console.log('State A'); | |
state = 'B'; | |
break; | |
case 'B': | |
console.log('State B'); | |
state = 'C'; | |
break; | |
case 'C': | |
console.log('State C'); | |
state = 'A'; | |
break; | |
} | |
yield; | |
} | |
} |
在调用 stateMachine 时,会返回一个遍历器对象。调用遍历器对象的 next 方法时,会执行 Generator 函数,并返回一个对象,包含 value 和 done 两个属性。value 属性为 undefined,done 属性为 false。在调用遍历器对象的 next 方法时,会执行下一个 case 语句,并将 state 的值更新为下一个状态。最终返回最后一次调用 next 方法时的返回值。
# Promise 对象
Promise 对象是 JavaScript 中的一种异步编程解决方案。它用于表示一个异步操作的最终完成(或失败)及其结果值。Promise 对象有三种状态:pending(等待态),fulfilled(成功态),rejected(失败态);状态一旦改变,就不会再变。Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。Promise 对象让你能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。
# 基础语法
const promise = new Promise((resolve, reject) => { | |
setTimeout(() => { | |
resolve('Success!'); | |
}, 1000); | |
}); | |
promise.then(value => { | |
console.log(value); // "Success!" | |
}); |
以上代码,创建了一个 Promise 对象,并在 1 秒后使用 resolve 函数将其状态从 “未完成” 变为 “成功”。然后,我们使用 then 方法附加了一个处理程序,该处理程序在异步操作成功时被调用,并接收异步操作的结果作为参数。
# 使用案例
使用 Promise 对象管理 Ajax 请求
function ajax(url) { | |
return new Promise((resolve, reject) => { | |
const xhr = new XMLHttpRequest(); | |
xhr.open('GET', url); | |
xhr.responseType = "json"; | |
xhr.setRequestHeader("Accept", "application/json"); | |
xhr.onreadystatechange = function () { | |
if (xhr.readyState === 4) { | |
if (xhr.status === 200) { | |
resolve(xhr.responseText); | |
} else { | |
reject(xhr.statusText); | |
} | |
} | |
}; | |
xhr.onerror = function () { | |
reject('Network Error'); | |
}; | |
xhr.send(); | |
}); | |
} | |
ajax('/api/data') | |
.then((data) => { | |
console.log(data); | |
}) | |
.catch((error) => { | |
console.error(error); | |
}); |
# async 函数
async 是 ES6 中的一个关键字,用于定义异步函数。异步函数是一种特殊的函数,它返回一个 Promise 对象,可以使用 await 关键字来等待异步操作完成。async 函数可以让我们更方便地使用 Promise,避免了回调地狱的问题。
# 基础语法
async function foo() { | |
// 异步操作 | |
} |
你可以在异步函数中使用 await 关键字来等待异步操作完成,例如:
async function foo() { | |
const result = await someAsyncOperation(); | |
console.log(result); | |
} |
这里 someAsyncOperation () 是一个异步操作,它返回一个 Promise 对象。当我们在异步函数中使用 await 关键字等待这个 Promise 对象时,JavaScript 引擎会暂停当前函数的执行,直到这个 Promise 对象被 resolve 或 reject 为止。如果这个 Promise 对象被 resolve 了,那么 await 表达式的结果就是这个 Promise 对象 resolve 时的值;如果这个 Promise 对象被 reject 了,那么 await 表达式会抛出一个异常。
请注意,只有在异步函数中才能使用 await 关键字。如果你在普通函数中使用 await 关键字,那么 JavaScript 引擎会抛出一个 SyntaxError 异常。
# 使用案例
接收 api 的天气数据
async function fetchWeatherData(city) { | |
const response = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY`); | |
const weatherData = await response.json(); | |
return weatherData; | |
} | |
async function main() { | |
try { | |
const weatherData = await fetchWeatherData('Seattle'); | |
console.log(weatherData); | |
} catch (error) { | |
console.error(error); | |
} | |
} | |
main(); |
这个例子中,使用了 async 函数来获取 Seattle 的天气数据。fetchWeatherData () 函数使用了 await 关键字来等待 fetch () 函数返回的 Promise 对象。当 fetch () 函数返回的 Promise 对象被 resolve 时,我们就可以使用 await 关键字来获取这个 Promise 对象 resolve 时的值。在这个例子中,我们使用了 try…catch 语句来捕获可能出现的异常。
# Class
ES6 Class 是 ES6 提供的一个语法糖,本质上是一个构造函数。通过 class 关键字,可以定义类。ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。
# 基础语法
class Point { | |
constructor(x, y) { | |
this.x = x; | |
this.y = y; | |
} | |
toString() { | |
return '(' + this.x + ', ' + this.y + ')'; | |
} | |
} | |
let s = new Point(1,2); | |
console.log(s); |
在这个例子中,定义了一个 Point 类。Point 类有一个 constructor 方法,用来接收参数并初始化实例对象。Point 类还有一个 toString 方法,用来返回实例对象的字符串表示形式。
# 类的继承
ES6 Class 的继承可以通过 extends 关键字实现。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this 上面(所以必须先调用 super 方法),然后再用子类的构造函数修改 this 。如果子类没有定义 constructor 方法,这个方法会被默认添加。也就是说,不管有没有显式定义,任何一个子类都有 constructor 方法。
class Animal { | |
constructor(name) { | |
this.name = name; | |
} | |
speak() { | |
console.log(this.name + ' makes a noise.'); | |
} | |
} | |
class Dog extends Animal { | |
speak() { | |
console.log(this.name + ' barks.'); | |
} | |
} | |
let d = new Dog('Mitzie'); | |
d.speak(); // Mitzie barks. |
在这个例子中,定义了一个 Animal 类和一个 Dog 类。Dog 类继承了 Animal 类,并重写了 Animal 类的 speak 方法。
# Module
ES6 Module 的语法是通过 export 命令显式指定输出的代码,再通过 import 命令输入。ES6 模块不是对象,而是一个静态化的模块解决方案,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
# 基础语法
// ES6 Module | |
// 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)); | |
} | |
// main.js | |
import { square, diag } from 'lib'; | |
console.log(square(11)); // 121 | |
console.log(diag(4, 3)); // 5 |
在这个例子中,定义了一个 lib.js 文件和一个 main.js 文件。lib.js 文件中定义了三个函数:sqrt、square 和 diag,并通过 export 命令显式指定输出。main.js 文件中通过 import 命令输入 lib.js 文件中的 square 和 diag 函数。