Javascript手写深拷贝、事件总线

176 阅读4分钟

一、深拷贝

1.1 简单实现

  • 使用 JSON 方法

    • const newObj = JSON.parse(JSON.stringify(obj))
    • 缺点:
      • 对象中有new Date(),深拷贝后,时间会变成字符串的形式,而不是时间对象
      • 对象中有RegExp、Error对象,则序列化的结果会变成空对象{}
      • 对象中有function,undefined,则序列化的结果会把function 或 undefined丢失
      • 对象中有NaN、Infinity和-Infinity,则序列化的结果会变成null
      • JSON.stringify()只能序列化对象的可枚举的自有属性,如果obj中的对象是由构造函数生成的实例对象, 深拷贝后,会丢弃对象的constructor
      • 对象中存在循环引用的情况也无法正确实现深拷贝(会报错)
  • 对象和数组拷贝

    • 封装一个判断是否是对象的方法(null 返回 false)
    function isObject(value) {
      const valueType = typeof value
      return valueType !== null && (valueType === 'object' || valueType === 'function')
    }
    
    • 对象深拷贝
    function deepClone(originValue) {
      // 1.原始类型直接返回
      if (!isObject(originValue)) {
        return originValue;
      }
    
      // 2.如果是对象创建一个新的对象
      const newObj = {};
      for (const key in originValue) {
        newObj[key] = deepClone(originValue[key]);
      }
      return newObj;
    }
    
    • 数组深拷贝
    function deepClone(originValue) {
      // 1.原始类型直接返回
      if (!isObject(originValue)) {
        return originValue;
      }
    
      // 2.判断是数组还是对象
      const newObj = Array.isArray(originValue) ? [] : {};
      for (const key in originValue) {
        newObj[key] = deepClone(originValue[key]);
      }
      return newObj;
    }
    

2.2 其他类型的拷贝(Set、Map、Symbol、Function)

function deepClone(originValue) {
  // Symbol类型需要创建一个新的Symbol
  if (typeof originValue === "symbol") {
    return Symbol(originValue.description);
  }

  // 原始类型直接返回
  if (!isObject(originValue)) {
    return originValue;
  }

  // 函数类型直接返回
  if (typeof originValue === "function") {
    return originValue;
  }

  // Set类型
  if (originValue instanceof Set) {
    const newSet = new Set();
    originValue.forEach((v) => {
      newSet.add(deepClone(v));
    });
    return newSet;
  }

  // Map类型
  if (originValue instanceof Map) {
    const newMap = new Map();
    originValue.forEach((v, k) => {
      newMap.set(deepClone(k), deepClone(v));
    });
    return newMap;
  }

  // 判断是数组还是对象
  const newObj = Array.isArray(originValue) ? [] : {};

  for (const key in originValue) {
    newObj[key] = deepClone(originValue[key]);
  }

  // 单独遍历symbol
  const symbolKeys = Object.getOwnPropertySymbols(originValue);
  for (const symbolKey of symbolKeys) {
    newObj[Symbol(symbolKey.description)] = deepClone(originValue[symbolKey]);
  }

  return newObj;
}

2.3 循环引用的拷贝

如果有深拷贝,将拷贝过的对象赋值给新的引用

function deepClone(originValue, map = new WeakMap()) {
  // Symbol类型需要创建一个新的Symbol
  if (typeof originValue === "symbol") {
    return Symbol(originValue.description);
  }

  // 原始类型直接返回
  if (!isObject(originValue)) {
    return originValue;
  }

  // 函数类型直接返回
  if (typeof originValue === "function") {
    return originValue;
  }

  // Set类型
  if (originValue instanceof Set) {
    const newSet = new Set();
    originValue.forEach((v) => {
      newSet.add(deepClone(v, map));
    });
    return newSet;
  }

  // Map类型
  if (originValue instanceof Map) {
    const newMap = new Map();
    originValue.forEach((v, k) => {
      newMap.set(deepClone(k, map), deepClone(v, map));
    });
    return newMap;
  }

  // 判断是否有循环引用
  if (map.get(originValue)) {
    return map.get(originValue);
  }

  // 判断是数组还是对象
  const newObj = Array.isArray(originValue) ? [] : {};
  map.set(originValue, newObj);

  for (const key in originValue) {
    newObj[key] = deepClone(originValue[key], map);
  }

  // 单独遍历symbol
  const symbolKeys = Object.getOwnPropertySymbols(originValue);
  for (const symbolKey of symbolKeys) {
    newObj[Symbol(symbolKey.description)] = deepClone(originValue[symbolKey]);
  }

  return newObj;
}

二、事件总线

事件总线一般用于跨组件之间进行通信

  • 自定义事件总线属于一种观察者模式,其中包括三个角色:

    • 发布者(Publisher):发出事件(Event)
    • 订阅者(Subscriber):订阅事件(Event),并且会进行响应(Handler)
    • 事件总线(EventBus):无论是发布者还是订阅者都是通过事件总线作为中台的

2.1 on、emit、off

{ 'name': [fn1, fn2 ] }

class EventBus {
  constructor() {
    this.eventMap = {};
  }

  on(name, eventFn) {
    let eventFns = this.eventMap[name];
    if (!eventFns) {
      eventFns = [];
      this.eventMap[name] = eventFns;
    }
    eventFns.push(eventFn);
  }

  emit(name, ...args) {
    const eventFns = this.eventMap[name];
    if (!eventFns) return;
    eventFns.forEach((fn) => {
      fn(...args);
    });
  }

  off(name, eventFn) {
    const eventFns = this.eventMap[name];
    if (eventFns) {
      this.eventMap[name] = eventFns.filter((fn) => fn !== eventFn);
    }
  }
}

2.2 增加once功能

修改eventMap的数据结构:{'name': [{ fn: fn1, isOnce: false }]}

class EventBus {
  constructor() {
    this.eventMap = {};
  }

  on(name, eventFn, isOnce = false) {
    let eventFns = this.eventMap[name];
    if (!eventFns) {
      eventFns = [];
      this.eventMap[name] = eventFns;
    }
    eventFns.push({ fn: eventFn, isOnce: isOnce });
  }

  once(name, eventFn) {
    this.on(name, eventFn, true);
  }

  emit(name, ...args) {
    const eventFns = this.eventMap[name];
    if (!eventFns) return;

    // 使用filter执行函数并进行过滤
    this.eventMap[name] = eventFns.filter((item) => {
      const { fn, isOnce } = item;
      fn(...args);

      if (isOnce) return false;
      return true;
    });
  }

  off(name, eventFn) {
    const eventFns = this.eventMap[name];
    if (eventFns) {
      this.eventMap[name] = eventFns.filter((item) => item.fn !== eventFn);
    }
  }
}
  • 注意:在emit触发事件时的小细节,使用filter函数,不仅执行了回调而且过滤掉只执行一次的事件

2.3 once 功能-不使用额外参数

class EventBus {
  constructor() {
    this.eventMap = {};
  }

  on(name, eventFn) {
    const eventFns = this.eventMap[name];
    if (!eventFns) {
      this.eventMap[name] = [];
    }
    this.eventMap[name].push(eventFn);
  }

  emit(name, ...args) {
    const eventFns = this.eventMap[name];
    if (eventFns) {
      eventFns.forEach((fn) => fn.apply(this, args));
    }
  }

  off(name, eventFn) {
    const eventFns = this.eventMap[name];
    if (eventFns) {
      this.eventMap[name] = eventFns.filter((f) => f !== eventFn);
    }
  }

  once(name, eventFn) {
    // 定义包装函数
    const wrapper = (...args) => {
      eventFn.apply(this, args);
      // 执行之后,移除包装函数
      this.off(name, wrapper);
    };
    // 注册包装函数
    this.on(name, wrapper);
  }
}

// 创建事件总线实例
const eventBus = new EventBus();

// 定义事件监听器
const onFoo = (arg1, arg2) => {
  console.log(`foo event received with args: ${arg1}, ${arg2}`);
};

const onBar = (arg) => {
  console.log(`bar event received with arg: ${arg}`);
};

// 注册事件监听器
eventBus.on("foo", onFoo);
eventBus.on("bar", onBar);

// 触发事件
eventBus.emit("foo", "Hello", "World"); // 输出: foo event received with args: Hello, World
eventBus.emit("bar", 66); // 输出: bar event received with arg: 66

// 移除事件监听器
eventBus.off("foo", onFoo);

// 触发事件
eventBus.emit("foo", "Hello", "World"); // 无输出
eventBus.emit("bar", 99); // 输出: bar event received with arg: 99

// 注册一次性事件监听器
eventBus.once("baz", (arg) => {
  console.log(`baz event received with arg: ${arg}`);
});

// 触发一次性事件
eventBus.emit("baz", 100); // 输出: baz event received with arg: 100
eventBus.emit("baz", 200); // 无输出