一、深拷贝
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); // 无输出