迭代与遍历的区别在于:迭代并不保证所有数据能取出、而遍历强调的是将所有数据取出。
js中迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。
//next方法返回的对象格式:
{
value:'值',
done:'是否迭代完成'
}
通过定义我们可以实现我们的自定义迭代器:
var arr = [1,2,3,4,5,6,7,8,9]
var iterator = {
i:0,
next(){
return {
value:arr[this.i++],
done:this.i >= arr.length
}
}
}
iterator.next() //{value: 1, done: false}
iterator.next() //{value: 2, done: false}
//实现遍历功能,完成所有数据的取出
var data = iterator.next()
while(!data.done){
console.log(data.value)
data = iterator.next()
}
通过迭代器实现非波拉契数列:
function createFeiBoIterator() {
let prev1 = 1, prev2 = 1, n = 1;
return {
next() {
let value;
if (n <= 2) {
value = 1
} else {
value = prev1 + prev2
}
prev1 = prev2;
prev2 = value;
n++;
return {
value,
done: false
}
}
}
}
function getFeiBoItem(n) {
let FeiBoIterator = createFeiBoIterator()
let arr = []
for (let i = 0; i < n; i++) {
arr.push(FeiBoIterator.next().value)
}
return arr[n-1]
}
getFeiBoItem(1) //1
getFeiBoItem(2) //1
getFeiBoItem(3) //2
getFeiBoItem(4) //3
getFeiBoItem(5) //5
MDN:迭代器和生成器
ES6规定,如果一个对象具有Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是可迭代的。
var obj = {
[Symbol.iterator](){
return {
next(){
value:1,
done:false
}
}
}
}
数组本质上就是一个可迭代对象,通过其原型可以看到Symbol.iterator的方法。
我们可以调用数组身上的迭代器方法。
除了数组,大多数类数组、伪数组本身也是可迭代对象。如获取dom返回的类数组。
String、Array、TypedArray、Map 和 Set 都是内置可迭代对象,因为它们的原型对象都拥有一个 Symbol.iterator 方法
for of的遍历机制是遍历可迭代对象。
var iterator = [1,2,3]
for(let item of iterator){
//iterator是一个可迭代的对象
//item是每次迭代得到的数据
}
//上面for of循环等价于下面的while循环
var iterObj = iterator[Symbol.iterator]()
var res = iterObj.next()
while(!res.done){
const item = res.value
//...
//for of循环体做的事情
//...
res = iterObj.next()
}
因为for of只能遍历可迭代对象,所以正常情况下一般对象是不可以使用for of循环的,不过我们可以给对象添加方法,实现对象的可迭代。
var obj = {
a: 1,
b: 2,
c: 3,
d: 4
}
for(const item of obj){
console.log(item) //TypeError: obj is not iterable
}
//给对象添加迭代器方法,实现for of的可遍历
var obj = {
a: 1,
b: 2,
c: 3,
d: 4,
[Symbol.iterator](){
const keysArr = Object.values(this)
let i=0;
return {
next(){
return {
value:keysArr[i++],
done:i>keysArr.length
}
}
}
}
}
最初生成器的产生背景在于解决迭代器的书写问题(如迭代器里书写next方法)。
生成器是由构造函数Generator(该构造函数只能系统内部调用)创建的对象。
生成器既是一个迭代器(具有next方法),又是一个可迭代对象(具有Symbol.iterator)。
function* test(){
console.log('run')
return 'end'
}
var gen = test() //函数内部没有运行
get.next() // {value: 'end', done: true}
function* test() {
console.log('start');
const y = yield 1;
console.log(y, 'between')
yield console.log('yeild run')
return 'end'
}
var gen = test() //函数内部没有运行
get.next() // 输出'start'; 返回{value: 1, done: false}
gen.next() // 输出undefined 'between' 'yeild run'; 返回{value: undefined, done: false}
gen.next() // 返回{value: 'end', done: true}
value为yeild或者return的数据,当函数运行结束,done为true。
使用生成器完成非波拉契数列的遍历:
function* createFeiBo() {
let prev1 = 1, prev2 = 1, value, n = 1;
while (true) {
if (n <= 2) {
value = 1
} else {
value = prev1 + prev2
}
prev1 = prev2
prev2 = value
n++
yield value
}
}
var gen = createFeiBo()
gen.next() //{value: 1, done: false}
gen.next() //{value: 1, done: false}
gen.next() //{value: 2, done: false}
gen.next() //{value: 3, done: false}
gen.next() //{value: 5, done: false}
生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中。
调用生成器的next方法时,可以传入参数,参数会交给yield表达式的返回值。(迭代器并不具备,是生成器特有)
next没有传参数的情况,yield返回值为undefined:
function* test() {
const y = yield 1;
console.log(y, 'between')
return 'end'
}
var gen = test() //函数内部没有运行
get.next() // 返回{value: 1, done: false}
gen.next() // 输出undefined 'between'; 返回{value:'end', done: true}
next传入参数的情况:
function* test() {
const y = yield 1;
console.log(y, 'between')
return 'end'
}
var gen = test() //函数内部没有运行
get.next() // 返回{value: 1, done: false}
gen.next('hello') // 输出'hello' 'between'; 返回{value:'end', done: true}
生成器函数里可以调用其他生成器,需要在yield后加*表示调用生成器,本质上可以看作是把其他生成器的内部代码替换当前的调用。
function* innerYield() {
console.log("inner yield1")
yield 'inner 1'
console.log("inner yield2")
yield 'inner 2'
console.log('return')
return 'inner end'
}
function* test() {
yield * innerYield()
const y = yield 1;
console.log(y, 'between')
return 'end'
}
//本质上生成器test等价于下面的代码
function* test() {
console.log("inner yield1")
yield 'inner 1'
console.log("inner yield2")
yield 'inner 2'
console.log('return')
const y = yield 1;
console.log(y, 'between')
return 'end'
}
var gen = test()
gen.next() //输出'inner yield1' 返回{value: 'inner 1', done: false}
gen.next() //输出'inner yield2' 返回{value: 'inner 2', done: false}
gen.next() //输出'return' 返回{value: 1, done: false}
gen.next() //输出undefined 'between' 返回{value: 'end', done: true}
``
生成器的暂停和恢复,主要是通过协程来实现。
协程是一种比线程更加轻量的存在,一个线程可存在多个协程,但是在线程上同时只能执行一个协程。
一个进程可以拥有多个线程,一个线程可以拥有多个协程。
如果从A协程启动B协程,A协程可以称为B协程的父协程。
function* test() {
console.log("inner yield1")
yield 'inner 1'
console.log("inner yield2")
yield 'inner 2'
console.log('return')
const y = yield 1;
console.log(y, 'between')
return 'end'
}
var gen = test()
gen.next()
console.log('out1')
gen.next()
console.log('out2')
gen.next()
gen.next(3)
通过封装一个run方法,参数为一个生成器,实现通过yield完成await的效果。
function run(task) {
const gen = task()
let res = gen.next()
handle()
// 递归处理
function handle() {
if (res.done) {
return
}
//Promise和普通数据
if (res.value[Symbol.toStringTag] === 'Promise') {
res.value.then(data => {
res = gen.next(data)
handle()
}).catch(error => {
gen.throw(error)
})
} else {
res = gen.next(res.value)
handle()
}
}
}
function* task() {
console.log("start")
const yield1 = yield 1
console.log(yield1)
const yield2 = yield 2
console.log(yield2)
const res = yield fetch('http://localhost:8080')//此处简单请求跨域,后端自行配置Access-Control-Allow-Origin:http://127.0.0.1:5500
console.log(res)
const yield3 = yield 3
console.log(yield3)
}
run(task)
作为redux异步操作的一个方案,saga中间件实现对异步的控制。
saga通过一系列指令实现对任务的监听,指令的前面需要yield修饰。
具体用法如下:
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// 创建saga中间件
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
// 通过调用run方法,参数传入生成器,启动saga任务。
sagaMiddleware.run(mySaga)
redux-saga官网
saga需要在yield后面使用一些合适的saga指令,如果放的是普通数据,saga不作任何处理。
每个指令本质是一个函数,该函数调用后返回一个指令对象。
take指令
take指令监听某个action,当action发生了变化,就会进行下一步处理,仅监听一次(take会阻塞后续执行)。
import { take } from 'redux-saga/effects'
import { actionTypes } from './action/xxx'
export default function*(){
const action = yield take(actionTypes.xxx)
console.log('触发了action',action) //当dispatch该action时触发,此处代码将运行
}
上面的代码当触发监听的action后会进入下一个yield的执行,而上面的代码只触发一次,为了实现持续监听,我们可以将代码用while包装。
import { take } from 'redux-saga/effects'
import { actionTypes } from './action/xxx'
export default function*(){
while(true){
const action = yield take(actionTypes.xxx)
console.log('触发了action',action) //当dispatch该action时触发,此处代码将运行
}
}
all指令
该函数传入一个数组,数组中放入生成器,saga会等待所有的生成器全部完成后才会进一步处理(all会阻塞后续执行)。
import { all } from 'redux-saga/effects'
import task1 from './task1'
import task2 from './task2'
export default function*(){
yield all([task1(),task2()])
console.log('saga都完成')
}
takeEvery指令
takeEvery指令的作用时不断监听某个action,当触发某个action后运行某个函数,takeEvery永远不会结束当前的生成器。(takeEvery不会阻塞后续执行,能够实现对多个action的监听)
// main.js
import { all } from 'redux-saga/effects'
import task1 from './task1'
import task2 from './task2'
export default function*(){
yield all([task1(),task2()])
}
// task1.js
// 通过takeEvery实现对多个action的监听
import { takeEvery } from 'redux-saga/effects'
export default function*(){
yield takeEvery('actionType1',genFun1)
yield takeEvery('actionType2',genFun2)
}
ES7引入async/await语法糖,灵感来自于通过生成器实现异步操作,而实际正是使用生成器去实现,更近一步讲,底层正是使用协程机制。
async/await提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力。
async function run() {
try {
console.log('start')
const res = await fetch('http://localhost:8080/')
console.log(res)
} catch (err) {
console.log(error)
}
}
run()
async/await异步处理的逻辑可以使用同步代码的方式去实现,并且支持try catch捕获错误。
async返回一个Promise,当async函数里有return时,return的值就是resolved的值。
await会默认帮我们调用resolve,代码如下:
//await 1等价于下面代码
let promise = new Promise((resolve,reject){
resolve(1)
})
//await fetch('https://www.baidu.com')
let promise = new Promise((resolve,reject){
fetch('https://www.baidu.com').then(res=>resolve(res))
})
async/await执行机制示意图:
async function run(){
console.log('start')
const res = await 1
console.log(res)
return 'end'
}
console.log('out start')
run()
console.log('out end1')
console.log('out end2')