前端手撕代码题

105 阅读7分钟

1. bind函数

Function.prototype.myBind = function (thisArg, ...args) {
    if (thisArg == null) {
        thisArg = window || global
    }
    const fn = this
    if (typeof fn !== 'function') {
        throw 'wrong called'
    }
    return function (...otherArgs) {
        return fn.apply(thisArg, args.concat(otherArgs))
    }
}

2. call函数

改变内部this指向,立即执行

Function.prototype.myCall = function (thisArg, ...args) {
    const fn = this
    if (typeof fn !== 'function') {
        throw 'wrong called'
    }
    if (thisArg == null) {
        thisArg = window || global
    }
    thisArg[fn] = fn
    let res = thisArg[fn](...args)
    delete thisArg[fn]
    return res
}

3. new创建对象

function myNew(con, ...args) {
    let newObj = Object.create(con.prototype) // 空对象的原型设置为构造函数的原型,即obj.proto = func.prototype;
    let res = con.apply(newObj, args) //继承对像属性和方法
    return typeof res === 'object' && res != null ? res : newObj
}

4. instanceof实现

用于检测构造函数的 prototype属性是否出现在某个实例对象的原型链上

例如数组实例 arr的构造函数 Array的原型对象就在该实例对象的原型链上。

function myInstanceof(left, right) {
    if (typeof left !== 'object' || right == null) {
        return false
    }

    left = left.__proto__
    right = right.prototype
    while (true) {
        if (left == null) return false
        if (left === right) return true
        left = left.__proto__
    }
}

5. 防抖函数

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。(函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条。)(用户频繁触发事件)

思路:外界事件触发定时器,定时器存在就清除重新定一个定时器,最后执行完将定时器初始化

function debounce(fn, delay = 500) {
    let timer = null
    return function (...args) {
        // 如果定时器已经开启,则清除定时器
        if (timer) clearTimeout(timer)
        // 重新定时
        timer = setTimeout(() => {
            fn.apply(this, args)
            timer = null
        }, delay)
    }
}

6. 节流函数

函数节流是间隔时间执行。

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。(函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹。)

function throttle(fn, delay = 200) {
    let timer = null
    return function (...args) {
        // 如果定时器存在,说明还未到时,直接返回
        if (timer) return
        // 设置定时任务,到时后执行fn,并重置定时器为null
        timer = setTimeout(() => {
            fn.apply(this, args)
            timer = null
        }, delay)
    }
}

7. 函数柯里化

部分求值,给函数分步传递参数

每次传递参数进行处理,并返回一个更具体的函数接受剩下的参数

const sum = (...args) => {
  return args.reduce((a,b) => {
    return a+b;
  })
}

var currying = function(func){
  const args = [] //func函数的参数
  return function result(...rest){//接受剩下的参数,需要递归调用
    if(rest.length === 0 ){//当长度等于0的时候就开始计算sum
      return func(...args)
    }else{//参数有长度
      args.push(...rest);
      return result;//链式调用
    }
  }
}
currying(sum)(1)(2,3)(5)()

8. 数组扁平化

WechatIMG203.png

Array.prototype.flat = function(){
  const result = this.map(item=>{ //对数组每项进行map处理,返回新的数组
    if(Array.isArray(item)){//元素还是个数组,继续进行扁平化
      return item.flat()
    }
    return [item]//[[1],[2],[3]]
  })
  eturn [].concat(...result)
} 
//去重set
Array.prototype.unique = function(){
  return [...new Set(this)]
}
//排序
const sotrFn = (a,b) => a-b;
arr.flat().unique()..sort(sortFn)

9. 数组转树形结构

var menu_list = [
    {
        id: '1',
        menu_name: '设置',
        menu_url: 'setting',
        parent_id: 0,
    },
    {
        id: '1-1',
        menu_name: '权限设置',
        menu_url: 'setting.permission',
        parent_id: '1',
    },
    {
        id: '1-2',
        menu_name: '菜单设置',
        menu_url: 'setting.menu',
        parent_id: '1',
    },
    {
        id: '2',
        menu_name: '订单',
        menu_url: 'order',
        parent_id: 0,
    },
    {
        id: '2-1',
        menu_name: '报单审核',
        menu_url: 'order.orderreview',
        parent_id: '2',
    },
    {
        id: '2-2',
        menu_name: '退款管理',
        menu_url: 'order.refundmanagement',
        parent_id: '2',
    },
]
function toTree{
  var res = []
  var map = {}
  data.forEach(item=>{//根据id弄个字典
    map[item.id] = item;
  })
  data.foreach(item=>{
    let parent = map[item.pid];
    if(parent){
      (parent.child || (parent.child = [])).push(item)
    }else{//pid找不到导致parent不存在
      res.push(item)
    }
  })
  return res
}

10. 对象深拷贝

浅拷贝方法: Object.assign({},list)、[...list]、{...list}、map、filter、reduce

深拷贝方法:

  • Json

    • JSON.parse(JSON.stringify(list))
    • json能解决数组的深拷贝也能解决Object的深拷贝
function deepCopy(newObj, oldObj){
  for(let k in oldObj){
    // 判断我们的属于那种数据类型
    // 1. 获取属性值
    let item = oldObj[k]
    // 2. 判断值是否是数组
    if(item instanceof Array){
      newObj[k] = [];
      deepCopy(newObj[k], item)
    }else if(item instanceof Object){// 3. 判断是是否是对象
      newObj[k] = {}
      deepCopy(newObj[k], item)
    }else{// 4. 属于简单数据类型
      newObj[k] = item
    }
  }
}

11.洋葱模型

app.use(async next => {
  console.log(1)
  await next()
  console.log(2)
})
app.use(async next => {
  console.log(3)
  await next()
  console.log(4)
})
app.use(async next => {
  console.log(5)
  await next()
  console.log(6)
})
app.compose() //135642

12.手写发布订阅模式

class EventEmitter {
  events: {[key: string]: Function[]} = {}
 	// 订阅
	on(type: string, callback: Function) {
    if(!this.events) this.events = Object.create(null)
    
    if(!this.events[type]) {
      this.events[type] = [callback]
    }else {
      this.events[type].push(callback)
    }
  }
  // 取消订阅
  off(type: string) {
    if(!this.events[type]) return 
   	delete this.events[type]
  }
  // 只执行一次订阅
  once(type: string, callback: Function) {
    function fn() {
      callback()
      this.off(type)
    }
    this.on(type, fn)
  }
  // 触发事件
  emit(type: string, ...rest) {
    this.events[type] && this.events[type].forEach(fn => fn(...rest))
  }
}

// 使用情况
const event = new EventEmitter()
event.on('click', (...rest) => {
  console.log(rest)
})
event.emit('click')
event.off('click')
event.once('click', (...rest) => {
  console.log(rest)
})

13. 手写Promise系列

//实现promise.all()
/*
    Promise函数对象的all方法
    返回一个promise对象,只有当所有promise都成功时返回的promise状态才成功
*/
Promise.myAll = function(promises){
	const values = new Array(promises.length)
	var resolvedCount = 0; //计状态为resolved的promise的数量
	return new Promise((resolve,reject)=>{
		// 遍历promises,获取每个promise的结果
		promises.forEach((p,index)=>){
			 Promise.resolve(p).then(
                value =>{
                    // p状态为resolved,将值保存起来
                    values[index] = value
                    resolvedCount++;   
                    // 如果全部p都为resolved状态,return的promise状态为resolved
                    if(resolvedCount === promises.length){
                        resolve(values)
                    }
                },
                reason =>{ //只要有一个失败 return的promise状态reject
                    reject(reason);
                })                         
        })
    })
}

//实现Promise.race()
/*
     Promise函数对象的race方法
     返回一个promise对象,状态由第一个完成的promise决定
*/
Promise.myRace = function(promises){
    return new Promise((resolve,reject)=>{
        // 遍历promises,获取每个promise的结果
        promises.forEach((p,index)=>{
            Promise.resolve(p).then(
                value => {
                    // 只要有一个成功,返回的promise的状态就为resolved
                    resolve(value)

                },
                reason => { //只要有一个失败,return的promise状态就为reject
                    reject(reason)
                }
            )
        })
    })
}

//手写promise
class myPromise{
    constructor(fn){
        //将成功的函数集成在successList数组里面
        this.successList = [];
        //将所有的失败函数集成在failList里面
        this.failList = [];
        //pending(进行中) fulfilled(已成功) rejcet(以失败)
        this.state = "pending"
        //传入的函数对象(异步操作函数内容)
        fn(this.resolveFn.bind(this),this.rejectFn.bind(this));
    }
    // 状态转变为 resolve 方法
    resolveFn(res){
        this.state = "fulfilled";
        this.successList.forEach(function(item){
            //将成功的事件循环调用
            item(res)
        })
    }
     // 状态转变为 rejected 方法
    rejectFn(res){
        //将注册到的失败所有事件进行调用
        this.state = "rejected";
        this.failList.forEach(function(item){
            item(res)
        })
        // throw Error(res);
    }
    //then方法
    then(successFn,failFn){
        if(typeof successFn == "function"){
            this.successList.push(successFn);
        }

        if(typeof failFn == "function"){
            this.failList.push(failFn)
        }
    }
	//catch方法
    catch(failFn){
        if(typeof failFn == "function"){
            this.failList.push(failFn)
        }
    }
}

var fn = function(resolve,reject){
    setTimeout(function(){
        if(false){
            resolve("自定义成功")
        }else{
            reject("自定义失败")
        }
    },2000)
}

14.使用class 手写一个promise

/创建一个Promise的类
  class Promise{
    constructor(executer){//构造函数constructor里面是个执行器
      this.status = 'pending';//默认的状态 pending
      this.value = undefined//成功的值默认undefined
      this.reason = undefined//失败的值默认undefined
      //状态只有在pending时候才能改变
      let resolveFn = value =>{
        //判断只有等待时才能resolve成功
        if(this.status == pending){
          this.status = 'resolve';
          this.value = value;
        }
      }
      //判断只有等待时才能reject失败
      let rejectFn = reason =>{
        if(this.status == pending){
          this.status = 'reject';
          this.reason = reason;
        }
      }    
      try{
        //把resolve和reject两个函数传给执行器executer
        executer(resolve,reject);
      }catch(e){
        reject(e);//失败的话进catch
      }
    }
    then(onFufilled,onReject){
      //如果状态成功调用onFufilled
      if(this.status = 'resolve'){
        onFufilled(this.value);
      }
      //如果状态失败调用onReject
      if(this.status = 'reject'){
        onReject(this.reason);
      }
    }
  }

15. 手写ajax的get和post封装

WechatIMG203.png

16. 手写Vuex

let Vue;

class Store{
  constructor(options){// options就是new Vuex.Store传入的参数
    // 1.使用vue实列来实现响应式变化,保证状态更新会刷新视图
    this.vm = new Vue({//1.data会被使用Object.defineProperty重新定义
      date: {
        state: options.state
      }
    });
    // 2.解析options.getters,它是一个对象,里面都是函数
    this.getters={};
    Object.keys(options.getters).forEach(getterName=>{
      Object.definePropery(this.getters, getterame,{
        get:()=>{return options.getters[getterName](this.state)}
      })
    });
    //3.解析options.mutations
    this.mutations = {};
    Object.keys(options.mutations).forEach(mutationsName=>{
      this.mutations[mutationsName] = (payload)=>{
        options.mutations[mutationsName](this.state, payload)
      }
    });
    // 4.解析options.actions
    this.actions = {};
    Object.keys(options.actions).forEach(actionsName=>{
      this.actions[actionsName] = (payload)=>{
        options.actions[actionsName](this, payload)
      }
    });
  }
  get state(){ //1.获取实例上的state——>store.state
    // 1.相当于this.state = this.vm.state
    return this.vm.state
  }
  // 3.用箭头函数的目的是让this一直指向Store——>$store.commit
  commit = (mutationName, payload) => {this.mutations[mutationsName](payload)};
  // 4.$store.dispatch
  dispatch = (actionName, payload) =>{this.actions[actionsName](payload)}
}

// 每一个插件都有一个install函数,主要就是只有当前的实例才能使用这个插件
const install = (_Vue) => { //_Vue是vue的构造函数
  // 使得当前插件不再依赖Vue而是通过用户把Vue传过来,import vue打包的时候体量太大?
  Vue = _Vue; //传过来的vue的构造函数
  // 下载不是下载到vue的原型上,我们只想给当前实列下的组件使用
  // 使用mixin,抽离组件的公共逻辑,每个组件(vue)调用beforeCreate都会执行里面的方法
  // 值得注意的是,main.js(父)有个vue,app.vue也有个vue(子)
  Vue.mixin({
    beforeCreate(){
      // console.log(this.name)//root-->app-->app的子...
      // 为了让所有子组件使用store,就把root的store属性放在每个组件的实列上
      if(this.$options.store){//是root,这个root是有store的
        this.$store = this.$options.store
      }else{// 是子组件
        this.$store = this.$parent && this.$parent.$store
      }
    }
  }) 
}

export default{
  Store,
  install
}