循环 / 遍历 / 迭代 / 递归
表示重复的含义这个动作有很多比如:循环(loop)递归(recursion)遍历(traversal)迭代(iterate)
循环算是最基础的概念,重复执行一段代码都可以称为循环,大部分的递归 遍历 迭代都是循环
递归是重复调用函数自身实现循环,将复杂情况逐步转换为基本情况
遍历是观察或者获取集合中的元素的一种做法,按顺序访问非线性结构的每一项
迭代是函数内部某段代码实现循环,按顺序访问线性结构中的每一项
非线性结构:集合(Set)字典(Map)(Object 是特殊的字典类型)
// 集合
const set = new Set([1, 2, 3, 4])
// 字典
const map = new Map([
['name', '吃花椒的喵'],
['age', '男']
])
参考文章
线性结构:普通数组/栈结构
const list = [1, 2, 3, 4]
而迭代与普通循环的区别是:循环代码中参与的运算的变量是保存结果的变量,当前保存的结果作为下一次循环计算的初始值
普通循环
const list = [1, 2, 3, 4]
for (let i = 0, len = list.length; i < len; i++) {
console.log(list[i])
}
迭代: 重复一定的算法,达到想要的目的
function iteration(x){
let sum = 1;
for(x; x >= 1; x--){
sum *= x;
}
}
iteration(4) // sum 4 12 24 24
举例 forEach 实现
Array.prototype.forEach = function(callback, thisAry) {
const len = this.length
// 循环数组,利用call方法调用传入的回调函数并改变它的this指向
for (let index = 0; index < len; index++) {
callback.call(thisAry, this[index], index, this)
}
}
迭代器
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就需要一种统一的接口机制,来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员,之前只有数组和NodeList等比较特殊的类数组才拥有该特性) 。
1.Iterator的作用:
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列
- ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
2.原生具备iterator接口的数据(可用for of遍历)
- Array
- Set容器
- Map容器
- String
- 函数的 arguments 对象
- NodeList 类数组
注意点:for of 不支持遍历普通对象
var obj = { name: '吃花椒酱的喵', a ge: 23 }
for (let i of obj) {
console.log(i) // Uncaught TypeError: obj is not iterable
}
当使用扩展运算符(...)或者对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法
for of 和 for in 的区别
for in 用来可枚举数据(对象,数组,字符串)键名,在遍历数组时有可能会把原型链的属性([自定义]的属性)遍历出来,遍历数组的时候可能不是按照顺序的
for of 用来可迭代数据 (Map, Set,NodeList)键值,可以避免for in的问题
Promise
首先明白两个概念,同步和异步
同步:连续的执行
异步:非连续执行
例如:
function fn() {
console.log(1)
setTimeout(() => {
console.log(2)
}, 1000)
console.log(3)
}
fn()
如果javascript 是同步的那么话打印顺序为 1 2 3
但实际上因为是异步的原因,打印顺序为 1 3 2
Promise 是异步编程的一种解决方案,它是一个容器,里面保存着某个未来才会结束的事件(通常是个异步操作)
从语法上说,它是一个对象,它的状态不受外界影响,一旦状态改变就不会在改变,它有三种状态,pending(进行中)fulfilled(已成功)rejected(已失败)所以他只能有两种情况 从pending 到 fulfilled 或者 pending 到 rejected,只要这两种情况发生了,状态就凝固了,会一直保持这个结果,这时候就称为resolved(已定型)
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
p.then((res) => {
console.log(res)
}).then(() => {
setTimeout(() => {
console.log(2)
}, 2000)
}).then(() => {
console.log(3)
})
// 1 3 2
Promise 简单版本实现
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
constructor(executor) {
// 当前状态
this.status = PENDING
// 存放成功状态的值
this.value = undefined
// 存放失败状态的值
this.reason = undefined
// 成功的回调函数
this.onResolvedCallbacks = []
// 失败的回调函数
this.onRejectedCallbacks = []
let resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
this.onResolvedCallbacks.forEach(fn => fn())
}
}
let reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
if (this.status === PENDING) {
// 需要等待状态确定之后才执行then里面的回调
this.onResolvedCallbacks.push(() => onRejected(this.value))
this.onRejectedCallbacks.push(() => onRejected(this.reason))
}
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
}
}
async / await
一句话总结 Generator 函数的语法糖, 返回的是一个Promise 对象
说一个比较常见的例子:按照顺序完成异步操作(程序运行环境判断)
function request(urls) {
const textPromises = urls.map(url => {
return fetch(url).then(res => res.text())
})
// 按顺序输出
textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromises).then(text => console.log(text))
}, Promise.resolve)
}
async function request(urls) {
for (const url of urls) {
const res = await fetch(url)
console.log(await res.text())
}
}
async function request(urls) {
const textPromises = urls.map(url => {
const res = await fetch(url)
return res.text()
})
for (const res of textPromises) {
console.log(await res)
}
}
Generator
Generator 函数它是一个状态机,封装多个内部状态
特征
- Function 关键字与函数名之间有个 *
- 函数体内部使用yield表达式,定义不同的内部状态
- 执行 Generator 函数会返回一个遍历器对象,可调用 next 方法进行遍历,遍历的结果是Generator 函数里面通过函数里面通过yield 或者是return 定义的值,按照顺序输出
Generator 函数的调用方法和普通函数一样,不同的是该函数不是立即执行,返回的结果也不是函数的运行结果,而是一个指向内部状态的指针对象(也就是遍历对象 Iterator Object),下一步调用遍历器对象的next方法,使得指针移向下一个状态。每次调用next 方法,内部指针函数从函数头或者上一次停下来的地方开始执行,直到遇到下一个yield表达式(或者return 语句)位置
注意:如果return 语句之后还有yield语句,那么return之后所有的yiely语句都将不会被遍历
const list = [1, 2, 3, 4, 5]
function * fn() {
for (const val of list) {
yield console.log(val) // 不会立即执行,next方法将指针移动到这句才会执行
}
}
const g = fn()
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
yiely 表达式使用规范
- yiely 表达式只能在 Generator 函数主体里面
- yiely 表达式如果用在另一个表达式之中,必须放在圆扣号里面
next函数
yiely 表达式本身没有返回值,next方法可以带一个参数,该参数会被当作上一个yiely表达式的返回值
第一个next 传递的参数会被忽略
function * f() {
for (var i = 0; true; i++) {
console.log(99)
var rest = yield i
console.log(88)
if (rest) i = -1
}
}
const g = f()
console.log( g.next())
console.log( g.next())
console.log( g.next(true))
注意:第二次执行next方法没有打印 88 出来
for of 与生成器
for of 可以自动遍历 Gentrator 函数运行时生成的Iterator,不需要手动调用next方法
function * f() {
yield 1
yield 2
yield 3
yield 4
yield 5
return 6
}
for (const val of f()) {
console.log(val)
}
// 依次打印 1 2 3 4 5
一旦next方法返回值中done 属性为true 循环就会终止
yiely * 表达式
如果在 Generator 函数内部调用另一个 Generator 函数,需要在函数体内部自己手动完成遍历
function *fn1() {
yiely 1;
yiely 2;
}
function * fn2() {
yiely 1;
for (const val of fn1()) {
console.log(val)
}
yiely 2;
}
使用yield 表达式
function *fn1() {
yiely 1;
yiely 2;
}
function * fn2() {
yiely 1;
yiely* fn1()
yiely 2;
}
注意点
function *fn1() {
yiely* [1,2,3,4];
}
const g1 = fn1()
g1.next() // { value: 1, done: false }
function *fn2() {
yiely* 'jen'];
}
const g2 = fn2()
g2.next() // { value: 'j', done: false }
举个例子
function * fn() {
let [prev, curr] = [0, 1]
let count = 0
for (;;) {
yield [count, curr];
[prev, curr] = [curr, prev + curr]
count ++
}
}
for (const [count, curr] of fn()) {
if (count > 10) break
console.log(curr)
}