1
前景

疫情无情人有情,在去年经历互联网一系列的风波之后,我相信大家有很多的小伙伴想在今年金三银四的面试季中为自己的未来找一个好一点的公司。那么今天我们来讲解一下身为 P5 工程师需要知道的一些原理及其如何亲自手写出它的实现过程,有可能我们日常开发业务的时候用不上这些自己写的方法,但是我们我们对原理一无所知,那么在面试的时候一旦被深挖那么可能离自己心念的公司就会又远一步。

模式 call

  • 第一个参数为 null或者 undefined 时,this 指向全局对象 window,值为原始值的指向改原始值的自动包装对象,如 String、number、boolean
  • 为了避免函数名与上下问(context)的属性发生冲突,使用 Symbol 类型作为唯一值
  • 将函数作为传入的上下文(context)属性执行
  • 函数执行完后删除改属性
  • 返回执行结果
Function.prototype.myCall = function(context,...args){
    context = (context ?? window) || new Object(context);
    const key = Symbol();
    context[key] = this;
    const result = context[key](...args);
    delete context[key];
    return result;
}

模式 apply

  • 前部分与call一样
  • 第二个参数可以不传,但类型必须为数组或者类数组
Function.prototype.myApply = function(context) {
    context =  (context ?? window) || new Object(context)
    const key = Symbol()
    const args = arguments[1]
    context[key] = this
    let result
    if(args) {
        result = context[key](...args)
    } else {
        result = context[key]
    }
    delete context[key]
    return result
}

模式 bind

  • 使用 call / apply 指定 this
  • 返回一个绑定函数
  • 当返回的绑定函数作为构造函数被new调用,绑定的上下文指向实例对象
  • 设置绑定函数的prototype 为原函数的prototype
Function.prototype.myBind = function(context, ...args) {
    const fn = this
    const bindFn = function (...newFnArgs) {
        fn.call(
            this instanceof bindFn ? this : context,
            ...args, ...newFnArgs
        )
    }
    bindFn.prototype = Object.create(fn.prototype)
    return bindFn
}

深拷贝

  • 判断类型是否为原始类型,如果是,无需拷贝直接返回
  • 为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回
  • 开辟一个存储空间,来存储当前对象和拷贝对象的对应关系
  • 对引用类型递归拷贝直到属性为原始类型
const deepClone = (target, cache = new WeakMap()) => {
    if(target === null || typeof target !== 'object') {
        return target
    }
    if(cache.get(target)) {
        return target
    }
    const copy = Array.isArray(target) ? [] : {}
    cache.set(target, copy)
    Object.keys(target).forEach(key => copy[key] = deepClone(obj[key], cache))
    return copy
}

函数防抖

  • this继承自父级上下文,指向触发事件的目标元素
  • 事件被触发时,传入event对象
  • 传入leading参数,判断是否可以立即执行回调函数,不必要等到事件停止触发后才开始执行
  • 回调函数可以有返回值,需要返回执行结果
const debounce = (fn, wait = 300, leading = true) => {
    let timerId, result
    return function(...args) {
        timerId && clearTimeout(timerId)
        if (leading) {
            if (!timerId) result = fn.apply(this, args)
            timerId = setTimeout(() => timerId = null, wait)
        } else {
            timerId = setTimeout(() => result = fn.apply(this, args), wait)
        }
        return result
    }
}

函数节流(定时器)

函数停止触发后 n妙后开始执行,停止触发后继续执行一次事件
const throttle = (fn, wait = 300) => {
    let timerId
    return function(...args) {
        if(!timerId) {
            timerId = setTimeout(() => {
                timerId = null
                return result = fn.apply(this, ...args)
            }, wait)
        }
    }
}

函数节流(时间戳)

函数触发后立刻执行,停止触发后不在执行事件

const throttle = (fn, wait = 300) => {
    let prev = 0
    let result
    return function(...args) {
        let now = +new Date()
        if(now - prev > wait) {
            prev = now
            return result = fn.apply(this, ...args)
        }
    }
}

ES6版继承

ES5的继承,实质是先创造子类的实例对象,然后将再将父类的方法添加到this上。ES6的继承,先创造父类的实例对象(所以必须先调用super方法,然后再用子类的构造函数修改this。

class Super {    constructor(foo) {      this.foo = foo    }    printFoo() {      console.log(this.foo)    }  }       class Sub extends Super {    constructor(foo, bar) {      super(foo)      this.bar = bar    }  }js

上面作为一部分都是作为一个 P5应该具有基本技能,同时我这边会陆续的更新一些列的面试题,对于 call、apply、bind 有什么不清楚不好理解的地方可以查看我之前的文章,让你一篇文章彻底搞清楚,这里说了ES6继承那么后续我会结合面向对象编程和设计模式依次为大家讲解,关注 tu 老师说前端带你玩转全栈
图片


凃老师
39 声望3 粉丝