ES6相关语法
一、let和const
使用let声明变量。
使用const声明常量。
二、Symbol数据类型
回顾一下:ES5的类型有这些

Symbol介绍
/**
* Symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值。
* 不能在调用Symbol时使用new关键字
* 在通过Symbol生成独一无二的值时可以设置一个标记,这个标记仅仅用于区分, 没有其它任何含义
*/
let a = Symbol() let b = Symbol() typeof a //symbol a===b //false
Symbol.for & Symbol.keyFor
/**
* 有时,我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点。
* Symbol.for方法根据string查找Symbol,没有就新建,类似单例模式。
* Symbol.keyfor方法根据Symbol查找string,找不到就是undefined
*/
let c = Symbol("nick")
let d = Symbol.for("nick")
let e = Symbol.for("nick")
c===d //false
d===e //true
Symbol.keyFor(c) //undefined
Symbol.keyFor(d) //字符串 "nick"
Symbol使用场景:
/**
* Symbol应用场景一: 使用Symbol来替代常量,可以保证常量是唯一的
*/
{ const TYPE_AUDIO = 'AUDIO' const TYPE_VIDEO = 'VIDEO' const TYPE_IMAGE = 'IMAGE' } { const TYPE_AUDIO = Symbol() const TYPE_VIDEO = Symbol() const TYPE_IMAGE = Symbol() }
/**
* Symbol应用场景二:作为对象属性名(key)能防止重名属性
* 跟普通key用法一样,只是这个key不能通过Object.keys或Object.getOwnPropertyNames获取,也不会被序列化
* 可以通过Object.getOwnPropertySymbols拿到Symbol的key
* 通过反射能拿到所有属性,以及属性的值
*/
let name = Symbol('name')
let obj = {
[name]: 'Jack',
age: 18,
title: 'Engineer'
}
obj[name] //'Jack'
name.description //'name'
Object.keys(obj) //["age","title"] Array
Object.getOwnPropertyNames(obj) //['age', 'title'] Array
JSON.stringify(obj) // '{"age":18,"title":"Engineer"}' string
Object.getOwnPropertySymbols(obj) //[Symbol(name)] Array
Reflect.ownKeys(obj) //["age","title",Symbol(name)] Array
for(let k of Reflect.ownKeys(obj)){
console.log(obj[k]) // 18 Engineer Jack
}
三、解构赋值
写的非常详细: https://www.runoob.com/w3cnote/deconstruction-assignment.html
使用场景: 可以快速方便地从对象和数组中提取属性或数据到单独的变量中。
四、字符串的扩展方法和模板字符串
写的非常详细:https://www.runoob.com/w3cnote/es6-string.html
额外补充:
// es5 { const str1 = 'a' //用直接输入字符串 const str2 = '\u20bb7' //unicode编码输出字符串 console.log(str2) }
es6中,用花括号括起来就能正确输出了:
{ const str3 = '\u{20bb7}' console.log('str3', str3) }
输出结果:

如何遍历unicode超范围的字符串呢?必须用for-of遍历,用传统for循环遍历结果不正确
{ const str3 = '\u{20bb7}' //传统for循环 for (let i = 0; i < str3.length; i++) { console.log('for', str3[i]) } // for of for (let word of str3) { console.log('for-of', word) } }
输出结果:

*五、ES6和ES7之数组扩展和运算符扩展
写的非常详细包括示例:https://www.runoob.com/w3cnote/es6-array.html
function add(x, y) { return x + y } let addList = [1, 2] console.log(add(...addList));
2. 复制数组,浅拷贝
const list = [1, 2, 3, 4, {a:1}]
let list2 = [...list]
console.log(list2)
3.分割数组
const totalList = [1, 'a', 'b', 'c'] let [, ...strList] = totalList console.log(strList)
4.合并数组
console.log([...[1, 2],...[3, 4]]); // [1, 2, 3, 4]
数组方法
Array.of():将参数中所有值作为元素形成数组
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4] // 参数值可为不同类型 console.log(Array.of(1, '2', true)); // [1, '2', true] // 参数为空时返回空数组 console.log(Array.of()); // []
Array.from(obj) :将obj转换为数组,obj可以是类数组对象、可迭代对象、map、set、字符串、mapFn
//类数组对象 console.log(Array.from([1, 2])); // [1, 2] //map let map = new Map(); map.set('key0', 'value0'); map.set('key1', 'value1'); console.log(Array.from(map)); // [['key0', 'value0'],['key1', 'value1']] //set let arr = [1, 2, 3]; let set = new Set(arr); console.log(Array.from(set)); // [1, 2, 3] //字符串 let str = 'abc'; console.log(Array.from(str)); // ["a", "b", "c"] //mapFn console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]
includes() :数组是否包含指定值
find(fn) :查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素
findIndex(fn) :查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引
fill(a, i1, i2) :将索引i1~i2范围的值用a填充
flat() :嵌套数组转一维数组
遍历数组
let a = Array.of('a', 'b', 'c', 'd')
// for-in 取索引
for (let i in a) {
console.log(a[i]) // a b c d
}
// for-of 取值
for (let v of a) {
console.log(v) // a b c d
}
// entries既取索引又取值
for (let [i, v] of a.entries()) {
console.log(i, v) // 0 a 1 b 2 c 3 d
}
数组中的map和reduce用法
{ // map 数据映射 const json = [{ title: 'es6', status: 1 }, { title: 'react', status: 0 }, { title: 'webpack', status: 1 }, { title: 'vue', status: 1 }] let video = json.map(function (item) { // return { // name: item.title, // statusTxt: item.status ? '已上线' : '未上线' // } let obj = {} Object.assign(obj, item) obj.status = item.status ? '已上线' : '未上线' return obj }) console.log('json', json) console.log('video', video) }
reduce类似于大数据中的reduce,一组输入,1行或n行输出,如何定义一组,由需求决定
参数解释:reduce(callback(acc, currValue, currIndex, array), initalValue)
callback是一个回调函数,acc是每次回调的返回值,currValue是当前数组里进行回调的值,currIndex是currValue在数组中的位置,Array是回调的数组。
initalValue是一个可选参数,如果填了,acc的初始值就是initialValue,如果不填acc的初始值是数组的第一项
reduce例子1:
// 统计字符次数 const letterList = 'abcadefrd'.split('') const result = letterList.reduce(function (acc, cur) { acc[cur] ? acc[cur]++ : acc[cur] = 1 return acc }, {}) console.log(result) //{ a: 2, b: 1, c: 1, d: 2, e: 1, f: 1, r: 1 }
reduce例子2:
//展开多层数组 const list = [1, ['2nd', 2, 3, ['3rd', 4, 5]], ['2nd', 6, 7]] const deepFlat = function(list) { return list.reduce(function(acc, cur) { return acc.concat(Array.isArray(cur) ? deepFlat(cur) : cur) }, []) } let flatList = deepFlat(list) console.log('reduce-flat', flatList) // [1, '2nd', 2, 3, '3rd', 4, 5, '2nd', 6, 7 ]
*六、ES6和ES8对象新特性及新增方法
1、定义对象可用简写形式
{ let name = '小明' let age = 18 let es6Obj = { name, age, sayHello() { console.log('this is es6Obj') } } //等价于 let es5Obj = { name: name, age: age, sayHello: function () { console.log('this is es5Obj') } } }
还可以用表达式作为属性名
const hello = "Hello"; const obj = { [hello+"2"]:"world" }; obj //{Hello2: "world"}
2、拓展运算符(...)用于拷贝对象,合并对象,覆盖属性
{ // 拷贝对象,浅拷贝 const obj = { name: 'Nick', video: 'es6' } let videoObj = { ...obj } console.log(videoObj) //合并对象 const initObj = { color: 'red' } let obj3 = { ...obj, ...initObj } console.log(obj3) // 自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉 let obj2 = { ...obj, name: 'Jack123' } console.log(obj2) }
3、对象的遍历:Object.keys() Object.values(), Object.entries()
const json = {name: 'Nick', video: 'es6', date: 2019}
for (const key of Object.keys(json)) {
console.log(key) //name video date
}
for (const value of Object.values(json)) {
console.log(value) //Nick es6 2019
}
for (const [k, v] of Object.entries(json)) {
console.log(k, v) // name Nick video es6 date 2019
}
4、Object.assign(target, source_1, ···) 用于将源对象的所有可枚举属性复制到目标对象中,类似于对象合并
- 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
- 如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
//第一个参数是目标对象,后面的参数是源对象,后面会覆盖前面的同名属性 // assign 的属性拷贝是浅拷贝 let target = {a: 1}; let obj1 = {b: 2}; let obj2 = {c: 3}; let obj3 = {a: 999} Object.assign(target, obj1, obj2, obj3); console.log(target); // { a: 999, b: 2, c: 3 } //函数只有一个参数,参数转换为对象返回 Object.assign(3); // Number {3} typeof Object.assign(3); // "object"
5、Object.is(value1, value2) 用来比较两个值是否严格相等,与(===)基本类似
与(===)的区别
//一是+0不等于-0 Object.is(+0,-0); //false +0 === -0 //true //二是NaN等于本身 Object.is(NaN,NaN); //true NaN === NaN //false
*七、Map与WeakMap
map与object的区别
- Object 的键只能是字符串或者 Symbols,Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突
Map
1、map对象的操作
// 初始化 let map = new Map([['name', 'Nick'], ['sex', 'male']]) map //Map(2) { 'name' => 'Nick', 'sex' => 'male' } map.size //2 // 添加元素,key可以是对象、方法、NaN let map1 = new Map(); let obj = [1,2,3] let funObj = function () {} let nanObj = NaN map1.set(obj, 'number').set('hobbies', ['swimming', 'running']).set(funObj, 'function').set(nanObj, 'not a number') map1 //Map(1) { [ 1, 2, 3 ] => 'number', 'hobbies' => [ 'swimming', 'running' ], [Function: funObj] => 'function', NaN => 'not a number'} // 查找元素 map1.get('hobbies') //[ 'swimming', 'running' ] map1.get([1,2,3]) //undefined 因为 obj !== [1,2,3] map1.get(obj) //'number' // 元素是否存在 map1.has('hobbies') //true // 删除元素 map1.delete('hobbies') // 清空 map1.clear()
map的转换、克隆、合并
// map 与 Array的相互转换 let kvArray = [["key1", "value1"], ["key2", "value2"]]; let myMap = new Map(kvArray); let outArray = Array.from(myMap); // map的克隆 var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]); var myMap2 = new Map(myMap1); console.log(myMap1 === myMap2); //false // map的合并 ,如果有重复的键值,则后面的会覆盖前面的 let first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]); let second = new Map([[1, 'uno'], [2, 'dos']]); let merged = new Map([...first, ...second]);
2、map对象的遍历:与object一样
// 将会显示两个 log。 一个是 "0 = zero" 另一个是 "1 = one" for (const [key, value] of myMap) { console.log(key + " = " + value); }
WeakMap
1、WeakMap与Map的区别
- 只接受对象作为一个键名,不接受其他类型的数据作为键名
- WeakMap键名是弱引用,不触发垃圾回收机制。换句话说,如果除WeakMap的key之外没有其他引用指向对象,对象就会被垃圾回收掉。
- 只有set()、get()、has()和delete()四个方法,没有clear, 没有size, 无法遍历
2、WeakMap的使用场景
借助WeakMap键名对象都是弱引用这一特点,WeakMap 可用于把 DOM 节点作为键名,当DOM节点被删除后,WeakMap内部的引用便会自动消失,可以有效的解决内存泄漏问题
例如,我们将某节点对象作为WeakMap的键名,然后我们将存有点击次数的对象作为键值存放于WeakMap中,每当发生一次click事件,点击次数+1,一旦这个 DOM 节点删除,保存点击次数的对象就会自动消失,不存在内存泄漏风险
const wm1 = new WeakMap(); const key = {foo: 1}; wm1.set(key, 2); wm1.get(key) // 2 const k1 = [1, 2, 3]; const k2 = [4, 5, 6]; const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]); wm2.get(k2) // "bar" wm2.delete(k1) //true wm2.has(k1) //false // 4. WeakMap只能接受对象作为键名,null除外 const map = new WeakMap(); map.set(1, 2) // TypeError: 1 is not an object! map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key map.set(null, 2) // TypeError: Invalid value used as weak map key //5. WeakMap没有遍历方法,不能被遍历 const k1 = [1, 2, 3]; const k2 = [4, 5, 6]; const map = new WeakMap([[k1, 'foo'], [k2, 'bar']]); map.size;//undefined map.keys()//TypeError: map.keys is not a function
*八、Set与WeakSet
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
var mySet = new Set([1, 2, 3, 4, 4]); [...mySet]; // [1, 2, 3, 4]
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var union = new Set([...a, ...b]); // {1, 2, 3, 4}
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var difference = new Set([...a].filter(x => !b.has(x))); // {1}
其他的参考Map与WeakMap
*九、Map、Set与Array以及Object之间的区别以及转换
/** * Map、Set与Array以及Object之间的区别 */ { let array = [] let obj = {} let map = new Map() let set = new Set() const gooditem = { fruit: 'apple' } // 增加 array.push(gooditem) obj['fruit'] = 'apple' map.set('fruit', 'apple') set.add(gooditem) console.log(array, obj, map, set) // [{ fruit: 'apple' }] // { fruit: 'apple' } //Map(1) { 'fruit' => 'apple' } //Set(1) { { fruit: 'apple' } } // 查询 const resultArray = array.includes(gooditem) const resultObj = 'fruit' in obj const resultMap = map.has('fruit') const resultSet = set.has(gooditem) console.log(resultArray, resultObj, resultMap, resultSet) // true true true true // 修改 array.forEach(function (item) { item.fruit = item.fruit ? 'orange' : '' }) obj['fruit'] = 'orange' map.set('fruit', 'orange') set.forEach(function (item) { item.fruit = item.fruit ? 'orange' : '' }) console.log(array, obj, map, set) //[{ fruit: 'orange' }] //{ fruit: 'orange' } //Map(1) { 'fruit' => 'orange' } //Set(1) { { fruit: 'orange' } } // 删除 const index = array.findIndex(function (item) { return item.fruit }) array.splice(index, 1) delete obj.fruit map.delete('fruit') set.delete(gooditem) console.log(array, obj, map, set) //[] //{} //Map(0) {} //Set(0) {} } { // 类型转换 map和对象间的转换 let obj = { name: 'Nick', hobbies: 'swimming' } console.log(Object.entries(obj)) //[ [ 'name', 'Nick' ], [ 'hobbies', 'swimming' ] ] let map = new Map(Object.entries(obj)) console.log('map', map) //Map(2) { 'name' => 'Nick', 'hobbies' => 'swimming' } let obj2 = Object.fromEntries(map) console.log('obj', obj2) //{ name: 'Nick', hobbies: 'swimming' } // 数组和set let array = [1, 2, 3, 4, 5] let set = new Set(array) console.log('set', set) //Set(5) { 1, 2, 3, 4, 5 } let array2 = Array.from(set) console.log('array', array2) //[ 1, 2, 3, 4, 5 ] }
*十、JS的数据结构中统一的遍历接口Iterator和for...of循环
简介:介绍什么是Iterator及其作用和与for…of循环的关系
什么是Iterator?
Iterator(迭代器)是一种接口,目的是为了给不同的数据结构提供统一的遍历方式,任何数据结构如果实现了Iterator接口,就能够用for…of循环进行遍历。
什么结构默认实现了Iterator接口?
1、Array
2、String
3、Set
4、Map
5、函数的argument对象
怎样实现Iterator接口?
Symbol.iterator
本质是一个函数,就是当前的数据集合默认的迭代器生成函数,执行这个函数,就会返回一个遍历器。
返回值是一个迭代器对象。这个对象里的显著特点就是有一个next()方法。每次调用next都会返回一个描述当前成员的信息对象,具有value和done两个属性。
例子:给object对象实现迭代器
// 应用场景,给object对象实现迭代器接口 const obj = { color: 'red', price: 18, size: 'small', [Symbol.iterator]() { let index = 0 const values = Object.values(this) return { next() { if(index < values.length) { return { value: values[index ++], done: false } } else { return { done: true } } } } } } for (const value of obj) { console.log(value) } // red // 18 // small
*十一、Proxy与Reflect
{ // Proxy, 代理的就是对象的一些操作 let account = { id: 9923, name: 'admin', _private: 'test', phone: '13812345678', create_time: '2019' } let accountProxy = new Proxy(account, { // 拦截读取和设置的操作。需求:将手机号中间4位隐藏,并且把2019变成2020 get: function (target, key) { switch (key) { case 'phone': return target[key].substring(0, 3) + '****' + target[key].substring(7) case 'create_time': return target[key].replace('2019', 2020) default: return target[key] } }, set: function (target, key, value) { //需求:不能设置id字段 if (key === 'id') { return target[key] } else { return target[key] = value } }, // 拦截key in obj has: function(target, key) { // 需求:打印出来 if(key in target) { console.log(`${key}:`, target[key]) return true } else { console.log('并无此属性') return false } }, // 拦截delete deleteProperty: function(target, key) { //需求:私有属性不能删除 if(key.indexOf('_') === 0) { console.warn('私有属性不能被删除') return false } else { delete target[key] return true } }, // 拦截Object.keys() ownKeys(target) { //需求:过滤掉'id'和下划线 return Object.keys(target).filter(function(item) { return item !== 'id' && item.indexOf('_') !== 0 }) } }) console.log('拦截读取', accountProxy.phone, accountProxy.create_time) // 138****5678 2020 accountProxy.id = 1234 accountProxy.name = 'guest' console.log('拦截设置', accountProxy.id, accountProxy.name) // 9923 guest console.log('拦截in', 'sex' in accountProxy) // false console.log('拦截删除', delete accountProxy['_private']) // false console.log('拦截Object.keys()',Object.keys(accountProxy)) //[ 'name', 'phone', 'create_time' ] }
如要拦截其他操作,请看:https://www.runoob.com/w3cnote/es6-reflect-proxy.html
Reflect
动态获取或添加对象的属性
let obj = { name: 'Nick', age: '32', sex: 'male', hobbies: 'swimming' } console.log(Reflect.get(obj, 'name')) Reflect.set(obj,'name', 'Jack') console.log(obj.name) 'name' in obj Reflect.has(obj, 'name')
反射的其他知识,请看:https://www.runoob.com/w3cnote/es6-reflect-proxy.html
十二、ES6中函数的扩展
1、设置默认参数
function es6Print(x, y = 'world') { console.log('es6', x + y) } es6Print('hello', '') //helloworld
// rest, 不确定有多少个参数 function add(...rest) { //rest直接就是数组,但argument不是数组 let sum = 0 for (let value of rest) { sum += value } console.log(sum) // Array.prototype.method.apply(arrgument) //将argument变成数组 }
// 尾调用,提高性能,使用递归的时候改为尾调用执行 function step2(x) { console.log('尾调用', x) } function step1(x) { return step2(x) }
/** * 在JavaScript什么时候使用箭头函数?? * 只要知道箭头函数做的是什么事就是可以了,大概如此:可以看出,箭头函数中的this是外部域的this * var _self = this; function fn () { console.log(_self) } * 以下四种情况用普通函数,其他情况用箭头函数 * 1.定义对象方法,像fruit.sum() * 2.定义原型方法,比如Cat.prototype.sayHello=function(){} * 3.定义事件回调函数,xx.addListener('xx',function(){}) * 4.定义构造函数 */
十三、ES6中理解类的概念
function Person(name, age) { this.name = name this.age = age } Person.prototype.sayHello = function () { console.log(`大家好, 我叫${this.name},我今年${this.age}岁了`) } const p = new Person('小明', 17) console.log(p) console.log(typeof p) //object console.log(typeof Person) //function
ES6改造ES5实现类的方法。可以看出,ES6中的class实际上是function的语法糖
class Person { constructor(name, age) { this.name = name this.age = age } sayHello() { console.log(`大家好, 我叫${this.name},我今年${this.age}岁了`) } } const p = new Person('小红', 17) console.log('class', p) //class Person { name: '小红', age: 17 } console.log(typeof p) //object console.log(typeof Person) //function
类的继承
// 类的继承,ES5中是用原型实现类继承 class Parent { constructor(name = 'Nick') { this.name = name } } class Child extends Parent { constructor(name = 'Jack') { // super要放在构造函数的最前面 super(name) this.name = name } } console.log('继承', new Child()) //Child { name: 'Jack' }
构造器
class Person { constructor(name = 'Nick') { this.name = name } get fullName() { return this.name + '\xa0' + 'Liu' } set fullName(value) { this.name = value } } const p = new Person() console.log('get', p.fullName) // Nick Liu p.fullName = 'Jack' console.log('set', p.name) // Jack
静态方法和静态属性
{ // 定义静态方法 class Person { constructor(name = 'Nick') { this.name = name } static sayHello(obj) { console.log('my name is ' + obj.name) } } const p = new Person('小花') Person.sayHello(p) // my name is 小花 } { // 定义静态属性 class Person { static prop = 'test' //es7 constructor(name = 'Nick') { this.name = name } static sayHello(obj) { console.log('my name is ' + obj.name) } } // Person.prop = 'test' //es6 console.log(Person.prop) // test }
十四、模块化开发(import和export)
注意:要在package.json中加type=module,否则会报错

方式一:每个变量都export
模块a中每个变量都export
export let a = 3; //导出变量 export function sayHello() { //导出函数 console.log('hello') } export class Test { //导出类 say() { console.log('test') } }
再在模块b中import
import {a, sayHello, Test} from './chapter5-4.2.js' //只引用某个
import * as test from './chapter5-4.2.js' //全部引用
方式二:统一export
模块a中统一export
let a = 3; function sayHello() { console.log('default', 'hello') } //推荐用这种,不会误操作 export default { a, sayHello }
模块b中import
import mod from './chapter5-4.2.js'
console.log(mod.a)
mod.sayHello()
十五、Javascript中异步实现方式
1、回调函数
$.ajax({ url:"", success: function (result) { $.ajax({ url:"", success: function (result1) { } }) } })
2、setInterval和setTimeout
/** * 异步实现方式之:setInterval和setTimeout */ { console.log(1) setTimeout(() => { console.log(2) }, 0) console.log(3) } // 1 // 3 // 2
3、Promise
const p1 = new Promise((resolve, reject) => { if(success) { // 成功 resolve(value) } else { // 失败 reject(error) } })
首先,Promise构造函数接受函数为参数,注意该函数是同步函数,Promise只要被new,function里面的逻辑会被立即执行;其次,该函数的两个参数分别是resolve和reject,这是两个回调函数,由js引擎提供.
Promise保存着异步操作的结果,进行中,成功或失败
resolve函数主要是实现状态从pending => resolved;如果状态变为resolved,value会传递给外部。
reject函数是将promise的状态从pending =>rejected·如果状态变为rejected,error会传递给外部。
示例:
-----------------
let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('resolved.'); }); console.log('Hi!'); // Promise // Hi! // resolved
---------------------
resolve函数的参数除了可以是普通值外,还可以是另一个 Promise 实例,比如像下面这样:一个p2异步操作的结果是返回另一个p1异步操作
也就是说,p2的状态由p1决定,只有p1完成之后,p2才能完成,而且p1的结果会传递出去
以下两种写法是等价的
-------------------
//resolve参数是Promise对象 const p1 = new Promise((resolve, reject) => { resolve('7777777') }); const p2 = new Promise((resolve, reject) => { resolve(p1); }) p2.then(result => { console.log(result) }) // 7777777
---------------------------
//then中返回Promise对象 const p1 = new Promise((resolve, reject) => { resolve('7777777') }); const p2 = new Promise((resolve, reject) => { resolve(); }) p2.then(() => { return p1 }).then(result => { console.log(result) }) // 7777777
------------------
//后面还可以.then无限连下去,只不过结果为undefined p2.then(() => { return p1 }).then(result => { console.log(result) }).then(result => { console.log(result) }) // 7777777 // undefined
----------------------------------------
promise的异常处理
// 使用catch方法捕捉错误 function judgeNumber(num) { return new Promise((resolve, reject) => { if (typeof (num) === 'number') { resolve(num) } else { const err = new Error('error:类型不是number') reject(err) } }) } judgeNumber('2') .then(num => console.log(num)) .catch(err => console.log(err))
Promise.all :全部执行成功,才执行then
// Promise.all例子: 图片全部加载完成,页面才显示 const imgUrl1 = 'http://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/1901/vue/vue.png' const imgUrl2 = 'http://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/1901/webpack/webpack.png' const imgUrl3 = 'https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/video/2019_frontend/html_css/html.png' function getImage(url) { return new Promise((resolve, reject) => { const img = document.createElement('img') img.src = url img.onload = () => resolve(img) img.onerror = (err) => reject(err) }) } Promise.all([getImage(imgUrl1), getImage(imgUrl2), getImage(imgUrl3)]).then(images => { images.forEach(item => { document.body.appendChild(item) }) })
Promise.race:有一个执行成功,就执行then
// Promise.race例子:图片有一个加载完成就执行 Promise.race([getImage(imgUrl2), getImage(imgUrl1), getImage(imgUrl3)]).then(image => { document.body.appendChild(image) })
4、Generator
先预习之前的迭代器相关知识。先看一个简单的例子:
// 生成器的定义要在function后面加上* const say = function* () { yield 'a' yield 'b' yield 'c' } // 返回一个生成器对象 const fn = say(); // 调用生成器对象的next方法 console.log(fn.next()) //{ value: 'a', done: false } console.log(fn.next()) //{ value: 'b', done: false }
应用场景一:用generator来实现object的迭代器
let obj = { a: 1, b: 2, c: 3 } obj[Symbol.iterator] = function* () { for (const key of Object.keys(obj)) { yield obj[key] } } for (const value of obj) { console.log(value) } // 1 // 2 // 3
应用场景二:状态机,任何时候都只有一定数量种状态
const state = function* () { while(1) { yield 'success' yield 'fail' yield 'pending' } } const stateData = state() console.log(stateData.next()) //success console.log(stateData.next()) //fail console.log(stateData.next()) //pending console.log(stateData.next()) //success
应用场景三:长轮询,查询订单是否付款成功,前三次未付款,第四次付款成功
//用codeGen来模拟服务器的响应:未付款code=-1,已付款code=0 function* codeGen() { yield {code: -1} yield {code: -1} yield {code: -1} yield {code: 0} } const codeGen1 = codeGen(); // 长轮询,查询订单是否付款成功的功能,前三次未付款,第四次付款成功 function fn1() { return new Promise(resolve => { setTimeout( () => { console.log('查询中') resolve(codeGen1.next().value) },1000) }) } const getStatus = function* () { yield fn1() } function autoGetStatus() { const gen = getStatus() const status = gen.next() status.value.then(res => { if(res.code === 0) { console.log('用户付款成功') } else { console.log('暂未付款') setTimeout( () => autoGetStatus(), 500) } }) } autoGetStatus() // 查询中 // 暂未付款 // 查询中 // 暂未付款 // 查询中 // 暂未付款 // 查询中 // 用户付款成功
应用场景四:模拟异步
{ const ajax = function* () { console.log('start') yield setTimeout(() => { console.log('异步任务执行结束') }, 100) console.log('end') } const runAjax = ajax() runAjax.next() //第一个next用于启动生成器 runAjax.next() // start // end // 异步任务执行结束 } { const ajax = function* () { console.log('start') yield function (cb) { setTimeout(() => { console.log('异步任务结束') cb && cb() }, 1000) } console.log('end') } const runAjax = ajax() const first = runAjax.next() first.value(() => runAjax.next()) // start // end // 异步任务执行结束 }
5、async
先看下面的异步代码,等待1s后,同时输出:任务1、任务2、任务3
function fn1() { setTimeout(() => { console.log('任务1') },1000) } function fn2() { setTimeout(() => { console.log('任务2') },1000) } function fn3() { setTimeout(() => { console.log('任务3') },1000) } function init() { fn1() fn2() fn3() } init() // 等待1s后,同时输出:任务1、任务2、任务3
如果我们想先输出任务1、隔1s再输出任务2、隔1s后再输出任务3,怎么实现??
async结合Promise使用,可以将异步变为同步,而且让代码看起来更加直观
function login() { return new Promise<string>(resolve => { setTimeout(() => { resolve("token_KFDKLJKFLK"); }, 1000); }); } function getInfo(token: string) { return new Promise<string>(resolve => { setTimeout(() => { resolve(`${token}: 张三`); }, 1000); }); } function getList(token: string) { return new Promise<string>(resolve => { setTimeout(() => { resolve(`${token}: 获取列表成功`); }, 1000); }); } async function ini() { const token = await login(); return { info: await getInfo(token), list: await getList(token) }; } ini().then(res => { console.log(res); }); /** * 输出结果: * { info: 'token_KFDKLJKFLK: 张三', list: 'token_KFDKLJKFLK: 获取列表成功' } */
async和await踩坑:
//踩坑一: async中两个await会处于串行 //此处会让两个fetch处于串行 async function f() { const r1 = await fetch('http://...11'); const r2 = await fetch('http://...22'); } //改进:更高效的做法是用Promise.all async function f1() { const promise1 = fetch('http://...11'); const promise2 = fetch('http://...22'); const [r1, r2] = await Promise.all([promise1, promise2]); } // 踩坑二: 不要在aync中使用forEach和map方法 // 这里的forEach会立刻返回,并不会等到所有异步操作都执行完毕 async function f2() { [1, 2, 3].forEach(async (i) => { await someAsyncOperation(i); }); } f2(); //如果想让循环中所有异步操作一一完成后才继续执行,应该使用for循环 async function f3() { for (let i of [1, 2, 3]) { await someAsyncOperation(i); } console.log('done'); } //如果想让循环中所有操作都并发执行,一种更炫酷的写法是使用for await,这里的for循环依然会等到所有异步操作都完成之后才继续向后执行 async function f4() { const promises = [ someAsyncOperation(1), someAsyncOperation(2), someAsyncOperation(3) ]; for await (let result of promises){ } } // 踩坑三:不能在全局或普通函数中直接使用await关键字,await只能被用在异步函数中 async function f5() { await someAsyncOperation(); } f5(); //或者更简洁的写法: (async ()=>{ await someAsyncOperation(); })();
浙公网安备 33010602011771号