目录
前言
由于这些几个知识点都是相关且层层递进的,所以一起总结学习了。
Symbol
ES5中对象的属性名都是字符串,容易造成重名,污染环境。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol
的原因。
ES6中的添加了一种原始数据类型symbol, 表示独一无二的值。(已有的原始数据类型:String, Number, boolean, null, undefined, 对象)。
Symbol 值通过Symbol
函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型的属性,就都是独一无二的,可以保证不会与其他属性名产生冲突。
特点:
1、Symbol属性对应的值是唯一的,解决命名冲突问题
2、Symbol值不能与其他数据进行计算,包括同字符串拼串
3、for in, for of遍历时不会遍历symbol属性。
使用:
1、调用Symbol函数得到symbol值,Symbol类型属于ES6中新增的基本数据类型之一,内部没有construtor构造器,不能使用new关键字创建
let symbol = Symbol(); // 创建一个symbol值
let obj = {};
obj[symbol] = 'hello'; // 将symbol作为一个对象的属性
2、传参标识
let symbol = Symbol('one');
let symbol2 = Symbol('two');
console.log(symbol);// Symbol('one')
console.log(symbol2);// Symbol('two')
3、内置Symbol值:除了定义自己使用的Symbol值以外,ES6还提供了11个内置的Symbol值,指向语言内部使用的方法。
举例应用:
<script type="text/javascript">
//创建symbol属性值
let symbol = Symbol();
console.log(symbol);
let obj = {username:"chenjie",age:33};
obj.sex = '女';
//symbol可以作为对象的属性时 不能用obj.symbol的形式赋值
obj[symbol] = 'hello';
console.log(obj);
console.log("---------for in for of 不能遍历symnol属性--------");
for (let i in obj) {
console.log(i);
}
console.log("---------symbol属性值是唯一的------------");
let symbol2 = Symbol();
let symbol3 = Symbol();
console.log(symbol2==symbol3);//false
console.log(symbol2,symbol3);//Symbol() Symbol()
let symbol4 = Symbol('one');
let symbol5 = Symbol('two');
console.log(symbol4==symbol5);//false
console.log(symbol4,symbol5);//Symbol(one) Symbol(two)
console.log("----------可以利用Symbol定义常量---------");
const Person_key = Symbol('person_key');
console.log(Person_key);
</script>
Iterator
介绍
概念: iterator是一种接口机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
作用:
1、为各种数据结构,提供一个统一的、简便的访问接口;
2、使得数据结构的成员能够按某种次序排列
3、ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费。
工作原理:
-
创建一个指针对象(迭代器对象**),指向数据结构的起始位置。
-
第一次调用next方法,指针自动指向数据结构的第一个成员
-
接下来不断调用next方法,指针会一直往后移动,直到指向最后一个成员
-
每调用next方法返回的是一个包含value属性和done属性的对象,{value: 当前成员的值, done: 布尔值}
* value表示当前成员的值; done对应的布尔值表示当前的数据的结构是否遍历结束,即是否还有必要再一次调用
next
方法。 * 当遍历结束的时候返回的value值是undefined,done值为true
原生具备iterator接口的数据结构:
1、Array
2、arguments
3、set容器
4、map容器
5、String
举例
手动实现Iterator
<script>
console.log('--------------模拟iterator指针对象(遍历器对象)-------------');
function myIterator(arr) {
let nextIndex = 0; //记录指针的位置
return {
next: function () {
return nextIndex < arr.length ? { value: arr[nextIndex++], done: false } : { value: undefined, done: true };
},
};
}
// 准备一个数据来测试
let arr = [1, 4, 65, 'abc'];
let iteratorObj = myIterator(arr); // iterator对象
console.log(iteratorObj.next()); // {value: 1, done: false}
console.log(iteratorObj.next()); // {value: 4, done: false}
console.log(iteratorObj.next()); // {value: 65, done: false}
console.log(iteratorObj.next()); // {value: "abc", done: false}
console.log(iteratorObj.next()); // {value: undefined, done: true}
console.log(iteratorObj.next()); // {value: undefined, done: true}
//将iterator接口部署到指定的数据类型上,就可以使用for of去循环遍历
</script>
用for…of遍历数据结构
console.log('----------用for of 遍历Array-------------------------');
let arr = [1, 4, 65, 'abc'];
for (let i of arr) {
console.log(i);
}
console.log('----------用for of 遍历String-------------------------');
let str = 'abcd';
for (let i of str) {
console.log(i);
}
console.log('----------用for of 遍历arguments--------------');
function fun() {
for (let i of arguments) {
console.log(i);
}
}
fun(1, 2, 'abc');
Symbol.iterator属性返回迭代器
ES6 规定,默认的 Iterator 接口是部署在数据结构的Symbol.iterator属性上的,或者说,一个对象只要具有Symbol.iterator属性,就说明他具备iterator接口。Symbol.iterator属性值是一个函数,这个函数是当前对象默认的遍历器生成函数,执行这个函数,就会返回一个遍历器。
/*
Array,String等对象,身上都有一个Symbol.iteration
当使用for of去遍历某一个数据结构的时候,首先去找Symbol.iteration属性,
找到了就去遍历,没有找到的话就不能遍历 xxx is not iterable
实际就是下面这套原理
*/
// 下面的语句在targetData对象身上部署了iterator接口 那么他和数组Array对象一样可以用for of来遍历
let targetData = {
[Symbol.iterator]: function () {
let nextIndex = 0; //记录指针的位置
return {
next: function () {
return nextIndex < this.length ? { value: this[nextIndex++], done: false } : { value: undefined, done: true };
},
};
},
};
ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被for…of循环遍历。原因在于,这些数据结构原生部署了Symbol.iterator属性,另外一些数据结构没有(比如对象)。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。
数组对象的Symbol.iterator属性
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); // 返回该数组的迭代器对象
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
上面代码中,变量arr是一个数组,原生就具有遍历器接口,部署在arr的Symbol.iterator属性上面。所以,调用这个属性,就得到遍历器对象。
对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for…of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for…of循环遍历。
iterator接口的应用
有些语法操作会静悄悄的调用迭代接口,比如:
- 解构赋值:对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
- 扩展运算符:扩展运算符(…)也会调用默认的 Iterator 接口。
console.log('--------使用三点运算符和解构赋值默认都去调用了iterator接口----');
let arr3 = [2, 3, 4, 5];
arr2 = [1, ...arr3, 6]; //三点运算符
console.log(arr2); // [1,2,3,4,5,6]
let [a, b] = arr2; //解构赋值
console.log(a, b); // 1 2
Generator
概念
1、ES6提供的解决异步编程的方案之一
2、Generator函数是一个状态机,内部封装了不同状态的数据,
3、用来生成遍历器对象
使用方法
-
function 与函数名之间有一个星号
-
内部用yield表达式来定义不同的状态
// 下面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world),即该函数有三个状态:hello,world 和 return 语句(结束执行)。 function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();
-
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象。调用遍历器对象的next方法,会使指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止,next方法的返回值是这样的一个对象:
{value: yield表达式后的表达式结果/yield表达式后面的语句的执行返回值, done: false/true}
function* helloWorldGenerator() { // 包含三个状态 hello world ending 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 } // 第一次调用从函数头部执行到yield 'hello'为止 // 第二次调用从函数头部指定到yield 'world'为止 // 第二次调用从函数头部指定到return 'ending'为止
function* helloWorldGenerator() { // 包含四个状态 hello world ending undefined yield 'hello'; yield 'world'; yield 'ending'; } var hw = helloWorldGenerator(); hw.next() // 返回{ value: 'hello', done: false } hw.next() // 返回{ value: 'world', done: false } hw.next() // 返回{ value: 'ending', done: false } hw.next() // 返回{ value: undefined, done: true }
-
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态是不变的。通过
next
方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。对比下面的例子可以理解:
function* foo(x) { var y = 2 * (yield (x + 1)); // y -> 2 * undefined -> NaN var z = yield (y / 3); // z -> undefined return (x + y + z); // 5 + NaN + undefined } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true}
function* foo(x) { var y = 2 * (yield (x + 1)); // y -> 2 * 12 var z = yield (y / 3); // z -> 13 return (x + y + z); // 5+24+13=42 } var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } 这个12会被当作上一个yield表达式的返回值 b.next(13) // { value:42, done:true }
-
Generator 函数可以不用
yield
表达式,这时就变成了一个单纯的暂缓执行函数。function* f() { console.log('执行了!') } var generator = f(); setTimeout(function () { generator.next() }, 2000);
-
注意,yield表达式如果用在另一个表达式之中,必须放在圆括号里面。
function* demo() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK }
-
for…of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。
function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 打印1 2 3 4 5
上面代码使用for…of循环,依次显示 5 个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for…of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for…of循环之中。
function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; yield 6; } for (let v of foo()) { console.log(v); } // 打印1 2 3 4 5 6
利用Generator为对象部署迭代器
任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
// 原生实现
Object.prototype[Symbol.iterator] = (function (){
// Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
let keys = Object.keys(this)
let index = 0
return {
next: () => {
if (index < keys.length) {
return { value: [ keys[index++], this[keys[index]] ], done: false }
} else {
return { value: undefined, done: true }
}
}
}
})
let a = {name:1,age:2,gender:0};
// for...of会调用 对象的 Symbol.iterator 接口实现迭代
for(let [key, value] of a){
console.log(key,value)
}
// Generator实现
Object.prototype[Symbol.iterator] = (function* (){
// Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
let keys = Object.keys(this)
// (yield* 表达式)用于委托给另一个generator 或可迭代对象。
yield* keys.map(key=>([key, this[key]]))
})
let a = {name:1,age:2,gender:0};
// for...of会调用 对象的 Symbol.iterator 接口实现迭代
for(let [key, value] of a){
console.log(key,value)
}
Generator在异步函数中的应用
异步任务的封装
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。就像前面说过的,这段代码非常像同步操作,除了加上了yield命令。
执行这段代码的方法如下。
var g = gen();
var result = g.next(); // {value: promise对象, done: false}
result.value.then(function(data){ // 等网络操作完成后再对返回数据进行处理
return data.json();
}).then(function(data){
g.next(data);
});
上面代码中,首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个 Promise 对象,因此要用then方法调用下一个next方法。
可以看到,虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。
async函数
介绍
- 概念: 真正意义上去解决异步回调的问题,同步流程表达异步操作
- 本质: Generator的语法糖
- 语法:
async function foo() {
await 异步操作;
await 异步操作;
}
-
特点:
1、不需要像Generator去手动调用next方法,遇到await等待,当前的异步操作完成就往下执行
2、async函数返回的总是Promise对象,可以用then方法进行下一步操作
3、async取代Generator函数的星号*,await取代Generator的yield
4、语意上更为明确,使用简单
与Generator区别
const fs = require('fs');
// 异步操作 读取文件
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
// Generator的异步应用
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
// async的异步应用
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法。
(2)更好的语义。
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
基本使用
下面的代码展示了async的基本用法。async函数是使用async关键字声明的函数,在async函数中可以使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
// async的异步应用
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async函数返回一个 Promise 对象。当函数执行的时候,一旦遇到await语句,就会暂停当前async函数的执行,async函数会先返回一个等待状态的promise对象(记为A),然后等待await后的语句的执行结果。
await后面一般跟一个返回Promise对象(记为B)的异步任务,若 Promise对象(B) 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async 函数。
若 Promise 对象(B)处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。
async函数返回的 Promise 对象(A),必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。
async基本使用
// async基本使用
function foo () {
console.log('foo代码执行了')
return new Promise(resolve => {
setTimeout(resolve,2000);
});
}
async function test () {
console.log('开始执行',new Date().toTimeString());
await foo(); // 遇到await之后test函数会先返回 等待await后面的异步操作完成后,再继续就往下执行
console.log('执行完毕',new Date().toTimeString());
}
test();
console.log('hhh')
// 打印结果
// 开始执行 15:09:03 GMT+0800 (中国标准时间)
// foo代码执行了
// hhh
// 执行完毕 15:09:05 GMT+0800 (中国标准时间)
// 或者写成
// async基本使用
async function foo () {
console.log('foo代码执行了')
return new Promise(resolve => {
setTimeout(resolve,2000);
});
}
async function test () {
console.log('开始执行',new Date().toTimeString());
await foo(); // 遇到await 等待后面的异步操作完成,当前的异步操作完成就往下执行
console.log('执行完毕',new Date().toTimeString());
}
test();
console.log('hhh')
// 打印结果
// 开始执行 15:09:03 GMT+0800 (中国标准时间)
// foo代码执行了
// hhh
// 执行完毕 15:09:05 GMT+0800 (中国标准时间)
async函数一定会返回一个promise对象。
async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。
async function foo() {
return 1
}
// 等价于
function foo() {
return Promise.resolve(1)
}
async函数内部抛出错误
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log('resolve', v),
e => console.log('reject', e)
)
//reject Error: 出错了
await操作符的返回值
语法:[返回值] = await 表达式;
await
操作符用于等待一个Promise对象,它只能在异步函数中使用。表达式可以是一个Promise对象或任何其他类型的值(数值、字符串、函数、布尔值…,但这时会自动转成立即 resolved 的 Promise 对象)。
如果表达式是一个Promise对象,那么返回值是该Promise对象的处理结果;如果表达式的不是 Promise 对象,则返回该值本身。
function test2() {
return 'xxx';
}
async function asyncPrint() {
let result = await test2();
console.log(result);//xxx
let result2 = await Promise.resolve('成功了');
console.log(result2);//成功了
let result3 = await Promise.reject('失败了');
console.log(result3);//VM119:11 Uncaught (in promise) 失败了
}
asyncPrint();
async function f() {
return await 123;
// 等同于
// return 123;
}
f().then(v => console.log(v))
// 123
await操作符后的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。如下所示:
async function f() {
await Promise.reject('出错了');
console.log('hhh')
await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
注意,在async函数中,任何一个await操作符后的 Promise 对象变为reject状态,那么整个async函数都会中断执行。有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try…catch里面,这样不管这个异步操作是否成功,第二个await都会执行。
async function f() {
try {
await Promise.reject('出错了');
console.log('hhh')
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// hello world
或者这样
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
console.log('hhh')
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出错了
// hhh
// hello world
例:await操作符后面跟一个promise对象
function foo1 () {
return new Promise(resolve => {
setTimeout(function () {
console.log('foo1')
resolve('hhhfoo1');
},1000);
});
}
function foo2 () {
return new Promise(resolve => {
setTimeout(function () {
console.log('foo2')
resolve();
},1000);
});
}
function foo3 () {
return new Promise(resolve => {
setTimeout(function () {
console.log('foo3')
resolve();
},1000);
});
}
async function test () {
console.log( await foo1() );
console.log('foo1执行完了')
await foo2();
console.log('foo2执行完了')
await foo3();
console.log('foo3执行完了')
}
test();
// 打印结果
// 一秒后打印 foo1 hhhfoo1 foo1执行完了
// 又一秒后打印 foo2 foo2执行完了
// 又一秒后打印 foo3 foo3执行完了
如果await后跟的是promise,会等promise的状态变为成功,才会继续往下执行。
例:await操作符后面跟一个普通函数
function foo1() {
console.log('foo1执行啦')
setTimeout(function () {
console.log('foo1');
}, 1000);
}
function foo2() {
console.log('foo2执行啦')
setTimeout(function () {
console.log('foo2');
}, 1000);
}
function foo3() {
console.log('foo3执行啦')
setTimeout(function () {
console.log('foo3');
}, 1000);
}
async function test() {
await foo1();
console.log('foo1执行完了');
await foo2();
console.log('foo2执行完了');
await foo3();
console.log('foo3执行完了');
}
test();
// 打印结果
// 立刻打印 foo1执行拉 foo1执行完了 foo2执行拉 foo2执行完了 foo3执行拉 foo3执行完了
// 1秒后打印 foo1 foo2 foo3
如果await后面跟的是普通函数,普通函数回立即返回,不会中断async函数。
参考
http://www.ruanyifeng.com/blog/2015/05/async.html 大部分来源于该博客