最近了解的一些问题(前端)

193 阅读5分钟

循环 / 遍历 / 迭代 / 递归

表示重复的含义这个动作有很多比如:循环(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 语句)位置

image-20220605212339029

注意:如果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))

image-20220605230654599

注意:第二次执行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)
  }

参考资料