js高频面试手撕总结(高频版)

158 阅读4分钟

前端面试系列

一.如何确定this的值

1.全局执行坏境,指向全局对象window(严格和非严格模式下)

2.函数内部,取决于函数被调用的方式

​ 1.直接调用的this值:严格模式下是undefined;非严格模式下是window

​ 2.对象.方法():都指向调用者

注释严格模式是指在全局或函数内部顶端写下'use strict'

二.指定this的值

1.调用时指定:

function.call(),function.apply()
const person={a:1}
function fn(a,b){
 console.log(this,a,b)
}
// fn.call(person,10,20)
// fn(10,20)
fn.apply(person,[0,1,2])

2.创建时指定:

const fn2 = fn.bind(person,5,6)
fn2(3,4)  //5  6
const obj = {
 name:'张三',
 fn(){
  console.log(this)
  // setTimeout(function(){
  //  console.log(this) //window
  // },1000)
  setTimeout(()=>{
   console.log(this) //obj
  })
 }
}
obj.fn()

以下是一些使用这些方法的常见情况:

  1. 在继承中使用callapply 方法可以在实现继承时调用父类的构造函数,从而避免代码重复。
  2. 改变函数执行上下文:当需要在不同的对象上调用同一个函数时,callapply 方法可以临时改变函数的执行上下文。
  3. 函数式编程:在函数式编程中,bind 方法可以用于创建新的函数,固定部分参数,方便函数组合和柯里化。
  4. 回调函数:在一些异步操作中,使用callapply 方法可以确保回调函数中的this指向正确。

1.手撕call()方法,个人理解就是给第一个传入的对象上先加上这个方法,然后就传入参数调用,得到结果后,再从对象上删去,给人的感觉就是借鸡下蛋。。。

call方法的作用不就是改变函数的指向嘛,将调用call的函数的this指向传入的第一个对象参数,所以实际上就是吧调用call的函数赋给这个对象,让它来调用,调用完再删了,而调用的过程中,就可以用一下这个对象上的其它属性和值喽,理解了的话就会感觉,切,还以为啥牛逼呢?!!!就这。。。

Function.prototype.myCall = function (context, ...args) {
      const key = Symbol('key')
      context[key] = this
      const res = context[key](...args)
      delete context[key]
      return res
    }
    const person = {
      name: 'zhangsan'
    }
    function fn(a, b) {
      console.log(this)
      console.log(a, b)
      console.log(this.name)
      return a + b
    }
    // fn(1,2)
    const res = fn.myCall(person, 10, 20)
    console.log(res)

2.手撕apply(),和call()方法一样,唯一的区别就是传参成数组了,上代码。。。

 Function.prototype.myCall = function (context,numArr) {
   const key = Symbol('key')
   context[key] = this
   const res = context[key](numArr)
   delete context[key]
   return res
  }

  const person = {
   name: 'zhangsan'
  }

  function fn(numArr) {
   console.log(this)
   let sum = 0
   numArr.forEach(item=>{
​    console.log(item)
​    sum+=item
   })
   console.log(this.name)
   return sum
  }

  // fn(1,2)
  const res = fn.myCall(person,[1,2,3,4,5])
  console.log(res)		//1 2 3 4 5 zhangsan 15

3.手撕bind,里面用到了call

 Function.prototype.myBind = function (context,...Args){
      return (...reArgs)=>{
        // this就是调用myBind的函数
        return this.call(context,...Args,...reArgs)
      }
    }
    const person = {
      name: 'zhangsan'
    }
    function fn(a,b,c) {
      console.log(this)
      return a+b+c
    }
    const resFn = fn.myBind(person,1,2)
    console.log(resFn(3))	//6

三.js继承:子类可以具有父类的方法和属性,无需自己编写

class类的实现:

 class Person{
    name
    age=18
    constructor(name){
      this.name = name
    }
    study(){
      console.log(`${this.name}爱学习`)
    }
   }
   const p = new Person('小明')
   console.log(p)
   p.study()

class类实现继承:

  class Student extends Person{
    schoolNum
    constructor(name,schoolNum){
      //必须调用父类构造函数,否则会报错
      super(name)
      this.schoolNum = schoolNum
    }
    talk(){
      console.log(`${this.name}同学的学号是${this.schoolNum}`)
    }
   }
   const stu = new Student('小王',20210806)

静态属性/方法:通过static关键字定义静态方法,通过类本身来调用(实例也能调用,但不推荐)

私密属性/方法:前缀加#,只能在类内使用

测试过,实例可以给静态属性和私密属性赋值

class Student extends Person{
    static schoolNum
    #hobby='私密爱好~~'
    constructor(name,schoolNum,hobby){
      //必须调用父类构造函数,否则会报错
      super(name)
      this.schoolNum = schoolNum
      this.#hobby = hobby
    }
    static talk(){
      console.log('我是静态方法,可以在类内部调用')
    }
    #exercise(){
      console.log('我是私有方法,只能在类内部调用')
    }
    out(){
      console.log('私密爱好是:',this.#hobby+'\n静态属性是:'+this.schoolNum)
      this.#exercise()
    }
   }
   const stu = new Student('小王',20210806,'洗脚')
   console.log(stu)
   stu.out()

ES5-寄生组合式继承:通过借用构造函数继承属性(使用的父类的构造函数,this指向自己,所以给自己动态添加了属性),通过原型链来继承方法

寄生:把父类原型中的constructor改为指向自身的

function Person(name){
  this.name = name
}
Person.prototype.say=function(){
  console.log('我爱说实话',this)
}
function Student(name){
  //通过构造函数继承属性
  Person.call(this,name)
}
const prototype = Object.create(Person.prototype,{
  constructor:{
    value:Student   
  }
})
Student.prototype = prototype
const stu = new Student('小明')
console.log(stu.name,stu.say())

fetch:返回一个promise对象,可以用await等待

 let btn = document.querySelector('.btn')
    btn.addEventListener('click', async () => {
      const p = new URLSearchParams({ pname: '湖南省', cname: '长沙市' })
      const res = await fetch('http://hmajax.itheima.net/api/area?' + p.toString())
      if (res.status >= 200 && res.status < 300) {
        console.log(res)
        const data = await res.json()
        console.log(data)
      } else {
        console.log(res.status, '请求失败')
      }
    })

fetch上传文件

<input type="file">
  <img src="" alt="">
  <script>
    document.querySelector('input').addEventListener('change',async function(){
      const img = this.files[0]
      console.log(img)
      const data = new FormData()
      data.append('img',img)
      const res = await fetch('http://hmajax.itheima.net/api/uploadimg',{
        method:'post',  
        body:data
      })
      console.log(res)
      const resData = await res.json()
      console.log(resData)
      document.querySelector('img').src = resData.data.url
    })

feath上传json

    document.querySelector('.btn').addEventListener('click',async()=>{
      const headers = new Headers()
      headers.append('content-type','application/json')
      
      const res = await fetch('http://hmajax.itheima.net/api/register',{
        method:'post',
        headers,
        body:JSON.stringify({
          username:'whdjajdnajd',
          password:'121348342j'
        })
      })
      console.log(res)
      const resData = await res.json()
      console.log(resData)
    })

在JavaScript中,for...of语句用于遍历可迭代对象(如数组、Map、Set等),而普通对象(Object)是不可迭代的,因此无法直接使用for...of来遍历普通对象。

如果要遍历普通对象的属性,可以使用for...in语句来实现。示例代码如下:

const obj = {a: 1, b: 2, c: 3};

for (const key in obj) {
  console.log(key, obj[key]);
}

如果想要使用for...of来遍历普通对象的属性,可以通过将对象的属性转换为可迭代对象来实现。示例代码如下:

const obj = {a: 1, b: 2, c: 3};

for (const entry of Object.entries(obj)) {
  console.log(entry);
}

在这个示例中,Object.entries(obj)将对象的属性转换为一个包含键值对的数组,然后就可以使用for...of来遍历这个数组了。

Generator函数是ES6提供的一种异步编程解决方案

  function* generatorArr(){
    yield 'x',
    yield 'y',
    yield 'z'
  }
  const genArr = generatorArr()
  // for (const iterator of genArr) {
  //   console.log(iterator)
  // }

image.png

三种异步:

1.promise:异步编程,内部返回一个新的promise对象,后面就可以接着then()调用

2.generator():同步定义,使用起来繁琐,利用yield关键字来分隔逻辑比如示例中依次调用了多个接口,通过yield分隔,通过next来触发调用

3.async:使用方便,阅读方便

Promise

实例方法:.catch(...),.finally(...)

静态方法:.resolve(...),.reject(...),.race(...),.all(...),.allSettled(...),.any(...)

手撕Promise

// 声明变量存储三种状态
const PENDING   = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED  = 'rejected'
class myPromise{
  state = PENDING
  result = undefined
  constructor(func){
    //定义resolve
    const resolve = (result)=>{
      if(this.state === PENDING)
      {
        this.state = FULFILLED
        this.result = result
      }
    }
    const reject = (result)=>{
      if(this.state === PENDING){
        this.state = REJECTED
        this.result = result
      }
    }
    // const my_finally = ()=>{
    //   console.log('最后执行finally!!') 
    // }
    func(resolve,reject)
    // my_finally()
  }
  //实现then()方法
  then(onFulfilled, onRejected){
    onFulfilled = typeof onFulfilled ==='function'? onFulfilled :x=>x
    onRejected = typeof onRejected ==='function'? onRejected :x=>{throw x}
    if(this.state === FULFILLED){
      onFulfilled(this.result)
    }else if(this.state === REJECTED){
      onRejected(this.result)
    }
  }
}
// myPromise实例
const my_promise = new myPromise((resolve,reject)=>{
  // resolve('成功')
  reject('失败')
})
my_promise.then(res=>{
  console.log('成功回调:',res)
},err=>{
  console.log('失败回调:',err)
})

现在实现异步回调和链式调用

Vue实现异步回调选用queueMicrotask和MutationObserve

 console.log('1')
 queueMicrotask(()=>{
   console.log('3')
 })
 console.log('2')


console.log('start')
const mo = new MutationObserver(()=>{
  console.log('创建mo实例时,传入回调函数,当监听的节点发生变化后,执行回调!!')
})
const divNode = document.createElement('div')
mo.observe(divNode,{childList:true})
divNode.innerHTML = '最爱喝牛奶'
console.log('end')

   console.log('start')
   const p =new Promise((resolve,reject)=>{
   //这里还是同步执行的,直到遇到resolve()或reject()才放入异步队列
   console.log(111)
    // console.log('resolve',resolve)
    // console.log('reject',reject)
    console.log(this)
      resolve(reject(resolve(reject('sb'))))
   })
    p.then(res=>{
        console.log('成功',res)
    }).catch(err=>{
      console.log('失败',err)
    })
    console.log('end')
    console.log(p)

image.png

函数柯里化:将接收多个参数的原函数改写为只接受一个参数(原函数的第一个参数)并返回结果(是函数)的函数。

function sum(a){

  return function(b){

​    return a+b

  }

}

console.log(sum(1)(2))		//3

动生成加数的个数,传入加数得到和

const fn = count => {
            let arr = []    //保存传入参数
            return (...args) => {
                arr.push(...args)
                if (arr.length >= count) {
                    const res = arr.slice(0, count).reduce((p, v) => p + v, 0)
                    arr = []
                    return res
                } else {
                    return fn
                }
            }
        }
        
fn(3)(1,2,3)		//6

函数柯里化具体应用——判断类型

const typeOfTest = type => thing => typeof thing === type
const objectType = typeOfTest('object')
console.log(objectType({}))     //true

JS设计模式

工厂模式:调用即可返回新对象的函数

  1. Vue3的createApp:将全局改变Vue实例的行为,移到单个Vue实例上
  2. axios-create:基于自定义配置新建实例

单例模式:单例对象整个系统保证只有一个存在

观察者模式:在对象之间定义一对多的依赖,目标对象变化后,所有观察者对象都会自动收到通知,从而触发回调函数。

​ 1.监听事件

​ 2.watch

发布订阅模式:和观察者模式很像,区别是多了个中间商

image.png

手写一个事件总线

<button class="on">注册事件</button>
<button class="emit">触发事件</button>
<button class="off">移除事件</button>
<button class="once-on">一次性事件注册</button>
<button class="once-emit">一次性事件触发</button>
  

class myEmmiter {
            // 私有属性,键值对方式存贮事件名及其回调函数
            #handler = {}
            // 注册事件
            $on(event, callback) {
                if (this.#handler[event] === undefined) {
                    this.#handler[event] = []
                }
                this.#handler[event].push(callback)
            }
            // 触发事件
            $emit(event, ...args) {
                const events = this.#handler[event] || []     //拿到事件对应回调函数的数组
                events.forEach(callback => callback(...args))
            }
            $off(event){
                this.#handler[event] = undefined
            }
            $once(event,callback){
                this.$on(event,(...args)=>{
                    callback(...args)
                    this.$off(event)
                })
            }

        }



        const bus = new myEmmiter()
        document.querySelector('.on').addEventListener('click', () => {
            bus.$on('event1', () => console.log('我是无敌的'))
            bus.$on('event2', () => console.log('你是无敌的'))
        })
        // 触发事件
        document.querySelector('.emit').addEventListener('click', () => {
            bus.$emit('event1')
            bus.$emit('event2')
        })
        // 移除事件
        document.querySelector('.off').addEventListener('click', () => {
            bus.$off('event2')
        })
        // 一次性事件注册
        document.querySelector('.once-on').addEventListener('click', () => {
            bus.$once('event3',(name)=>{
                console.log(name+',天下无双')
            })
        })
        // 一次性事件触发
        document.querySelector('.once-emit').addEventListener('click', () => {
            bus.$emit('event3','宫本武藏')
        })

原型模式:基于一个已有的对象复制一个新的对象,而不是新建

1.Object.create():以传入的对象作为原型,创建一个对象

2.Vue2数组的7中方法:push,pop,unshift, shift, splice, reserve, sort

代理模式:拦截和控制与目标对象的交互

for ... in ... : 索引遍历对象,包括继承来的属性

for... of ... : 遍历 Array,Map,Set,String,TypeArray,arguments等等

迭代器模式

迭代协议可以定制对象的迭代行为 分为2个协议:

1.可迭代协议: 增加方法Symbol.iterator{} 返回符合 迭代器协议 的对象

2.迭代器协议:

​ 有next方法的对象,next方法返回:

​ 已结束: {done:true}

​ 继续迭代: {done:false,value:'x'}

  • 使用Generator
  • 自己实现 对象,next
 const o = {
            // 1.generator方法
            // [Symbol.iterator]() {
            //     function* foodGenerator() {
            //         yield 'a',
            //         yield 'b',
            //         yield 'c'
            //     }
            //     const r = foodGenerator()
            //     return r
            // }

            // 2.自己手写
            [Symbol.iterator]() {
                const arr = [1,2,3]
                let index =0
                return {
                    next(){
                        if(index<arr.length){
                            return {
                                done:false,
                                value:arr[index++]
                            }
                        }
                        return {done:true}
                    }
                }
            }
        }
        for (const iterator of o) {
            console.log(iterator)
        }

防抖:触发频率高,耗费性能,只执行最后一次操作

1.频率高:改变视口宽高 2.input输入 3.scroll 4.keyup..

2.耗费性能:操纵页面、网络请求...

​ 防抖功能,this指向,参数

function debounce(func,wait=0){
  let timeId
  return function(...args){
    const _that = this
    clearTimeout(timeId)
    timeId = setTimeout(()=>{
        func.apply(_that,args)
    },wait)
  }
}

节流:触发频率高,耗费性能,只处理一次

function throttle(func,wait=0){
  let timeId
  return function(...args){
  const _that = this
    if(timeId){ 
      return
    }else{
      timeId = setInterval(()=>{
        func.apply(_that,args)
        timeId = undefined
      },wait)
    }
  }
}

lodash.throttle | Lodash中文文档 | Lodash中文网 (lodashjs.com)