js手写源码系列,参考各种方式和视频,有需求的小伙伴自取

202 阅读13分钟

手写系列

Promise(setTimeout方式)

/* promise特点 */
// 1. 有三种状态(默认为pending)
// 2. 状态改变后不可再改变
// 3. 执行体里出问题,状态变为拒绝(try catch)
// 4. then构建(判断状态)(不传resolve或reject不报错)(then使用setTimeout变成异步)(执行体状态改变为异步的处理)
// 5. then的链式操作(then返回promise)(返回已解决也是走成功)
// 6. then的穿透传递
// 7. then返回promise的处理(对return的值作区分)
// 8. promise返回值作约束
// 9. 实现Promise.resolve()和Promise.reject()
// 10. promise的all方法
// 11. promise的race方法
class HD {
  static PENDING = "pending";
  static FUFILLED = "fulfilled";
  static REJECTED = "rejected";
  constructor(executor) {
    this.status = HD.PENDING;
    this.value = null;
    // 储存状态未改变前需要执行的函数
    this.callbacks = [];
    try {
      // 需要bind绑定this为HD,因为外部调用this为undefined(class模式默认遵循严格模式,指向window就为undefined)(resolve()或reject()之后的同步代码应先执行)
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error);
    }
  }
  resolve(value) {
    if (this.status == HD.PENDING) {
      this.status = HD.FUFILLED;
      this.value = value;
      // resolve()或reject()之后的同步代码应先执行
      setTimeout(() => {
        this.callbacks.forEach((callback) => {
          callback.onFulfilled(value);
        });
      });
    }
  }
  reject(reason) {
    if (this.status == HD.PENDING) {
      this.status = HD.REJECTED;
      this.value = reason;
      setTimeout(() => {
        this.callbacks.forEach((callback) => {
          callback.onRejected(reason);
        });
      });
    }
  }
  then(onFulfilled, onRejected) {
    // 不传resolve或reject不报错
    if (typeof onFulfilled != "function") {
      // then的穿透传递
      onFulfilled = () => this.value;
    }
    if (typeof onRejected != "function") {
      // then的穿透传递
      onRejected = () => this.value;
    }

    // 执行体状态改变为异步的处理
    let promise = new HD((resolve, reject) => {
      if (this.status == HD.PENDING) {
        this.callbacks.push({
          onFulfilled: (value) => {
            this.parse(promise, onFulfilled(value), resolve, reject);
          },
          onRejected: (value) => {
            this.parse(promise, onRejected(value), resolve, reject);
          },
        });
      }

      // 判断状态是否改变
      if (this.status == HD.FUFILLED) {
        // then使用setTimeout变成异步
        setTimeout(() => {
          this.parse(promise, onFulfilled(this.value), resolve, reject);
        });
      }
      if (this.status == HD.REJECTED) {
        // then使用setTimeout变成异步
        setTimeout(() => {
          this.parse(promise, onRejected(this.value), resolve, reject);
        });
      }
    });
    // then返回promise
    return promise;
  }
  parse(promise, result, resolve, reject) {
    // promise返回值作约束
    if (promise == result) {
      throw new TypeError("Chaining cycle detected for promise");
    }
    try {
      // then返回promise的处理
      if (result instanceof HD) {
        // HD(promise)的实例
        result.then(resolve, reject);
      } else {
        // 这里普通的
        return result;
      }
    } catch (error) {
      reject(error);
    }
  }
  // 实现Promise.resolve()和Promise.reject()
  static resolve(value) {
    return new HD((resolve, reject) => {
      if (value instanceof HD) {
        value.then(resolve, reject);
      } else {
        resolve(value);
      }
    });
  }
  static reject(value) {
    return new HD((resolve, reject) => {
      if (value instanceof HD) {
        value.then(resolve, reject);
      } else {
        reject(value);
      }
    });
  }
  // promise的all方法
  static all(promises) {
    const values = new Array(promises.length);
    let count = 0;
    return new HD((resolve, reject) => {
      promises.forEach((promise, index) => {
          // HD.promise(promise)解决了传递非promise参数的问题
        HD.promise(promise).then(
          (value) => {
            // 这里保证了all的顺序与输出顺序一致
            values[index] = value;
            count++;
            if (count == promises.length) {
              resolve(values);
            }
          },
          (reason) => {
            reject(reason);
          }
        );
      });
    });
  }
  // promise的race方法
  static race(promises) {
    return new HD((resolve, reject) => {
      promises.forEach((promise, index) => {
        promise.then(
          (value) => {
            resolve(values);
          },
          (reason) => {
            reject(reason);
          }
        );
      });
    });
  }
}

vue2响应式原理(defineProperty + 发布订阅),不完整仅作参考

  • 参考资料

  • 参考视频

  • defineReactive函数:

    利用defineProperty 劫持数据

    import observe from "./observe";
    
    export default function defineReactive(data, key, val) {
      if (arguments.length == 2) {
        // 这里的val代表下一层的
        val = data[key];
      }
      // 子元素要进行observe,至此形成递归。这个递归不是函数自己调用自己,而是多个函数、类循环调用
      let childOb = observe(val);
    
      // val提供了一个闭包环境
      Object.defineProperty(data, key, {
        // 可枚举
        enumerable: true,
        // 可以被配置
        configurable: true,
        get() {
          console.log("访问" + key + "属性");
          return val;
        },
        set(newValue) {
          if (newValue === val) {
            console.log("改变" + key + "属性");
              val = newValue;
              // 当设置了新值,这个新值也要被observe
              childOb = observe(newValue)
          }
        },
      });
    }
    
    
  • Observer类

    将一个正常的object转换为每个层级都是响应式(可以被侦测的object)

    import Observer from "./Observer";
    // 创建observe函数,辅助判别
    export default function observe(value) {
      // 如果不是对象,什么都不做
      if (typeof value != "object") return;
    
      // 定义ob
      let ob;
      if (typeof value.__ob__ != "undefined") {
        ob = value.__ob__;
      } else {
        ob = new Observer(value);
      }
      return ob;
    }
    
  • observe函数

辅助判别


import {def} from './utils'
import defineReactive from './defineReactive';
import { arrayMethods } from './array'
import observe from './observe';
// 将一个正常的object转换为每个层级都是响应式(可以被侦测的object)
export default class Observer{
    constructor(value) {
        // console.log('我是observer构造器', value);
        // 给实例添加了__ob__属性,值是这次new的实例
        def(value, '__ob__', this, false)
        // 检查它是数组还是对象
        if (Array.isArray(value)) {
            // 如果是数组,要非常强行的将这个数组的原型指向这个arrayMethods
            Object.setPrototypeOf(value, arrayMethods)
            // 让这个数组变的observe
            this.observeArray(value)
        } else {
            // 执行循环
            this.walk(value)
        }

    }
    // 遍历:给该对象(value)下所有属性使用defineReactive
    walk(value) {
        // 深度优先遍历
        for (let key in value) {
            defineReactive(value,key)
        }
    }
    // 数组的特殊遍历
    observeArray(arr) {
        for (let i = 0, l = arr.length; i < l; i++){
            // 逐项进行observe
            observe(arr[i])
        }
    }
}
  • def工具函数

使某属性(_ob_)变成不可枚举属性

// 工具函数:使某属性变成不可枚举属性
export const def = function (obj, key, value, enumerable) {
    
    Object.defineProperty(obj, key, {
        value,
        enumerable,
        writable: true,
        configurable:true
    })
}
  • array函数

    重写了数组的七个方法 "push","pop","shift", "unshift", "splice","sort","reverse"

import { def } from "./utils";

// 得到Array.prototype
const arrayPrototype = Array.prototype;

// Array.prototype为原型创建arrayMethods对象
export const arrayMethods = Object.create(arrayPrototype);
// 暴露

// 要被改写的七个数组方法
const methodsNeedChange = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse",
];

methodsNeedChange.forEach((methodName) => {
  // 备份原来的方法,因为push,pop等7个函数的功能不能被剥夺
  const original = arrayPrototype[methodName];

  // 定义新的方法
  def(
    arrayMethods,
    methodName,
    function () {
      // 恢复原来的功能
      let result = original.apply(this, arguments);
      // 把这个数组身上的__ob__取出来,__ob__已经被添加了?因为数组肯定不是最高层
      // this.指的是调用方法的数组
      const ob = this.__ob__;

      // push,unshift,splice能够插入新项,现在要把插入的新项也要变为observe的
      let inserted = [];
      switch (methodName) {
        case "push":
        case "unshift":
          inserted =  [...arguments];

          break;
        case "splice":
          // Splice格式是splice(下标,数量,插入的新项)
          inserted = [...arguments].slice(2);
          break;

      }
      // 判断有没有插入的新项,让新项也变为响应的
      if (inserted.length) {
        ob.observeArray(inserted);
      }

      return result;
    },
    false
  );
});

  • 收集依赖

    在getter中收集依赖,在setter中触发依赖

    1. 依赖就是watcher。只有Watcher触发的getter才会收集依赖,哪个watcher触发了getter,就把哪个watcher收集到Dep中。
    2. Dep使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所有的watcher都通知一遍。
    3. 代码实现的巧妙之处: watcher把自己设置到全局的一个指定位置,然后读取数据,因为读取了数据,所以会触发这个数据的getter。在getter中就能得到当前正在读取数据的watcher,并把这个watcher收集到Dep中。

防抖节流函数

// 防抖
  function debounce(callback, delay) {
    // timer使用了闭包
    let timer = null;

    return function () {
      // 记录函数的执行环境
      let context = this;
      let args = arguments;
      // 如果此时存在定时器的话,则取消之前的定时器重新记时
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => {
        // 在实际执行函数的环境中执行,直接执行的话this指向window
        callback.apply(context, args);
      }, delay);
    };
  }

// 节流
  function throttle(callback, delay) {
    // flag使用了闭包
    let flag = false;
    return function () {
      let context = this;
      args = arguments;
      // 判断是否执行
      if (flag) return;
      flag = true;
      callback.apply(context, args);
      setTimeout(() => {
        flag = false;
      }, delay);
    };
  }

Promise实现并发控制

  • 参考资料

  • 实现一个输入url数组与并发数的并发数量控制请求队列函数。

  • 核心思路:控制并发数量,关键点就是利用promise,当一个请求完成后,去发起下一个请求。

const fn = url => {
    // 实际场景这里用axios等请求库 发请求即可 也不用设置延时
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('完成一个任务', url, new Date());
            resolve({ url, date: new Date() });
        }, 1000);
    })
};

function limitQueue(urls, limit) {
    // 完成任务数
    let i = 0;
    // 填充满执行队列
    for (let excuteCount = 0; excuteCount < limit; excuteCount++) {
        run();
    }
    // 执行一个任务
    function run() {
        // 构造待执行任务 当该任务完成后 如果还有待完成的任务 继续执行任务(核心思路)
        new Promise((resolve, reject) => {
            const url = urls[i];
            i++;
            resolve(fn(url))
        }).then(() => {
            if (i < urls.length) run()
        })
    }
};

Promise封装原生Ajax

function Ajax(url, method, data) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.response);
        } else {
          reject(new Error("error"));
        }
      }
    };
    if (method.toUpperCase() === "GET") {
      let paramsList = [];
      for (let key in data) {
        paramsList.push(key + "=" + data[key]);
      }
      let params = paramsList.join("&");
      url = url + "?" + params;
      xhr.open("get", url, true);
      xhr.send();
    } else if (method.toUpperCase() === "POST") {
      xhr.open("post", url, true);
      xhr.setRequestHeader(
        "Content-Type",
        "application/x-www-form-urlencoded;charset=utf-8"
      );
      xhr.send(data);
    }
  });
}

Promise封装一个delay函数

const delay = function (time) {
  if (typeof time !== "number") return;
  return new Promise((resolve, reject) => {
    setTimeout(resolve, time);
  });
};

手写Promise.all()

function PromiseAll(promiseArray) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promiseArray))
      return reject(new Error("传入的参数不是数组!"));
    const values = [];
    let count = 0;
    promiseArray.forEach((promise, index) => {
        // 使用 Promise.resolve是因为兼容传递非promise的参数项
      Promise.resolve(promise).then(
        (res) => {
            // 这里保证了all的顺序与输出顺序一致
          values[index] = res;
          count++;
          if (count == promiseArray.length) resolve(values);
        },
        (error) => {
          return reject(error);
        }
      );
    });
  });
}

手写Promise.race()

function PromiseRace(promiseArray) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promiseArray))
      return reject(new Error("传入的参数不是数组!"));
    promiseArray.forEach((promise, index) => {
      Promise.resolve(promise).then(
        ((res) => {
          resolve(res);
        },
        (error) => {
          reject(error);
        })
      );
    });
  });
}

数组去重

深拷贝

function deepClone(target) {
    // WeakMap作为记录对象Hash表(用于防止循环引用)
    const map = new WeakMap()

    // 判断是否为object类型的辅助函数,减少重复代码
    function isObject(target) {
        return (typeof target === 'object' && target ) || typeof target === 'function'
    }

    function clone(data) {

        // 基础类型直接返回值
        if (!isObject(data)) {
            return data
        }

        // 日期或者正则对象则直接构造一个新的对象返回
        if ([Date, RegExp].includes(data.constructor)) {
            return new data.constructor(data)
        }

        // 处理函数对象
        if (typeof data === 'function') {
            return new Function('return ' + data.toString())()
        }

        // 如果该对象已存在,则直接返回该对象
        const exist = map.get(data)
        if (exist) {
            return exist
        }

        // 处理Map对象
        if (data instanceof Map) {
            const result = new Map()
            map.set(data, result)
            data.forEach((val, key) => {
                // 注意:map中的值为object的话也得深拷贝
                if (isObject(val)) {
                    result.set(key, clone(val))
                } else {
                    result.set(key, val)
                }
            })
            return result
        }

        // 处理Set对象
        if (data instanceof Set) {
            const result = new Set()
            map.set(data, result)
            data.forEach(val => {
                // 注意:set中的值为object的话也得深拷贝
                if (isObject(val)) {
                    result.add(clone(val))
                } else {
                    result.add(val)
                }
            })
            return result
        }

        // 收集键名(考虑了以Symbol作为key以及不可枚举的属性)
        const keys = Reflect.ownKeys(data)
        // 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性以及对应的属性描述
        const allDesc = Object.getOwnPropertyDescriptors(data)
        // 结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链, 这里得到的result是对data的浅拷贝
        const result = Object.create(Object.getPrototypeOf(data), allDesc)

        // 新对象加入到map中,进行记录
        map.set(data, result)

        // Object.create()是浅拷贝,所以要判断并递归执行深拷贝
        keys.forEach(key => {
            const val = data[key]
            if (isObject(val)) {
                // 属性值为 对象类型 或 函数对象 的话也需要进行深拷贝
                result[key] = clone(val)
            } else {
                result[key] = val
            }
        })
        return result
    }

    return clone(target)
}

Vue Router实现原理

EventBus(含emit、on、once、off)

  • 参考资料
  • 注意点:
    1. 订阅事件如何存储?
    2. 如何传递参数?
    3. 如何给每个订阅事件添加唯一标识?
    4. 如何正确删除存储的订阅事件?
    5. $on$emit主要担任的是什么角色?
    6. 什么时发布订阅模式?
  class EventBus {
    constructor() {
      this.eventObj = {};
      this.callbackId = 0;
    }
    $on(eventNanme, callback) {
      if (!this.eventObj[eventNanme]) {
        this.eventObj[eventNanme] = {};
      }
      this.callbackId++;
      this.eventObj[eventNanme][this.callbackId] = callback;
      return this.callbackId;
    }
    $emit(eventNanme, ...args) {
      if (!this.eventObj[eventNanme]) {
        return new Error("未监听该事件");
      }
      for (let id in this.eventObj[eventNanme]) {
        this.eventObj[eventNanme][id](...args);
        if (id.indexOf("d") != -1) {
          delete this.eventObj[eventNanme][id];
        }
      }
      if (!Object.keys(this.eventObj[eventNanme]).length) {
        delete this.eventObj[eventNanme];
      }
    }
    $once(eventNanme, callback) {
      if (!this.eventObj[eventNanme]) {
        this.eventObj[eventNanme] = {};
      }
      this.callbackId++;
      this.eventObj[eventNanme]["d" + this.callbackId] = callback;
      return this.callbackId;
    }
    $off(eventNanme, callbackId) {
      if (
        !(eventNanme in this.eventObj) ||
        !Object.keys(eventNanme[callbackId]).length
      ) {
        throw new Error("事件不存在或处理函数不存在");
        return;
      }
      if (!eventNanme && !callbackId) {
        // 如果没有提供参数,则移除所有的事件监听器
        this.eventObj = {};
      } else if (eventNanme && !callbackId) {
        // 如果只提供了事件,则移除该事件所有的监听器;
        delete this.eventObj[eventNanme];
      } else if (eventNanme && callbackId) {
        // 如果同时提供了事件与回调,则只移除这个回调的监听器。
        delete this.eventObj[eventNanme][callbackId];
        if (!Object.keys(this.eventObj[eventNanme]).length) {
          delete this.eventObj[eventNanme];
        }
      } else {
        throw new Error("$off调用格式错误");
        return;
      }
    }
  }

手写类型判断函数

function getType(value) {
  // 判断数据是 null 的情况
  if (value === null) {
    return value + "";
  }
  // 判断数据是引用类型的情况
  if (typeof value === "object") {
    let valueClass = Object.prototype.toString.call(value),
      type = valueClass.split(" ")[1].split("");
    type.pop();
    return type.join("").toLowerCase();
  } else {
    // 判断数据是基本数据类型的情况和函数的情况
    return typeof value;
  }
}

手写instanceOf

  • 核心思想:

    沿着原型链向上查找,检查目标对象的原型链中是否有与指定对象的原型相同的原型!

function myInstanceOf(left, right) {
    if (typeof left !== "object" || left === null) return false;
    if (typeof right !== "function" || !right.prototype) {
      throw new TypeError(
        "Right-hand side of " instanceof " is not an object"
      );
    }

    let proto = left.__proto__;

    while (proto !== null) {
      if (right.prototype === proto) return true;
      proto = proto.__proto__;
    }

    return false;
  }

手写new操作符

  function objectFactory() {
    // 提取constructor构造函数
    let constructor = Array.prototype.shift.apply(arguments);
    if (typeof constructor !== "function") {
      throw Error("必须为函数");
      return;
    }
    // 创建空对象
    let newObj = null;
    // 将空对象的原型指向构造函数的原型
    newObj = Object.create(constructor.prototype);
    // 调用构造函数并让构造函数的this指向新创建的对象
    let result = constructor.apply(newObj, arguments);
    // 判断 return 的值类型
    const flag =
      result &&
      (typeof result === "object" || typeof result === "function");

    return flag ? result : newObj;
  }
  // 使用方法
  objectFactory(构造函数, 初始化参数);

手写call及apply

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 处理传入的参数,截取第一个参数后的所有参数。
  4. 将函数作为上下文对象的一个属性。
  5. 使用上下文对象来调用这个方法,并保存返回结果。
  6. 删除刚才新增的属性。
  7. 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {
    console.error("type error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
      result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;
  // 将调用函数设为对象的方法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};
// apply 函数实现
Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  let result = null;
  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;
  // 将函数设为对象的方法
  context.fn = this;
  // 调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  // 将属性删除
  delete context.fn;
  return result;
};

用call/apply实现bind

bind 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 保存当前函数的引用,获取其余传入参数值。
  3. 创建一个函数返回
  4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。
// bind 函数实现
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  var args = [...arguments].slice(1),
      fn = this;
  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};

实现数组的flat方法

function _flat(arr, depth) {
  // 判断arr是否为数组
  if(!Array.isArray(arr) || depth <= 0) {
    return arr;
  }
  return arr.reduce((prev, cur) => {
    // 当前项是否为数组,如果是数组则继续递归
    if (Array.isArray(cur)) {
      return prev.concat(_flat(cur, depth - 1))
    } else {
      return prev.concat(cur);
    }
  }, []);
}

数组扁平化

普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:

function flatten(arr) {
  let result = [];

  for(let i = 0; i < arr.length; i++) {
    if(Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]));
    } else {
      result.push(arr[i]);
    }
  }
  return result;
}

函数柯里化

// 柯里化函数
 function curry (fn) {
    return function nest(...args) {
      // fn.length表示函数的形参个数
      if (args.length === fn.length) {
        // 当参数接收的数量达到了函数fn的形参个数,即所有参数已经都接收完毕则进行最终的调用
        return fn(...args);
      } else {
        // 参数还未完全接收完毕,递归返回judge,将新的参数传入
        return function (arg) {
          return nest(...args, arg);
        };
      }
    };
  };

手写 reduce

    function myReduce(callback, initValues) {
      const arr = this;
      if (!Array.isArray(arr)) {
        throw new Error("error");
      }

      const result = initValues;

      for (let i = 0; i < arr.length; i++) {
        result = callback(result, arr[i], i, result);
      }

      return result;
    }

手写 Object.create

function myCreate(proto) {
  if (typeof proto !== 'object' && typeof proto !== 'function' || proto === null) {
    throw new TypeError('Object prototype may only be an Object or null');
  }
  function F() {}
  F.prototype = proto;
  return new F();
}