迭代器
迭代器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
迭代器的作用:
1、为各种数据结构,提供一个统一的、简便的访问接口。
2、 使得数据结构的成员能够按某种次序排列
3、es6创造了一种新的遍历命令for…of循环,iterator接口主要供for…of消费。
迭代器的遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置。
//迭代器实现了Iterator接口,只要有实现了Iterator就可以使用for-of遍历
let arr=[1,2,3,4,5];
console.log(arr.keys());
console.log(arr.values());
console.log(arr.entries());
// keys values entries 当前变量是迭代器对象
// 迭代器对象实现了Iterator接口,只要实现了迭代接口就可以使用for-of 遍历
// let [a,b]=10; 报错10 is not iterable
let str='hello'; //实现了迭代器接口,可以遍历
// console.log(a,b);
for(let key of str){
console.log(key)
}
// 以前遍历字符串
let [...a]=str;
console.log(a)
let result=str.split("")
console.log(result);
for(i=0;i<str.length;i++){
console.log(str.charAt(i))
}
//遍历迭代器对象
let keys=arr.keys()
for(let key of keys){
console.log(key)
}
let values=arr.values();
for(let value of values){
console.log(value,'--------')
}
let entries=arr.entries();
for(let entry of entries){
console.log(entry)
}
while(!(result=keys.next()).done){
console.log(result)
}
/* for-of实现原理就是调用迭代器的next()方法,第一次调用将指针指向数据结构的第一个成员,依次调用依次指向,直到没有成员可以指向,done为true
迭代过程:创建一个指针对象,指向当前的数据结构起始位置;
第一次调用指针对象的next方法,指向数据结构的第一个成员;
第二次调用指针对象的next方法,指向数据结构的第二个成员;
直到done为true,指向数据结构的结束位置;*/
let keys=arr.keys();
let values=arr.values();
let entries=arr.entries()
console.log(keys.next())
console.log(keys.next())
console.log(keys.next())
console.log(keys.next())
console.log(keys.next())
console.log(keys.next())
console.log(entries.next())
具备tterator接口的数据结构:
array、map、set、string、typedArray、arguments、NodeList
forEach、for in、for of 三者的区别:
1、foreach更多的是用来遍历数组
2、for in 一般常用来遍历对象或json
3、for of 数组对象都可以遍历,遍历对象需要通过object.keys()
Set
Set类似于数组,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,用来生成 Set 数据结构。Set 构造函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
var set = new Set()
// 添加元素
set.add('hello')
set.add('world')
set.add('world')
console.log(set);//Set(2) { 'hello', 'world' }
// 删除元素
set.delete('hello')
console.log(set);//Set(1) { 'world' }
// 遍历
let keys = set.keys()
let values = set.values()
let entries = set.entries()
console.log(keys,values,entries);//[Set Iterator] { 'world' } [Set Iterator] { 'world' } [Set Entries] { [ 'world', 'world' ] }
set.forEach((value)=>{
console.log(value);//world
})
// 判断有没有某个成员
set.has('hello');//返回布尔值true或者false
// 清空set
set.clear()
// 返回set成员个数
set.size()
set的应用
let set = new Set([1,2,3,4,5,2,3])
console.log(set);//Set(5) { 1, 2, 3, 4, 5 }
// 数组去重
let arr = [1,2,3,4,2,3,5]
let result = new Set(arr)//Set(5) { 1, 2, 3, 4, 5 }
console.log(result);
// 将字符串转换为数组
let [...arr1] = 'hello'
// 将set集合转换为数组
let [...arr2] = result
console.log(arr2);//[ 1, 2, 3, 4, 5 ]
console.log([...new Set(arr)]);//[ 1, 2, 3, 4, 5 ]
let s = new Set()
s.add([1])
s.add([1])
console.log(s);//Set(2) { [ 1 ], [ 1 ] }
console.log(s.size);//2
Map集合
Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。Map 可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
let obj = {
name:'zhangsan',
age:13,
}
// 遍历键值对组成的数组
let arr = Object.entries(obj)
console.log(arr);//[ [ 'name', 'zhangsan' ], [ 'age', 13 ] ]'
// 将数组作为参数放到map中
let map = new Map(arr)
console.log(map);//Map(2) { 'name' => 'zhangsan', 'age' => 13 }
// 添加元素
map.set('1','1')
console.log(map);//Map(3) { 'name' => 'zhangsan', 'age' => 13, '1' => '1' }
// 删除元素
map.delete('name')
console.log(map);//Map(2) { 'age' => 13, '1' => '1' }
// 获取元素
console.log(map.get('age'));//13
// 遍历
let keys = map.keys()
let values = map.values()
let entries = map.entries()
console.log(keys,values,entries);//[Map Iterator] { 'age', '1' } [Map Iterator] { 13, '1' } [Map Entries] { [ 'age', 13 ], [ '1', '1' ] }
map.forEach((value,key)=>{
console.log(value,key);//13 age 1 1
})
map 和object的区别
-
意外的键:
Map 默认情况不包含任何键。只包含显式插入的键
一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。 -
键的类型:
一个 Map的键可以是任意值,包括函数、对象或任意基本类型。
一个Object 的键必须是一个 String 或是Symbol。 -
键的顺序:
Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。
一个 Object 的键是无序的。注意:自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。 -
Size
Map 的键值对个数可以轻易地通过size 属性获取
Object 的键值对个数只能手动计算 -
迭代
Map 是 iterable 的,所以可以直接被迭代。
迭代一个Object需要以某种方式获取它的键然后才能迭代。 -
性能
map在频繁增删键值对的场景下表现更好。
object在频繁添加和删除键值对的场景下未作出优化。
async异步函数
async函数是使用async关键字声明的函数。async函数是AsyncFunction构造函数的实例,并且其中允许使用await关键字。
async和await关键字让我们可以利用一种更加简洁的方式写出基于promise的异步行为就好像搭配使用了生成器和promise,而无需可以地链式调用promise。
await关键字只在async函数内有效。如果你在async函数体之外使用它,就会抛出语法错误 SyntaxError 。async/await的目的为了简化使用基于promise的API时所需的语法。
async/await的行为就好像搭配使用了生成器和promise。async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。
简单来说,是一个函数,是一个异步编程的解决方案,内部封装了generator函数,是一个语法糖,内部自带执行器,与await配合使用,异步编程,同步处理。
async function loadArticle() {
try {
let res = await axios.get('url')
console.log(res.data.data);
} catch(error) {
throw new Error('请求失败');
} finally {
console.log('最终执行');
}
}
loadArticle()
Generator
是ES6提供的一种异步编程解决方案,语法不同于普通函数;简单的把Generator 理解为一个状态机,封装了多个内部状态。执行Generator 函数会返回一个迭代器对象,可以通过调用迭代器next依次遍历Generator函数内部的每一个状态。
//总结Generator函数
// 异步编程解决方案 异步代码 同步编写
function* test(){
let res=yield 1;
console.log(res);//100 是下方next的参数
yield 2;
}
let result=test();//拿到的是迭代器对象
result.next();
result.next(100);//拿到的不是yield后面的状态描述
//Async
//异步函数同步编程
async function test(){
let res=await $.get();
console.log(res)
}
Promise
传统的异步编程的解决方案是使用回调函数,但是这样就会导致嵌套过深,产生回调地狱,而promois是一种异步编程解决方案,因为调用返回的是一个promise对象,避免了层层嵌套的回调函数,可以链式调用降低了操作难度。
在异步编程中,许多操作都会放在回调函数(callback)中,有时候需要拿到上一个异步操作的返回值再做第二次请求。
asyncOperation(data => {
// 处理 `data`
anotherAsync(data2 => {
// 处理 `data2`
yetAnotherAsync(() => {
// 完成
})
})
})
上面代码中,每增加一个异步请求,就会多添加一层回调函数的嵌套,过多的回调也就让我们陷入“回调地狱”,让代码变得不易阅读与维护。
引入promises之后的代码
promiseSomething()
.then(data => {
// 处理 `data`
return anotherAsync()
})
.then(data2 =>{
// 处理 `data2`
return yetAnotherAsync()
})
.then(() => {
// 完成
})
Promises 将嵌套的 callback,改造成一系列的.then的连缀调用,去除了层层缩进的糟糕代码风格
注意:promise本身不是异步,往往内部都是封装一个异步任务。
promise状态
1、pending(进行中):等待状态,比如正在进行网络请求或者定时器没有到时间
2、fulfilled(已成功):满足状态,当主动回调了resolve时,就该处于该状态,并且回调.then()
3、rejected(已失败):拒接状态,当主动回调了reject时,就处于该状态,并且回调。catch()
特点
- 待定状态的promise对象要么会通过一个值被兑现(fullfilled),要么通过一个原因(错误)被拒绝(rejected)
- 当这些情况发生之一发生时,我们用promise的ten方法排列起来的相关处理程序就会被调用。
promise.prototype.then
和promise.prototype.catch
方法返回的是promise,所以它们可以被链式调用
promise的三个实例方法
-
then() 方法
then是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数
then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因 -
catch 方法
当出现异常 则需要catch方法进行捕获
-finally()方法
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
创建promise
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
promise
.then(value=>{
//成功回调,接收的就是容器中的resolve函数
//容器中resolve函数传的是什么,这里接收就是什么
console.log(value)
})
.catch(error=>{
//失败的回调,接收的就是容器中的reject函数
console.log(error)
})
链式写法
// 封装promise api
function fzPromise(isFlag,data,err){
return new Promise((resolve,rejecte)=>{
if(isFlag){
resolve(data)
} else {
rejecte(err)
}
})
}
const promiseOne = new fzPromise(true,1,'操作失败')
const promiseTwo = new fzPromise(true,2,'操作失败')
const promiseThree = new fzPromise(flase,3,'操作失败')
// promiseOne 的resolve
.then((data)=>{
console.log(data);
// 当promiseOne读取成功 的那概念函数return的结果就能在后面的then中的回调函数接收到
return promiseTwo
},(err)=>{
console.log(err);
})
// 这里是promiseTwo的resolve
.then((data)=>{
console.log(data);
return promiseThree
},(err)=>{
// 因为promiseThree是flase,所以结果是reject传的'操作失败'
console.log(err);
})
//1
//2
//操作失败
promise 封装ajax
let ajax = function(url,params,type='post'){
return new Promise((resolve,reject)=>{
$.ajax({
type,
url,
data:params,
dataType:'json',
success(res){
resolve(res)
},
error(res){
reject('相应错误')
}
})
})
}
// 使用示例
ajax('url',{id:1}).then(res=>{
console.log(res);
})
promise其他静态方法
- Promise.resolve()
方法返回一个以给定值解析后的Promise
对象
定值会有以下的几种情况
1、如果这个值是一个promise,那么将返回这个promise
2、如果不是具有then()方法的对象,或者根本就不是对象,Promise.resolve()会返回一个新的Promise对象,状态为resolved
3、没有参数时,直接返回一个resolve状态的Promise对象
- Promise.reject()
Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected
const p = Promise.reject('出错了')
// 等同于
const p = new Promise((resolve,reject)=> reject('出错了'))
p.then(null,function(s){console.log(s);})
- any()方法
接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。
const pErr = new Promise((resolve, reject) => {
reject("总是失败");
});
const pSlow = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "最终完成");
});
const pFast = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "很快完成");
});
Promise.any([pErr, pSlow, pFast]).then((value) => {
console.log(value);
// pFast fulfils first
})
- all()方法
方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例
const p = Promise.all([p1, p2, p3]);
实例p的状态由p1,p2,p3决定,分两种情况:
只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数
只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数
- race() 方法
该方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.race([p1, p2, p3]);
1、只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变
2、率先改变的 Promise 实例的返回值则传递给p的回调函数
Promise和async-await的区别
promise
promise是异步编程的一种解决方案,比起传统的解决方案-----回调函数和事件更加强大;promise相当于一个容器,里面存放着成功和失败的结果,这些解决方案一旦生成时无法改变的
async-await
async-await 也是一种异步解决方案,它遵循的时Generator函数的与反唐,拥有内置的执行器,不需要额外调用函数,返回一个promise对象。
区别
1、Promise
的出现解决了传统的callback函数导致的“回调地狱”问题,但它的语法导致了它向纵向发展形成了一个回调链,遇到复杂的业务场景,这样的语法也是不美观的。而async-await
代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于“同步效果”的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
2、async-await与Promise
一样,是非阻塞的。
3、async-await是基于Promise实现的,可以说是改良版的promise,它不能用于普通的回调函数。