这些手写的代码你都会了没有?

1、手写instanceof

  • instanceof主要判断一个实例是否属于某种类型;
  • 实现原理就是只要右边类型prototype在左边实例原型链上即可;

// L代表左侧的实列,R代表右侧的类型
function instance_of(L, R) {
    // 获取类型的原型对象prototype
    let rPrototype = R.prototype
    // 获取实例的隐式原型__proto__
    L = L.__proto__
    // 循环判断,直到最顶层null或者找到为止
    while(true) {
        if(L === null) return false
        if(L === R) return true
        L = L.__proto__
    }
}

// 测试
let arr = []
let obj = {}
let fn = function() {}
class Father {}
let father1 = new Father()

console.log(instance_of(arr, Array)) // true
console.log(instance_of(obj, Object)) // true
console.log(instance_of(fn, Function)) // true
console.log(instance_of(father1, Father)) // true
console.log(instance_of(father1, Array)) // false

复制代码

2、手写new关键词

使用new关键词执行构造函数,主要过程为:

  • 1、创建一个新对象;
  • 2、将第一步的空对象链接到另一个对象(new操作符后面跟着的构造函数的原型对象
  • 3、将第一步创建的对象作为构造函数的this上下文
  • 4、返回对象实例,如果构造函数有返回对象,则对象实例就是构造函数返回的对象,否则就返回this作为对象实例

function myNew(fn) {

    // fn必须是一个函数
    if(typeof fn !== 'function') {
        throw 'fn must be a function'
    }
    
    return function(...arg) {
    
        // 完成前两步,创建新对象,链接原型
        let newObj = Object.create(fn.prototype)
        
        // 第三步,改变构造函数的this指向
        let result = fn.apply(newObj, [...arg])
        
        // 第四步,判断返回值
        return result || newObj;
    }
}

// 测试
function Student(name, age) {
    this.name = name
    this.age = age
}

let tom = myNew(Student)('Tom', 18)
console.log(tom) // Student {name: 'Tom', age: 18}
console.log(tom.__proto__ === Student.prototype) // true
console.log(tom instanceof Student) // true

function Pepol(name) {
    this.name = name
    return {age: '18'}
}

let jim = myNew(Pepol)('Jim')
// 返回对象实例,如果构造函数有返回对象,则对象实例就是构造函数返回的对象
// 否则就返回this作为对象实例
console.log(jim) // {age: '18'}

复制代码

3、手写call

  • 改变函数的this指向
  • 第一个参数为this要指向的对象,如果没有或者参数为undefined/null则默认指向window
  • 参数传递是以参数列表传递,需要一个一个的列举出来
  • 函数立即执行

Function.prototype.myCall = function(context, ...arg) {

    // 如果context不存在/undefined/nul,则指向window
    if(!context) context = window
    
    // 使用符号确保属性名唯一
    let fn = Symbol()
    
    // this指向传入对象
    context[fn] = this
    
    // 带参数执行函数
    const result = context[fn](...arg)
    
    // 删除fn,确保不会污染原对象
    delete context[fn]
    
    return result
}

// 测试
let a = {
    name: 'Tom',
    age: 18
}
function fn(add, hobby) {
    console.log(`my name is ${this.name}, I\`m ${this.age}, I come from ${add}, I like playing ${hobby}`)
}
fn.myCall(a, 'America', 'basketball') // my name is Tom, I`m 18, I come from America, I like playing basketball

复制代码

4、手写apply

  • 跟call基本是一样的,只是call的剩余参数是参数列表(call的参数需要一个一个在后面列举出来),而apply的第二个参数必须是数组(apply的参数是以参数数组的形式传递的)
Function.prototype.myApply = function(context, arg) {

    // 如果context不存在/undefined/nul,则指向window
    if(!context) context = window
    
    // 使用符号确保属性名唯一
    let fn = Symbol()
    
    // this指向传入对象
    context[fn] = this
    
    // 带参数执行函数
    const result = context[fn](...arg)
    
    // 删除fn,确保不会污染原对象
    delete context[fn]
    
    return result
}

// 测试
let a = {
    name: 'Tom',
    age: 18
}
function fn(add, hobby) {
    console.log(`my name is ${this.name}, I\`m ${this.age}, I come from ${add}, I like playing ${hobby}`)
}
fn.myApply(a, ['America', 'basketball']) // my name is Tom, I`m 18, I come from America, I like playing basketball

复制代码

5、手写bind

  • bind函数也改变this的指向
  • 传入的函数不会立即执行,而是返回一个永久改变this指向的函数
  • 参数传递跟call一样使用参数列表,需要一个一个的列举出来传递
  • 绑定过后的函数被new实例化之后,需要继承原函数的原型链方法,且绑定过程中提供的this被忽略(继承原函数的this对象),但是参数还是会使用
Function.prototype.myBind = function(context) {

    // this必须是一个函数
    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    // 保存this,方便后续执行的时候使用
    let self = this;
    
    // 获取参数列表除第一个this之外的参数
    let args = Array.prototype.slice.call(arguments, 1);
    
    let fNOP = function () {};
    
    // 绑定后生成的函数
    let fBound = function () {
    
        let bindArgs = Array.prototype.slice.call(arguments);
        
        // 使用apply改变调用时的this指向
        // this只和运行的时候有关系,所以这里的this和上面的self不是一样的
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }
    
    // 继承原型
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    
    return fBound;
}

// 测试
let a = {
    name: 'Tom',
    age: 18
}
function fn(add, hobby) {
    console.log(`my name is ${this.name}, I\`m ${this.age}, I come from ${add}, I like playing ${hobby}`)
}
let fnBind = fn.myBind(a)
fnBind('America', 'basketball') // my name is Tom, I`m 18, I come from America, I like playing basketball
复制代码

6、防抖

  • 在事件触发的一段时间后执行该事件,如果这期间再次触发事件,则重新计时
function debounce(fn, delay) {

    // 保存一个timer
    let timer
    
    return function(...args) {
    
        // 如果以及存在则清除
        if(timer) clearTimeout(timer)
        
        // 延迟执行函数
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, delay)
    }
}

// 测试
function task() {
    console.log('滚动')
}
const debounceTask = debounce(task, 100)
window.addEventListener('scroll', debounceTask)

复制代码

7、节流

  • 事件在一定时间内只能触发一次
function throttle(fn, delay) {

    // 保存timer
    let timer
    
    return function(...args) {
    
        // 如果存在timer则返回
        if(timer) return
        
        timer = setTimeout(() => {
        
            fn.apply(this, args)
            
            // 清除定时器
            clearTimeout(timer)
            timer = null
            
        }, delay)
    }
}

// 测试
function task() {
    console.log('滚动')
}
const throttleTask = throttle(task, 100)
window.addEventListener('scroll', throttleTask)
复制代码

8、深克隆

  • 创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;

  • 使用JSON.stringify和JSON.parse也可以实现深拷贝:JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象

function deepClone(obj, cache = new WeakMap()) {

    // 判断是否是引用类型,如果不是则直接返回
    if(obj === null || typeof obj !== 'object') return obj
    
    // 判断是否是特殊的对象类型(Date/RegExp)
    if(obj instanceof Date) return new Date(obj)
    if(obj instanceof RegExp) return new RegExp(obj)
    
    // 如果出现循环引用,则返回缓存的对象,防止递归进入死循环
    if (cache.has(obj)) return cache.get(obj) 
    
    // 使用原对象的构造函数创建一个新对象
    let newObj = new obj.constructor()
    
    // 缓存对象,用于循环引用的情况
    cache.set(obj, newObj)
    
    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
            // 递归克隆
            newObj[key] = deepClone(obj[key], cache)
        }
    }
    
    return newObj
}

// 测试

let obj = {
    name: '111',
    a: [1,2,3],
    b: new Date(),
    c: {
        h: 111
    }
}
 let newObj = deepClone(obj)
 console.log(newObj === obj) // false
 console.log(newObj.__proto__ === obj.__proto__) // true
 newObj.a[2] = 10
 console.log(obj.a[2]) // 3
复制代码

9、手写promise

  • promise的出现是为了解决回调地狱、是一种异步编程的解决方案。

  • promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态)  ,初始状态是pending(等待态) ,状态一旦改变,就不会再变。


class MyPromise {
  constructor(executor) {
    // 捕获执行器的代码错误
    try {
      // executor执行器,进入会立即执行
      executor(this.resolve, this.reject)
    } catch (err) {
      this.reject(err);
    }
  }
  // 状态值pending(等待态),fulfiled(成功态),rejected(失败态),默认为pending
  PromiseState = 'pending'

  // 成功的回调
  onFulfilledCallbacks = []

  // 失败的回调
  onRejectedCallbacks = []

  // 执行后的值
  PromiseResult = null

  // 成功的方法
  resolve = (value) => {
    // 如果状态不是pending,则直接返回,因为状态已经改变就不可再次改变
    if (this.PromiseState !== 'pending') return
    // 状态置为成功的状态fulfiled
    this.PromiseState = 'fulfiled'
    // 执行后的值改为传进来的值
    this.PromiseResult = value
    // 查看是否存在可执行的回调
    while (this.onFulfilledCallbacks.length) {
      this.onFulfilledCallbacks.shift()()
    }
    console.log('成功的回调', value)
  }
  // 失败的方法
  reject = (value) => {
    // 如果状态不是pending,则直接返回,因为状态已经改变就不可再次改变
    if (this.PromiseState !== 'pending') return
    // 状态置为失败的状态rejected
    this.PromiseState = 'rejected'
    // 执行后的值改为传进来的值
    this.PromiseResult = value
    // 查看是否存在可执行的回调
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()()
    }
    console.log('失败的回调', value)
  }
  then(onFulfilled, onRejected) {
    // 如果不传,就使用默认函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

    const promise2 = new MyPromise((resolve, reject) => {
      // 成功
      const resolveMicrotask = () => {
        queueMicrotask(() => {
          // then执行阶段错误捕获
          try {
            const x = onFulfilled(this.PromiseResult);
            this.resolvePromise(x, promise2, resolve, reject);
          } catch (err) {
            reject(err);
          }
        })
      }
      // 失败
      const rejectMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = onRejected(this.PromiseResult);
            this.resolvePromise(x, promise2, resolve, reject);
          } catch (err) {
            reject(err);
          }
        })
      }
      // 如果`PromiseState`为`fulfiled`时执行第一个回调(成功的回调)
      if (this.PromiseState === 'fulfiled') {
        resolveMicrotask()
        // 如果`PromiseState`为`rejected`时执行第二个回调(失败的回调)
      } else if (this.PromiseState === 'rejected') {
        rejectMicrotask()
        // 如果`PromiseState`为`pending`时,暂时保存两个回调
      } else if (this.PromiseState === 'pending') {
        this.onFulfilledCallbacks.push(resolveMicrotask)
        this.onRejectedCallbacks.push(rejectMicrotask)
      }
    })
    return promise2;
  }
  resolvePromise(x, promise, resolve, reject) {
    if (x === promise) {
      return reject(new TypeError('The promise and the return value are the same'));
    }
    // 同我们原来的判断 (x instanceof MyPromise) ,这里只是为了和PromiseA+规范保持统一
    if (typeof x === 'object' || typeof x === 'function') {
      if (x === null) {
        return resolve(x);
      }
      let then;
      try {
        then = x.then;
      } catch (err) {
        return reject(err);
      }

      if (typeof then === 'function') {
        let called = false;
        try {
          then.call(x, y => {
            if (called) return;
            called = true;
            this.resolvePromise(y, promise, resolve, reject);
          }, r => {
            if (called) return;
            called = true;
            reject(r);
          })
        } catch (err) {
          if (called) return;
          reject(err);
        }
      } else {
        resolve(x);
      }
    }
    else {
      resolve(x);
    }
  }
  // 静态resolve方法
  static resolve = (value) => {
    if (value instanceof MyPromise) {
      return value;
    }
    // 常规resolve处理
    return new MyPromise((resolve, reject) => {
      resolve(value);
    })
  }
  // 静态reject方法
  static reject = (reason) => {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    })
  }
}
复制代码

10、发布订阅

一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。

// 调度中心
class PubSub {
  constructor() {
    this.subscribers = new Set();  // 缓存列表
  }
  // 添加订阅
  subscribe(ob) {
    this.subscribers.add(ob);
  }
  // 移除订阅
  unsubscribe(ob) {
    this.subscribers.delete(ob);
  }
  // 发布消息
  publish(topic, params) {
    for (let ob of this.subscribers) {  // 遍历缓存列表
      ob.update(topic, params)
    }
  }
}

// 发布者
class EventBus {
  constructor(topic) {
    this.topic = topic;
  }
  // 推送消息
  pushArticle(pubSub, val) {
    pubSub.publish(this.topic, val)
  }
}

// 订阅者
class Event {
  constructor(name) {
    this.name = name;
  }
  update(topic, val) {
    console.log(`${topic}${this.name}发送消息:${val}`)
  }
}

// 测试

const pubSub = new PubSub();
const eventBus = new EventBus("老师");
const tom = new Event('Tom');
const jim = new Event('Jim');

pubSub.subscribe(tom);
pubSub.subscribe(jim);
eventBus.pushArticle(pubSub, '今天下午放学留下来背书');
// 老师给Tom发送消息:今天下午放学留下来背书
// 老师给Jim发送消息:今天下午放学留下来背书

复制代码

博客主要记录一些学习的文章,如有不足,望大家指出,谢谢。

猜你喜欢

转载自juejin.im/post/7109360747074830372