学会这几个再也不怕JS手写了

355 阅读8分钟

手写instanceof

const newInstanceof = (val, type) => {

  // 判断基础类型直接返回false
  if (val === null || typeof val !== 'object') return false

  // 获取到 val 的原型对象
  let valProto = Object.getPrototypeOf(val)

  while (true) {  //循环往下寻找,直到找到相同的原型对象
    if (valProto === null) return false
    // 判断构造函数 type的prototype与val的原型相等不
    if (valProto === type.prototype) return true
    valProto = Object.getPrototypeOf(valProto)
  }

}

手写 call

   let obj = {
      name: '九思'
    }

    function fn () {
      console.log(a+b+this.name)
    }

    // 
    Function.prototype.myCall = function (obj, ...args) {

      // 1. 将方法挂载到我们传入的obj上下文对象上
      // 2. 然后将挂载以后的方法调用(执行对象的这个方法)
      // 3. 将添加的这个属性删除

      console.log(this) // 是fn 函数 谁调用myCall 谁就是this

      // 保证 当前xxx 是唯一的, 防止obj本身就有xxx这个属性
      let xxx = Symbol(1)

      obj[xxx] = this  // 把调用myCall方法的那个fn传给了他
      //  1. myCall 的内部this 是指向调用者fn函数(对象的)
      //  2. Obj.xxx 就是fn函数,obj对象调用了fn函数因此fn函数内部this指向了obj (this 谁调用指向谁)
      obj[xxx](...args)
      delete obj[xxx]

    }

    fn.myCall(obj, 'this','name')

手写 apply


Function.prototype.myApply = function (obj, args = []) {

      // 将方法挂载到我们传入的ctx
      // 然后将挂载以后的方法调用
      // 将添加的这个属性删除
      // 判断参数是否是数组
      
      let fn = Symbol(1)
      if (args && !(args instanceof Array)) {
        // 报错
        throw('只接收数组')
      }
        obj[fn] = this
        obj[fn](...args)
        delete obj[fn]
      }


手写 bind

Function.prototype.myBind = function (obj, ...args) {
	
    let fn = Symbol(1)
    return (...args1) => {
        obj[fn] = this
        obj[fn](...args.concat(args1))
        delete obj[fn]
    }

}


手写new 操作符

  1. 创建一个空对象
  2. 连接该对象,将该对象的原型对象连接到构造函数的原型对象上
  3. 将创建的对象作为this的上下文
  4. 若函数没有返回对象则返回this

image.png

image.png 看到一个通俗的介绍 某开发商先盖了一间样板房(prototype)然后根据用户需求增量修改图纸 (constructor)然后根据图纸制造出个性化的房子(instance)

function myNew(Constructor, ...args) {
    // 创建对象 直接继承构造函数的原型对象,相当于设置obj.__proto__ = Constructor.prototype
    const obj = Object.create(Constructor.prototype)
    const result = Constructor.call(obj, ...args)
    // result || obj 防止返回的是 null(因为 typeof null == 'object';
    return typeof result === 'object' ? result || obj : obj
}

使用setInterval实现setTimeout

const mySetTimeout = (func, time) => {
  let timerId = setInterval(() => {
    if (timerId) {
      clearInterval(timerId);
    }
    func();
  }, time);

  return () => clearInterval(timerId);
};

setTimeout 实现 setInterval

const mySetInterval = (func, time) => {
  let timeID = null;
  const fn = () => {
    timeID = setTimeout(() => {
      func();
      fn();
    }, time);
  };
  fn();

  return () => clearTimeout(timeID);
};

实现深拷贝和浅拷贝

浅拷贝

1. ...运算符
2. Object.assign()
3. slice / concat
4. Array.from

深拷贝

1. 使用JSON.parse(JSON.stringify(a)) 

// 1. 会忽略 undefined symbol 函数
// 2. 循环引用会报错 
// 3. new Date 转化结果不正确会被当作字符串处理  正则会被忽略
// 4. NaN infinity 会被当成null


2. 自定义
function cloneDeep(target) {
  if (obj === null) return null;
  if (obj instanceof RegExp) return new RegExp(obj);
  if (obj instanceof Date) return new Date(obj);
  if (target && typeof target === 'object') {
  	// 声明拷贝 
      let cloneTarget = Array.isArray(target) ? [] : {}
      for (let key in target) {
        // 判断 target value 是不是对象,是就递归
          if (target.hasOwnProperty(key)) {
            if (target[key] && typeof target[key] === 'object') {
              cloneTarget[key] = cloneDeep(target[key])
            } else {
              cloneTarget[key] = target[key]
            }
          }
      }
      return cloneTarget
  } else {
    return target
  }
}

3. 使用 reduce 函数
const deepCopy = function(obj){
    return Object.keys(obj).reduce(function (copy, item){
      // 如果对象的 value 类型为 object 就再次执行 deepCopy 来实现深拷贝
      copy[item] = typeof obj[item] === 'object' ? deepCopy(obj[item]) : obj[item];
      return copy;
      // 判断 obj 是 数组类型 还是对象类型
    }, Object.prototype.toString.call(obj) === '[object Array]' ? [] : {})
  }
  
 
 4.
 const deepClone = (target, hash = new WeakMap()) => {
  if (target === null) return target;
  if (target instanceof Date) return new Date(target); // 处理日期
  if (target instanceof RegExp) return new RegExp(target);
  if (target instanceof HTMLElement) return target; // 处理dom 元素
  if (typeof target === "function") return target; // 处理函数
  if (typeof target !== "object") return target; // 处理原始类型或者函数

  if (hash.get(target)) return hash.get(target); // 当需要拷贝当前对象时候,先去存储空间找,找到直接返回
  const cloneTarget = new target.constructor(); // 创建一个新的克隆对象或克隆数组
  hash.set(target, cloneTarget); // 存储空间没有就存进hash 中

  Reflect.ownKeys(target).forEach((key) => {
    // 利用Reflect 来处理Symbol 建明
    cloneTarget[key] = deepClone(target[key], hash);
  });

  return cloneTarget;
};

手写防抖

function debounce(fn, ms) {
    let timer = null;
    return function (...args) {
      if (timer) {
        clearTimeout(timer)
      }
      timer = setTimeout(() => {
        fn(...args)
        timer = null;
      }, ms);
    }
  }

useDebounce

function useDebounce(fn, delay, dep = []) {
  const { current } = useRef({ fn, timer: null });
  useEffect(function () {
    current.fn = fn;
  }, [fn]);

  return useCallback(function f(...args) {
    if (current.timer) {
      clearTimeout(current.timer);
    }
    current.timer = setTimeout(() => {
      current.fn(...args);
    }, delay);
  }, dep)
}

手写节流

function throttle(fn, ms) {
    let lastTime = 0;
    return function (arguments) {
      let nowTime = new Date().getTime()
      if (nowTime - lastTime > ms) {
        fn.apply(this, arguments)
        lastTime = nowTime
      }
    }
  }

useThrottle

function useThrottle(fn, delay, dep = []) {
  const { current } = useRef({ fn, timer: null });
  useEffect(function () {
    current.fn = fn;
  }, [fn]);

  return useCallback(function f(...args) {
    if (!current.timer) {
      current.timer = setTimeout(() => {
        delete current.timer;
      }, delay);
      current.fn(...args);
    }
  }, dep);
}

发布订阅模式

基于一个事件中心,要接收通知的对象通过定义事件订阅,然后保存到事件中心,发布通知的对象触发后,通过事件中心执行里面的订阅事件

const Events = {
  listenList: {},
  age: 16,
  listen: (key, fn) => {
    if (!Events.listenList[key]) {
      Events.listenList[key] = [];
    }
    Events.listenList[key].push(fn);
  },

  emit: (key, ...value) => {
    const listenList = Events.listenList[key];
    if (listenList) {
      listenList.forEach((item) => item(...value));
    }
  },

  remove: (key, fn) => {
    if (!Events.listenList[key]) return false;
    if (!fn) {
      delete Events.listenList[key];
    } else {
      const currentList = Events.listenList[key];
      currentList.forEach((fn, i) => {
        if (fn === fn) {
          currentList.splice(i, 1);
        }
      });
    }
  },
};

Events.listen("houser", (args) => {
  console.log("args houser", args, "---->housrer");
});

Events.listen("car", (args) => {
  console.log("args car", args, "---->car");
});

Events.emit("houser", 2500000);
Events.emit("car", 2500000);

发布订阅模式,一般是先订阅然后发布,但是,发布者发布事件的时候,不关心有没有订阅,如果没有订阅也能发布,订阅者不管心有没有事件发布,只要自己的事件发生,他就执行。所以,发布订阅模式中,发布者和订阅者互不关心对方,耦合度低,订阅者也可以自定义事件处理程序。

缺点: 当事件越来越多时候,命名不规范可能会导致不好维护。

观察者模式

观察者模式分为观察者和被观察者

被观察者相当于发布者, 当被观察者的状态变化的时候,会主动去通知所有的观察者, 将参数传给他们,然后执行让观察者执行他们自己的方法。

观察者和被观察者耦合在一起,必须要被观察者中注册了观察者后,才能和该观察者进行参数传递,并且还是当被观察者状态变化后,主动通知

// 观察者模式 内部基于发布订阅

class Subject {
  // 被观察者

  constructor() {
    this.arr = [];
  }

  attach(fn) {
    // 被观察者要接收观察者
    this.arr.push(fn);
  }

  setState(args) {
    this.arr.forEach((item) => item.update(args));
  }
}

class Observer {
  // 观察者
  update(args) {
    console.log("被观察者说 " + args, "--->args");
  }
}

let observer1 = new Observer("第一个观察者");
let observer2 = new Observer("第二个观察者");

let subject = new Subject();
// 搜集观察者
subject.attach(observer1);
subject.attach(observer2);

subject.setState("啦啦啦");

柯里化

先来介绍几个概念

  • 柯里化: 的含义就是让一个函数变的更具体一些, 原则上返回的函数只能接受一个参数,多个参数也暂且认为是柯里化 sum(1)(2)

  • 偏函数: 返回一个函数,函数的参数不止一个 sum(1,2)(3,4)(5)

  • 反柯里化: 让函数的作用范围变大

// 柯里化函数其实相当于一个高阶函数
// 用高级函数提前把其中一个变量写好。调用的时候只需要填写一个变量就好
// 高阶函数可以暂存变量(内部有闭包)

const add = (x, y, z, m) => {
  return x + y + z + m;
};

// console.log(add(1, 2, 3, 4));

const curry = (x) => {
  return (y) => {
    return (z) => {
      return (m) => {
        return x + y + z + m;
      };
    };
  };
};

// console.log(curry(1)(2)(3)(4));

// 要判断当前传入函数的参数个数 (args.length)
// 是否大于等于原函数所需参数个数 (fn.length) ,
// 如果是,则执行当前函数;如果是小于,则返回一个函数。

const curryFun = (fn) => {
  const curry = (...args) => {
    if (args.length >= fn.length) {
      return fn(...args);
    } else {
      return (...a) => curry(...args, ...a);
    }
  };

  return curry;
};

const curry1 = curryFun(add);

console.log(curry1(1, 2, 3)(4));
console.log(curry1(1)(2)(3)(4));
console.log(curry1(1)(2)(3, 4));

手写promise.all

function promiseAll(list) {
  return new Promise((resolve, reject) => {
    const r = [];
    const promiseList = Array.from(list);
    let count = 0;

    for (let i = 0; i < promiseList.length; i++) {
      Promise.resolve(promiseList[i])
        .then((o) => {
          r[i] = o;
          count++;
          if (count === promiseList.length) {
            return resolve(r);
          }
        })
        .catch((e) => reject(e));
    }
  });
}

手写promise.race

// 如果是一般值,p1成功,value就是这个值
    const p1 = Promise.resolve(2);
    // 如果是成功的primie, p2成功,value 就是这个promise成功的value
    const p2 = Promise.resolve(Promise.resolve(3));
    // 如果是失败的promise. p3失败,reason 是这个promise 的reason
    const p3 = Promise.resolve(Promise.reject(4));

    // Promise函数对象的reject方法
    // 返回一个指定reason的失败的Promise

    Promise.reject = function (reason) {
      // 返回一个失败的promise
      return new Promise((resolve, reject) => {
        reject(reason);
      });
    };

    // promise 函数对象的resolve方法
    // 返回一个指定结果的成功的promies
    Promise.resolve = function (value) {
      // 返回一个成功或者失败的promise
      return new Promise((resolve, reject) => {
        // 如果value 是一个promise
        if (value instanceof Promise) {
          //  使用value结果作为promise结果
          value.then(resolve, reject);
        } else {
          // value 不是promise => promise 变为成功,数据是value
          resolve(value);
        }
      });
    };

    // Promise.all
    // 返回一个新的promise
    Promise.all = function (promises) {
      const values = new Array(promises.length); // 用来保存所有成功values的数组
      // 记录成功promise的数量
      let i = 0;

      return new Promise((resolve, reject) => {
        // 遍历promise获取每一个promise的结果
        promises.forEach((p, index) => {
          Promise.resolve(p).then(
            (value) => {
              // p成功,将成功的value保存到values中
              // 不能直接values.push(value)这样不能保证顺序,成功的会先push
              values[index] = value;
              i++;
              // 如果全部成功,将return的promise改变成功
              if (i === promises.length) {
                resolve(values);
              }
            },
            (reason) => {
              // 只要有一个失败的,return的promise就失败
              reject(reason);
            }
          );
        });
      });
    };

    // Promise.race

    Promise.race = function (promises) {
      return new Promise((resolve, reject) => {
        // 遍历promise获取每一个promise的结果
        promises.forEach((p, index) => {
          p.then(
            (value) => {
              // 一旦有成功的。返回的变为成功
              resolve(value);
            },
            (reason) => {
              // 只要有一个失败的,return的promise就失败
              reject(reason);
            }
          );
        });
      });
    };

手写promise

简单实现

class MyPromise {

  PromiseStatus = 'pending'
  PromiseResult = undefined

  // 保存两组回调函数
  resolveList = []
  rejectList = []

  constructor (excetor) {
    excetor(this.resolve.bind(this), this.reject.bind(this))
  }

  resolve (value) {
    if (PromiseStatus !== "pending") return
    this.PromiseStatus = 'fulfilled'
    this.PromiseResult(reason)
    while (this.resolveList.length) {
      this.resolveList.shift()()
    }
  }

  reject (reason) {
    if (PromiseStatus !== "pending") return
    this.PromiseStatus = 'reject'
    this.PromiseResult(reason)
    while (this.rejectList.length) {
      this.rejectList.shift()()
    }
  }

  // then 方法本身会返回一个新的promise对象
  // 该对象的状态和结果由回调函数的返回值决定 
  /* 
  返回值是promise对象
     返回值为成功,新promise就是成功
     返回值为失败,新promise就是失败

    返回值非promise对象
      新promise就是成功,他的值就是返回值
  */
  then (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason }


    const thenPromise = new MyPromise((resolve, reject) => {

      const resolvePromise = cb => {
         // 异步返回值异常外面补货不到
         queueMicrotask(() => {
            try {
               let s = cb(this.PromiseResult)
                  if (x === thenPromise) {
                    throw new Error('不能返回自身')
                  }
                  if (s instanceof MyPromise) {
                    s.then(resolve, reject)
                  } else {
                    resolve(s)
                  }
            } catch (error) {
                reject(err)
            }
           

          })
      }

        if (this.PromiseStatus === 'fulfilled') {
          resolvePromise(onFulfilled)
      } else if (this.PromiseStatus === 'rejectd') {
        resolvePromise(onRejected)
      } else if (this.PromiseStatus === 'pending') {
        this.resolveList.push(resolvePromise.bind(this, onFulfilled))
        this.rejectList.push( resolvePromise.bind(this, onRejected))
      }
    })

    return thenPromise
    
  }

}

箭头函数和普通函数区别

1.箭头函数不能用来创建生成器(即不能写为生成器函数)