js复习

247 阅读28分钟

js相关

apply call bind 使用场景,区别

  • call/apply改变了函数的this上下文后马上执行该函数,bind不会立马调用
  • apply是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。
  • call从第2~n的参数都是传给fun的。
  • call/apply 返回fun的执行结果
  • bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。 call/apply/bind的核心理念:借用方法

借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存。

function Cat(name){
    this.name = name
    this.sayHi = function(age, favor){
       console.log("this.name + "," + age +"," + favor) 
    }
}
 
function Mouse(name){
    this.name = name
}
 
var cat = new Cat("Tom")
cat.sayHi(12, "fish")   // Tom, 12 , fish
var mouse = new Mouse("Jerry", 8, "cheese")
mouse.sayHI(10, "cheese")  // Uncaught TypeError: mouse.sayHI is not a function
 
cat.sayHi.apply(mouse, [10, "cheese"])  // Jerry,10 , cheese
cat.sayHi.call(mouse, 10, "cheese")  // Jerry,10 ,cheese
cat.sayHi.bind(mouse, 10, "cheese")()  // Jerry,10 ,cheese

常用加密方法

一、MD5加密

介绍: MD5中文含义为信息-摘要算法5,就是一种信息摘要加密算法,可以将数据转译为另一固定长度值

特点:
1.压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2.容易计算:从原数据计算出MD5值很容易。
3.抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4.强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。 使用:下包后引入

一、 sha.js

介绍:  项目中经常会用的方式,使用起来简单方便
使用方式:  项目中直接引入sha.js包 使用:下包后引入

二、base64加密

介绍:base64是一种加密算法,有着广泛的应用和支持,但却是当今最弱的编码标准之一。它主要是对明文转换后的二进制序列做处理,使之变为不能被人直接识别的形式。
特点
1.使用最广泛
2.简单易上手
3.可以将图片转译存储
4.编码之后的结果,只有64个字符 az AZ 0~9 / + 再加上一个辅助字符 =

使用方式:直接调用btoa方法转换成base64方法,使用atob方法可解码

function Base64() {
  return (
    <>
      <h3>MD5加密</h3>
      <br></br>
      {/* btoa用于加密,atob用于解密 */}
      <h3>加密前:18888888888 加密后:{window.btoa(18888888888)}</h3>
      <h3>解码后:{window.atob(window.btoa(18888888888))}</h3>
    </>
  )
}
export default Base64

节流和防抖的实现

防抖 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

生活中的实例:  如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行事件监听器),这时如果又有人进电梯了(在10秒内再次触发该事件),我们又得等10秒再出发(重新计时)。

节流 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

设置一个定时器,多少秒后触发,如果在定时时间内又点了,则清除之前定时器,重新计时

生活中的实例: 我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源。

设置一个时间间隔,记录开始时间和触发时间,判断时间差是否到大于时间间隔

方法和函数

函数是带有名称(named)和参数的JavaScript代码段,可以一次定义多次调用,function 方法是将函数对象合写在一起时,函数就变成了 “方法”(method)// 当函数赋值给对象的属性,我们称为"方法"

var obj = {
   name : '张三',
   age : 18
   fun : function(){
       console.log(this.name) //这里的this指向的是obj这个对象
     }//fun就成了该对象的一个方法
 }
	     
或者这样
var abc = function(){
    ......
}

函数和方法本质上是一样的,只不过方法是函数的特例,是将函数值赋给了对象

方法也是函数,只是比较特殊

常用高阶函数和循环

map

  • map()返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
  • map()不会对空数组进行检测。
  • map()如果改变的基本类型和整个对象的话,无法改变原数组,但是改变的是对象某个值的话,可以改变原数组
let newArr = arr.map((item) => { return item * 2; });

filter

  • filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
  • filter()不会对空数组进行检测。
  • filter()不会改变原始数组。
let newArr = arr.filter((item) => { return item < 100; });

filter妙用

.filter(item => item) 可以去除数组里面的null,'',undefined

          var arr = ['1', '2', undefined, '3.jpg', undefined, '', null]
          var newArr = arr.filter(item => item)
          console.log(newArr)
          //  ['1', '2', '3.jpg']

foreach

  • forEach()方法类似于 map(),传入的函数不需要返回值,并将元素传递给回调函数。
  • forEach()不会返回新的数组,总是返回undefined.
  • 如果改变的基本类型和整个对象的话,无法改变原数组,但是改变的是对象某个值的话,可以改变原数组 如果改变的基本类型和整个对象的话,无法改变原数组,但是改变的是对象某个值的话,可以改变原数组

sort

  • sort()方法用于对数组的元素进行排序。
  • sort()会修改原数组。

sort() 方法接受一个可选参数,用来规定排序顺序,必须是函数。 sort() 方法接受一个可选参数,用来规定排序顺序,必须是函数。

如果没有传递参数, sort() 方法默认把所有元素先转换为 String 再排序 ,根据 ASCII 码进行排序。

  • 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
  • 若 a 等于 b,则返回 0
  • 若 a 大于 b,则返回一个大于 0 的值。

x参数代表的是数组里面第一索引位元素,y参数代表与x相邻的后一位元素,当x>y时,返回1,则数组会按升序(从小到大的顺序)进行排序,返回-1,则按降序进行排序

some

  • some() 方法用于检测数组中的元素是否满足指定条件。

  • some() 方法会依次执行数组的每个元素。

  • 如果有一个元素满足条件,则表达式返回 true, 剩余的元素不会再执行检测。

  • 如果没有满足条件的元素,则返回 false

  • some()不会对空数组进行检测。

  • some()不会改变原始数组。

let arr = [10, 20, 1, 2]; 
let result = arr.some((item) => { return item > 10; }); 
console.log(result);//true

every

跟some差不多,不同的是,判断全部,some是判断一个元素满足就行

reduce

  • reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 
let sum = arr.reduce((prev, current) => {
return prev + current; 
}, 0); 
console.log(sum); //55

reduceRight跟reduce是反的从末尾向前

find

  • find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined
  • find()不会对空数组进行检测。
  • find()不会改变原始数组。
let arr = [11, 20, 51, 82]; 
let result = arr.find((item) => { return item > 50; }, 0); 
console.log(result);//51

findIndex

跟find一样,查到第一个满足条件的,没有返回-1

for of循环

for...of 可以迭代数组、类数组以及任何可以迭代的对象(mapssetsDOM集合),并且,for...of 的语句还很短。

const products = ['oranges''apples'];
for (const product of products) {
  console.log(product);
}
// 'oranges'
// 'apples'

for...of 循环遍历 products 的每一项。迭代项被赋值给变量 product.

class(类)和构造函数

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

class可以看成一个语法糖

class Point {
  constructor (x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

上面代码定义了一个“类”,可以看到里面有一个constructor()方法,这就是构造方法,而this关键字则代表实例对象,类的数据类型就是函数,类本身就指向构造函数。toString方法是定义到Point.prototype上的,

constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。

类的所有实例共享一个原型对象。就是说不管new了多少次,protoType指的都是一个对象

用new操作符调用函数、构造函数、类和实例

一、用new操作符调用函数 JS规定,使用new操作符调用函数会进行“四步走”

1)函数体内会自动创建出一个空白对象

2)函数的上下文(this)会指向这个对象

3)函数体内的语句会执行

4)函数会自动返回上下文对象,即使函数没有return语句

二、举例

第1步:函数体内会自动创建出一个空白对象

第2步:函数的上下文(this)会指向这个对象

function fun() {  
	//在函数体开头,自动创建了一个空白对象{}
	//this指向空白对象
    this.a = 3;
    this.b = 5;
}
var obj = new fun();
console.log(obj);

第3步:执行函数体中的语句

this.a = 3;
this.b = 5;
//由以上this是{},即 最后语句为{a: 3, b: 5}

第4步:函数会自动返回上下文对象,即使函数没有return语句

最后结果:{a: 3, b: 5} 总结:上下文规则

三、构造函数 1.构造函数:在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。构造函数首字母一般大写。

function People(name, age, sex) {  //接收三个参数
    this.name = name;
    this.age = age;
    this.sex = sex;    //this上绑定同名属性
}
var xiaoming = new People('小明', 12,'男');  //传入三个参数
var xiaohong = new People('小红', 10,'女');
var xiaogang = new People('小刚', 13,'男');  

(1)用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它

(2)构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化

(3)构造函数必须用new关键字调用,否则不能正常工作,正因如此,开发者约定构造函数命名时首字母要大写

(4)一个函数是不是构造函数,要看它是否用new调用,而至于名称首字母大写,完全是开发者的习惯约定

(5)构造函数中的this不是函数本身,this是创建的新对象,将来会成为小明、小红、小刚等 四、类和实例:实例是具体的对象

generator函数

generator函数跟普通函数在写法上的区别就是,多了一个星号*,并且只有在generator函数中才能使用yield,什么是yield呢,他相当于generator函数执行的中途暂停点,比如下方有3个暂停点。而怎么才能暂停后继续走呢?那就得使用到next方法next方法执行后会返回一个对象,对象中有value 和 done两个属性

  • value:暂停点后面接的值,也就是yield后面接的值
  • done:是否generator函数已走完,没走完为false,走完为true
    function fn (num) {
      console.log(num)
      return num
    }
    function* gen () {
      yield fn(1)
      yield fn(2)
      return 3
    }
    const g = gen()
    console.log(g.next())
    // 1
    // { value: 1, done: false }
    console.log(g.next())
    // 2
    //  { value: 2, done: false }
    console.log(g.next())
    // { value: 3, done: true }

next传参

generator函数可以用next方法来传参,并且可以通过yield来接收这个参数,注意两点

  • 第一次next传参是没用的,只有从第二次开始next传参才有用
  • next传值时,要记住顺序是,先右边yield,后左边接收参数
  fn (nums) {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(nums * 2)
        }, 1000)
      })
    },
    * gen () {
      const num1 = yield this.fn(1)
      const num2 = yield this.fn(num1)
      const num3 = yield this.fn(num2)
      return num3
    }
    const g = this.gen()
    const next1 = g.next()
    // 这里的next1.value.then取的是 yield this.fn(1)的promise数据,然后调了 promise的then
    next1.value.then(res1 => {
      console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
      console.log(res1) // 1秒后同时输出 2

      const next2 = g.next(res1) // 传入上次的res1
      next2.value.then(res2 => {
        console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false }
        console.log(res2) // 2秒后同时输出 4

        const next3 = g.next(res2) // 传入上次的res2
        next3.value.then(res3 => {
          console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
          console.log(res3) // 3秒后同时输出 8

          // 传入上次的res3
          console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
        })
      })
    })

宏任务和微任务

js分为同步任务和异步任务,而异步任务分为2种,宏任务和微任务,

  • 常见的宏任务有:
  • 宏任务:setTimeout,setInterval,Ajax,DOM事件
  • 微任务:Promise.then,async/await

因为Promise定义之后便会立即执行,其后的.then()是异步里面的微任务。

而setTimeout()是异步的宏任务。

微任务要比宏任务执行的要早

微任务 》 DOM渲染 》 宏任务

setTimeout的回调不一定在指定时间后能执行。而是在指定时间后,将回调函数放入事件循环的队列中。

如果时间到了,JS引擎还在执行同步任务,这个回调函数需要等待;如果当前事件循环的队列里还有其他回调,需要等其他回调执行完。

另外,setTimeout 0ms 也不是立刻执行,它有一个默认最小时间,为4ms。

// node
setTimeout(() => {
  console.log('setTimeout')
}, 0)
setImmediate(() => {
  console.log('setImmediate')
})

因为取出第一个宏任务之前在执行全局Script,如果这个时间大于 4ms,这时 setTimeout 的回调函数已经放入队列,就先执行 setTimeout;如果准备时间小于 4ms,就会先执行 setImmediate。

先执行宏任务再执行当前宏任务内的微任务(在此过程中将遇到的微宏任务依次推入到eventqueue,
先进入eventqueue的任务会被先执行),执行完当前宏任务内的所有微任务之后,再执行下一个宏任务,
以此循环,当然在微宏任务中同步任务会优先执行,而整体的scrpt标签就是第一个执行的宏任务

setImmediate

该方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数,

  setImmediate(() => {
        this.$set(this.$refs.formBox.formData[4], 'disabledDate', this.disableDate)
        this.$set(this.$refs.formBox.formData[1], 'disabledDate', this.disableDate1)
      })

promise原理

promise

Promise的初始状态是pending

  • 1、执行了resolve,Promise状态会变成fulfilled

  • 2、执行了reject,Promise状态会变成rejected

  • 3、Promise只以第一次为准,第一次成功就永久fulfilled,第一次失败就永远状态为rejected

  • 4、Promise中有throw的话,就相当于执行了reject

     class MyPromise {
      // eslint-disable-next-line space-before-function-paren
      constructor(executor) {
        // executor是个方法
        // 初始化值
        this.initValue()
        // 初始化this指向
        this.initBind()
        try {
          // 执行传进来的函数,而this.resolve, this.reject这两个函数作为了传参
          executor(this.resolve, this.reject)
        } catch (e) {
          this.reject(e)
        }
      }

      initBind () {
        // 初始化this
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
      }

      initValue () {
        // 初始化值
        this.PromiseResult = null // 初始化值
        this.PromiseState = 'pending' // 状态
        // then中传的参,可能多次调用then,所以用数据存
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
      }
      

resolve

      resolve (value) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行resolve,状态变为fulfilled
        this.PromiseState = 'fulfilled'
        // 终值为传进来的值
        this.PromiseResult = value
        // 执行保存的成功回调
        // onFulfilledCallbacks是各个函数组成的数组,所以shift()把数组第一个元素删除并返回,则是把数组的第一个函数删除返回了
        // (this.PromiseResult)则是传给函数的参
        while (this.onFulfilledCallbacks.length) {
          this.onFulfilledCallbacks.shift()(this.PromiseResult)
        }
      }

reject

      // then接收两个回调,一个是成功回调,一个是失败回调
      // 当Promise状态为fulfilled执行成功回调,为rejected执行失败回调
      // 如resolve或reject在定时器里,则定时器结束后再执行then
      // then支持链式调用,下一次then执行受上一次then返回值的影响
      reject (reason) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行reject,状态变为rejected
        this.PromiseState = 'rejected'
        // 终值为传进来的reason
        this.PromiseResult = reason
        // 执行保存的失败回调
        // onFulfilledCallbacks是各个函数组成的数组,所以shift()把数组第一个元素删除并返回,则是把数组的第一个函数删除返回了
        // (this.PromiseResult)则是传给函数的参
        while (this.onFulfilledCallbacks.length) {
          this.onFulfilledCallbacks.shift()(this.PromiseResult)
        }
      }

then

  • then接收两个回调,一个是成功回调,一个是失败回调

  • 当Promise状态为fulfilled执行成功回调,为rejected执行失败回调

  • 如resolve或reject在定时器里,则定时器结束后再执行then

  • then支持链式调用,下一次then执行受上一次then返回值的影响

then中加定时器的情况

也就是在这定时器时间内,我们可以先把then里的两个回调保存起来,然后等到定时器时间过后,执行了resolve或者reject,咱们再去判断状态,并且判断要去执行刚刚保存的两个回调中的哪一个回调。我们怎么知道当前时间还没走完甚至还没开始走呢?其实很好判断,只要状态是pending,那就证明定时器还没跑完,因为如果定时器跑完的话,那状态肯定就不是pending,而是fulfilled或者rejected

那是用什么来保存这些回调呢?建议使用数组,因为一个promise实例可能会多次then,用数组就一个一个保存了

      then (onFulfilled, onRejected) {
        // 参数校验,确保一定是函数,如果不是函数,onFulfilled会变成val => {return val },onRejected会变成reason => { throw reason }抛错
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
        // new MyPromise的话,里面的语句会立即执行
        var thenPromise = new MyPromise((resolve, reject) => {
          // 这里是定义的一个方法,cb是传参
          const resolvePromise = cb => {
            try {
              const x = cb(this.PromiseResult)
              if (x === thenPromise) {
                // 不能返回自身哦
                throw new Error('不能返回自身。。。')
              }
              if (x instanceof MyPromise) {
                // 如果返回值是Promise
                // 如果返回值是promise对象,返回值为成功,新promise就是成功
                // 谁知道返回的promise是失败成功?只有then知道
                x.then(resolve, reject)
              } else {
                // 非Promise就直接成功
                resolve(x)
              }
            } catch (err) {
              // 处理报错
              reject(err)
              throw new Error(err)
            }
          }
          // 这里的状态是继承的上一次resolve或者reject的状态
          if (this.PromiseState === 'fulfilled') {
            // 如果onFulfilled或者onRejected返回的是自己,那上面的cb(this.PromiseResult)
            // 如果当前为成功状态,执行第一个回调,this.PromiseResult为resolve或者reject执行完的值
            resolvePromise(onFulfilled)
          } else if (this.PromiseState === 'rejected') {
            // 如果当前为失败状态,执行第二哥回调
            resolvePromise(onRejected)
            // 因为定时器这时候还没执行resolve,或者reject,所以状态为pending
          } else if (this.PromiseState === 'pending') {
            // promise定时器的处理
            // 如果为待定则同时保存两个回调,这里的回调是then传进来的
            this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
            this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected))
          }
        })
        // 返回这个包装的Promise
        return thenPromise
      }

all

      // 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
      // 如果所有Promise都成功,则返回成功结果数组
      // 如果有一个Promise失败,则返回这个失败结果
      static all (promises) {
        // 接受成功数组
        const result = []
        let count = 0
        // 返回一整个新promise
        return new MyPromise((resolve, reject) => {
          // 定义一个函数
          const addData = (index, value) => {
            // 把传入的参,加到成功数组里面
            result[index] = value
            count++
            // 判断传入的promise数组是否都传入了,都传了就返回成功结果数组
            if (count === promises.length) resolve(result)
          }
          // 对传进来的值进行循环
          promises.forEach((promise, index) => {
            // 判断是否为promist,如果不是直接当成成功项
            if (promise instanceof MyPromise) {
              promise.then(res => {
                addData(index, res)
              }, err => reject(err))
            } else {
              addData(index, promise)
            }
          })
        })
      }

race

      // 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
      // 哪个Promise最快得到结果,就返回那个结果,无论成功失败
      static race (promises) {
        return new MyPromise((resolve, reject) => {
          promises.forEach(promise => {
            if (promise instanceof MyPromise) {
              promise.then(res => {
                resolve(res)
              }, err => {
                reject(err)
              })
            } else {
              resolve(promise)
            }
          })
        })
      }

any

      // 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
      // 如果有一个Promise成功,则返回这个成功结果
      // 如果所有Promise都失败,则报错
      static any (promises) {
        return new Promise((resolve, reject) => {
          let count = 0
          promises.forEach((promise) => {
            promise.then(val => {
              resolve(val)
              // eslint-disable-next-line handle-callback-err
            }, err => {
              count++
              if (count === promises.length) {
                reject(new Error('All promises were rejected'))
              }
            })
          })
        })
      }

allSettled

      //  接收一个Promise数组,数组中如有非Promise项,则此项当做成功
      // 把每一个Promise的结果,集合成数组,返回
      static allSettled (promises) {
        return new Promise((resolve, reject) => {
          const res = []
          let count = 0
          const addData = (status, value, i) => {
            res[i] = {
              status,
              value
            }
            count++
            if (count === promises.length) {
              resolve(res)
            }
          }
          promises.forEach((promise, i) => {
            if (promise instanceof MyPromise) {
              promise.then(res => {
                addData('fulfilled', res, i)
              }, err => {
                addData('rejected', err, i)
              })
            } else {
              addData('fulfilled', promise, i)
            }
          })
        })
      }
    }

超时报错

就是给定一个时间,如果接口请求超过这个时间的话就报错

  /**
 * 模拟延时
 * @param {number} delay 延迟时间
 * @returns {Promise<any>}
 */
    function sleep (delay) {
      // eslint-disable-next-line promise/param-names
      return new Promise((_, reject) => {
        // eslint-disable-next-line prefer-promise-reject-errors
        setTimeout(() => reject('超时喽'), delay)
      })
    }

    /**
 * 模拟请求
 */
    function request () {
      // 假设请求需要 1s
      return new Promise(resolve => {
        setTimeout(() => resolve('成功喽'), 1000)
      })
    }

    function timeoutPromise (requestFn, delay) {
      // 如果先返回的是延迟Promise则说明超时了
      return Promise.race([requestFn(), sleep(delay)])
    }
    timeoutPromise(request, 500)

并发控制

同时只执行几个接口请求,等前面的接口执行完毕再执行后面的接口

    class Scheduler {
      constructor(limit) {
        // 所有请求接口保存数组
        this.queue = []
        // 同时执行个数
        this.limit = limit
        this.count = 0
      }

      add (time, order) {
        const promiseCreator = () => {
          return new Promise((resolve, reject) => {
            // 因为是模拟的接口,所以加了定时器,正常是接口请求成功后resolve()
            setTimeout(() => {
              console.log(order)
              resolve()
            }, time)
          })
        }
        this.queue.push(promiseCreator)
      }

      taskStart () {
        // 能同时执行多少个函数,就同时执行多少个递归
        for (let i = 0; i < this.limit; i++) {
          this.request()
        }
      }

      // 递归函数
      request () {
        if (!this.queue.length || this.count >= this.limit) return
        // count是正在执行接口数量,用来限制一次只发2个接口请求的
        this.count++
        // shift()()把数组第一条数组摘出,并且执行
        this.queue.shift()().then(() => {
          // 执行完毕后在执行接口数量-1,并且回调自己
          this.count--
          this.request()
        })
      }
    }
    const scheduler = new Scheduler(2)
    const addTask = (time, order) => {
      scheduler.add(time, order)
    }
    addTask(100, '1')
    addTask(100, '2')
    addTask(300, '3')
    addTask(400, '4')
    scheduler.taskStart()

await async

async/await的用处就是:用同步方式,执行异步操作

async async函数返回的是一个Promise对象,但是值却是undefined,那要怎么才能使值不是undefined呢?很简单,函数有return返回值就行了,就有值了,并且还能使用then方法进行输出

原理也是利用的generator的next传参

    function generatorToAsync (generatorFn) {
      return function () {
        const gen = generatorFn.apply(this, arguments) // gen有可能传参

        // 返回一个Promise
        return new Promise((resolve, reject) => {
          function go (key, arg) {
            let res
            try {
              res = gen[key](arg) // 这里有可能会执行返回reject状态的Promise
            } catch (error) {
              return reject(error) // 报错的话会走catch,直接reject
            }

            // 解构获得value和done
            const { value, done } = res
            if (done) {
              // 如果done为true,说明走完了,进行resolve(value)
              return resolve(value)
            } else {
              // 如果done为false,说明没走完,还得继续走

              // value有可能是:常量,Promise,Promise有可能是成功或者失败
       return Promise.resolve(value).then(val => go('next', val),err => go('throw', err))
            }
          }
          go('next') // 第一次执行
        })
      }
    }

    const asyncFn = generatorToAsync(gen)
    
    asyncFn().then(res => console.log(res))
    function* gen () {
      const num1 = yield fn(1)
      console.log(num1) // 2
      const num2 = yield fn(num1)
      console.log(num2) // 4
      const num3 = yield fn(num2)
      console.log(num3) // 8
      return num3
    }

    const genToAsync = generatorToAsync(gen)
    const asyncRes = genToAsync()
    console.log(asyncRes) // Promise
    asyncRes.then(res => console.log(res)) // 8

generator其实就是JS在语法层面对协程的支持,真正支持与否看运行时环境,比如高版本的node就是支持的。协程就是主程序和子协程直接控制权的切换,并伴随通信的过程,那么,从generator语法的角度来讲,yield,next就是通信接口,next是主协程向子协程通信,而yield就是子协程向主协程通信

原型,原型链

1、引用类型,都具有对象特性,即可自由扩展属性。

2、引用类型,都有一个隐式原型 __proto__ 属性,属性值是一个普通的对象。

3、引用类型,隐式原型 __proto__ 的属性值指向它的构造函数的显式原型 prototype 属性值。

4、当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 __proto__(也就是它的构造函数的显式原型 prototype)中寻找。

首先, obj 对象并没有 toString 属性,之所以能获取到 toString 属性,是遵循了第四条规则,从它的构造函数 Object 的 prototype 里去获取。

最后一个 null,设计上是为了避免死循环而设置的, Object.prototype 的隐式原型指向 null

原型链就是一个过程,原型是原型链这个过程中的一个单位,贯穿整个原型链

原型链好比继承+迭代,他可以让一个对象,继承多种多样的属性,方法。
而__proto__和prototype,就好比人的dna。__proto__指向父母,prototype代表父母自己。
两者的区别就在于此。

数据判断区别

一共4个判断类型的

typeof
缺点typeof不能将Object、Array和Null区分,都返回object

instanceof可以正确判断对象的类型,

其内部运行机制是判断在其原型链中能否找到该类型的原型

缺点instanceof, Number,Boolean,String基本数据类型不能判断

console.log(2 instanceof Number); // false 
console.log(true instanceof Boolean); // false 
console.log('str' instanceof String); // false 
console.log([] instanceof Array); // true 
console.log(function(){} instanceof Function); // true 
console.log({} instanceof Object); // true

constructor

缺点 需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了:

console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true

constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。

function Fn(){};
 
Fn.prototype = new Array();
 
var f = new Fn();
 
console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true

(4)Object.prototype.toString.call()

Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:

var a = Object.prototype.toString;
 
console.log(a.call(2)); // [object Number]
console.log(a.call(true));  // [object Boolean]
console.log(a.call('str')); // [object String]
console.log(a.call([])); // [object Array]
console.log(a.call(function(){})); // [object Function]
console.log(a.call({})); // [object Object]
console.log(a.call(undefined)); // [object Object]
console.log(a.call(null)); // [object Object]

垃圾回收机制

程序工作过程中会产生很多 垃圾,这些垃圾是程序不用的内存或者是之前用过了,以后不会再用的内存空间,而 GC 就是负责回收垃圾的,可达性,那些以某种方式可访问,或者说可用的值,被保存在内存中,反之不可访问,则需要回收, 垃圾清除方法1、标记清除,把所有对象标记成0,然后从根对象开始遍历,把不是垃圾的节点改成1,清理所有标记为0的垃圾,销毁并回收他们所占用的内存空间,然后把内存中对象标记修改为0,等待下一轮垃圾回收,缺点是会有内存碎片

新建内容需要找到合适的内存大小放进去,就需要额外的计算,而新的标记整理算法就可以有效的解决,会把活着的对象,向内存一端移动,没有内存碎片

内存泄漏

内存泄漏,不再用到的对象没有被及时回收时,就是内存泄漏,内存泄漏一般有4种,闭包,DOM元素引用,定时器,全局变量

闭包

上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。

复制功能

1、clipboard 插件

2、# Clipboard法 还有read,readText,wrtieText方法,详细:www.ruanyifeng.com/blog/2021/0…

  navigator.clipboard.writeText('123123').then(() => {
      this.$Message.success('复制成功')
    })

作用域

作用域的定义:作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。

内层作用域可以访问外层作用域,但是外层作用域不能访问内层

块级作用域 --函数声明:function 函数名(){}

--函数表达式: var 函数名=function(){}

--自执行函数: (function 函数名(){} )();自执行函数前面的语句必须有分号,通常用于隐藏作用域。 (1.独立作用域 2.运行后即销毁)

    function foo (sex) {
      console.log(sex)
    }
    var f = function () {
      console.log('hello')
    }
    var height = 180;
    (
      function fn () {
        console.log(height)
      }
    )()
    foo('female')
    // 依次打印:
    // 180
    // female
    // hello

作用域链

作用域链是从里到外找,不能从外到里找,如果是一个块级作用域,用var定义变量,会有变量提升需要注意,块级作用域内,函数的提升,比变量提升优先,let,const会先存在暂时死区里面,在全区作用域声明的变量,var会挂载到window对象上面,而let,const不会

线程与进程

一个进程由一个或者多个线程组成,进程相互独立,浏览器是多进程的,简单理解,比如浏览器每打开一个tab页,就相当于创建了一个独立的浏览器进程

判断对象上是否存在某个属性

一般用in 或者 hasOwnProperty ,in的缺点是如果属性在原型链中也会返回true,hasOwnProperty如果接到Object.create后面会报错,Object.create(null).hasOwnProperty('name'),hasOwn则不会,
let object3 = Object.create(null)
Object.hasOwn(object3, 'age') // false

栈,堆内存

栈内存,先进后出,后进先出,基本类型保存在栈内存中,因为基本类型占用空间小,大小固定。引用类型在堆内存,堆内存存取随意,因为引用类型大小不确定,占据空间大。对象和数组的指针是放在栈内存的,指针的指向是指向堆内存的,要访问堆内存中的引用数据类型时,实际上我们首先是从变量中获取了该对象的地址指针, 然后再从堆内存中取得我们需要的数据

垃圾销毁的区别 栈内存当前环境执行完就会被垃圾回收销毁,而堆内存不会,堆内存只有等引用都结束才会销毁

JSON.stringify 的几个作用

如果第二个参数是数组,则只转换数组里的属性,如果是方法,则把每个属性都方法处理一下再返回,接受2个参数,key,value,可以在这里处理数据,比如过滤

第三个参数,空白缩进格

在对象中underfined,任意函数,symbol会被忽略,数组中会返回null,### NaN Infinity null会返回null,BigInt转换会报错,处理不了循环引用, const obj = { name: 'zcxiaobao', age: 18, }

const loopObj = { obj }

// 形成循环引用 obj.loopObj = loopObj;

JSON.stringify(obj)

/* Uncaught TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property 'loopObj' -> object with constructor 'Object' --- property 'obj' closes the circle at JSON.stringify (<anonymous>) at <anonymous>:10:6 */ `

妙用,对象是否相等的判断,利用对象序列化为字符串,来进行简单判断

    const obj = {
      age: 13
    }   //{"age":13}
    const obj1 = {
      age: 13
    }  //{"age":13}
    console.log(JSON.stringify(obj) === JSON.stringify(obj1)) // true
    
//判断数组是否包含某对象
const names = [ {name:'zcxiaobao'}, {name:'txtx'}, {name:'mymy'}, ]; 
const zcxiaobao = {name:'zcxiaobao'}; 
// true JSON.stringify(names).includes(JSON.stringify(zcxiaobao))

深拷贝浅拷贝

  • 浅拷贝和深拷贝都复制了值和地址,都是为了解决引用类型赋值后互相影响的问题。
  • 但是浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响。
  • 深拷贝就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。 浅拷贝方法 Object.assign,扩展运算符

浅拷贝

数组的 slice
const arr = ['lin', 'is', 'handsome']
const newArr = arr.slice(0)

arr[2] = 'rich' // 改变原来的数组

console.log(newArr) // ['lin', 'is', 'handsome']

console.log(arr == newArr) // false 两者指向不同地址
concat 方法
const arr = ['lin', 'is', 'handsome']
const newArr = [].concat(arr)

arr[2] = 'rich' // 改变原来的数组

console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变

console.log(arr == newArr) // false 两者指向不同地址

深拷贝

JSON.parse(JSON.stringify(obj)),会忽略undefinedsymbol函数NaNInfinity-Infinity 会被序列化为 null:而且还不能解决循环引用的问题 简单版本深拷贝

function clone(target) { 
if (typeof target === 'object') 
{ 
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
} 
return cloneTarget;
} 
else { 
return target; 
} };

let const var的区别

在全局作用域,或者块级作用域,var都会变量提升到最顶端,let,const会先放入暂时死区中。let,const变量只在当前作用域有效,const必须要给初始值,声明的是常量,const不能修改指针,但是可以修改值

let,const也有变量的提升,不同的是,var提升之后的变量会被初始化成undefined,而let,const没有被初始化,形成了暂时性死区,在这个区间中对变量的访问都会报错。let在这个区间之外没有初始化则默认为undefined,而const则必须进行初始化,否则报错。