递归深拷贝
但是存在一个问题,无法拷贝函数
const obj = {
name: "zs",
age: 29,
friends: {
frident_name: "ls",
friend_age: 23,
friend_fri: {
h_name: "w5",
h_age: 22,
h_f: {}
}
}
};
// 状态转移方程:newObj =(newObj[基本类型key] = oldObj[基本类型key]) + (newObj[引用类型key] = oldObj[引用类型key])
const deepCy = (obj) => {
let o = {};
Object.keys(obj).forEach((item) => {
// 判断 value 是不是引用类型
if (obj[item] instanceof Object) {
o[item] = deepCy(obj[item]); // newObj[引用类型key] = oldObj中引用类型的值
} else {
o[item] = obj[item];
}
});
return o;
};
const hh = deepCy(obj);
console.log(hh);
console.log(hh.friends.friend_fri.h_f === obj.friends.friend_fri.h_f); // true
基本实现
思路:
总体上一个对象属性分为引用类型和基本类型,基本类型可以直接拷贝,把对象引用属性单独拿来出还是一个对象,里面还是由基本属性和引用属性构成。
所以只要一层一层复制基本属性,引用属性就进入到引用属性中复制基本属性,再有引用属性就继续往下挖。这是典型的递归。
状态转移方程:对属性一一处理,当前属性值是对象吗?是,继续递归;不是,将属性值保存到新对象中。
判断值是否为对象的工具函数:
// 工具函数,判断一个值是不是对象
const isObject = (originValue) => {
const typeValue = typeof originValue;
return (typeValue != null) && (typeValue === "object" || typeValue === "function");
}
深拷贝基本实现:
const obj = {
name: "zs",
age: 29,
friends: {
frident_name: "ls",
friend_age: 23,
friend_fri: {
h_name: "w5",
h_age: 22,
h_f: {}
}
}
};
// 工具函数,判断一个值是不是对象
const isObject = (originValue) => {
const typeValue = typeof originValue;
return (typeValue != null) && (typeValue === "object" || typeValue === "function");
}
// 递归深拷贝
const deepCopy = (originValue) => {
// 2.1 判断传入的值是否是个对象
// 如果是对象则继续往下,如果不是说明是个基本类型值,递归到底了,则可直接返回当前属性值给新对象的key
if (!isObject(originValue)) return originValue;
const newObj = {};
// 1. 对传入的值中的基本类型属性进行复制
for (const key in originValue) {
// 1.1 动态添加属性的方式进行复制
// 2. 递归调用 deepCopy,对源对象当前属性 key 的属性值进行递归深挖
newObj[key] = deepCopy(originValue[key]);
}
return newObj;
}
const hh = deepCopy(obj);
console.log(hh);
console.log(hh.friends.friend_fri.h_f === obj.friends.friend_fri.h_f); // true
特殊值处理
数组
如果对象中有个属性值为数组,那么数组将会被复制成对象形式。
比如这样:arr: { '0': 'ls', '1': 'zs' }
因为我们给所有的引用类型的容器都是一个空对象const newObj = {}
,数组是个对象类型,但是容器不是花括号。
所以我们需要在设置空对象的时候要判断一下
const newObj = Array.isArray(originValue) ? [] : {};
函数
对象中存在函数,对于函数有两种处理方式:
- 真真切切地拷贝函数
- 直接使用原函数
拷贝函数算是根正苗红了,实实在在地将函数赋值了一遍,但是其实函数不好复制。因为函数很复杂,有参数,有逻辑,有返回值。new Function() 生成函数对象后,将参数、函数体、返回值通过正则转成字符串复制过去太麻烦了。
函数这个东西,我们知道生来就是来复用逻辑的,它自己在堆内存中都是只有一个函数对象,所以复制函数属实没有必要。而且函数也没有和对象一样的引用问题,原函数改了,对后续复制的函数没有影响。所以我们可以直接使用源函数。
const obj = {
foo() { console.log(123); }
};
// 工具函数,判断一个值是不是对象。可以到处复用
const isObject = (originValue) => {
const typeValue = typeof originValue;
return (typeValue != null) && (typeValue === "object" || typeValue === "function");
}
// 递归深拷贝
const deepCopy = (originValue) => {
// 3. 属性值是函数直接返回。
// 注意:一定要写在判断对象的工具函数前面,因为里面将函数判断为对象了
if(typeof originValue === "function") return originValue;
// 2.1 判断传入的值是否是个对象
// 如果是对象则继续往下,如果不是说明是个基本类型值,递归到底了,则可直接返回当前属性值给新对象的key
if (!isObject(originValue)) return originValue;
...
}
Symbol
Symbol 作为 key
12. 模板字符串、展开语法、Symbol…
Symbol 我们知道是无法迭代的,for in 拿不到。我们得使用Object.getOwnPropertySymbols()
const s1 = Symbol("hh");
const obj = {
[s1]: "aaa"
};
// 递归深拷贝
const deepCopy = (originValue) => {
// 3. 属性值是函数直接返回。
// 注意:一定要写在判断对象的工具函数前面,因为里面将函数判断为对象了
if(typeof originValue === "function") return originValue;
// 2.1 判断传入的值是否是个对象
// 如果是对象则继续往下,如果不是说明是个基本类型值,递归到底了,则可直接返回当前属性值给新对象的key
if (!isObject(originValue)) return originValue;
const newObj = Array.isArray(originValue) ? [] : {};
// 1. 对传入的值中的基本类型属性进行复制
for (const key in originValue) {
// 1.1 动态添加属性的方式进行复制
// 2. 递归调用 deepCopy,对源对象当前属性 key 的属性值进行递归深挖
newObj[key] = deepCopy(originValue[key]);
}
// 4. 处理 Symbol key
const symbolKeys = Object.getOwnPropertySymbols(originValue);
for (const symbolKey of symbolKeys) { // symbolKeys 是数组,所以要用 for of,for in 拿到的都是索引
newObj[symbolKey] = deepCopy(originValue[symbolKey]);
}
return newObj;
}
Symbol 作为 value
set、map
循环引用
obj.info = obj
在参数中用 WeakMap,存储一下 newObj。
function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}
function deepClone(originValue, map = new WeakMap()) {
// 判断是否是一个Set类型
if (originValue instanceof Set) {
return new Set([...originValue])
}
// 判断是否是一个Map类型
if (originValue instanceof Map) {
return new Map([...originValue])
}
// 判断如果是Symbol的value, 那么创建一个新的Symbol
if (typeof originValue === "symbol") {
return Symbol(originValue.description)
}
// 判断如果是函数类型, 那么直接使用同一个函数
if (typeof originValue === "function") {
return originValue
}
// 判断传入的originValue是否是一个对象类型
if (!isObject(originValue)) {
return originValue
}
if (map.has(originValue)) {
return map.get(originValue)
}
// 判断传入的对象是数组, 还是对象
const newObject = Array.isArray(originValue) ? []: {}
map.set(originValue, newObject)
for (const key in originValue) {
newObject[key] = deepClone(originValue[key], map)
}
// 对Symbol的key进行特殊的处理
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const sKey of symbolKeys) {
// const newSKey = Symbol(sKey.description)
newObject[sKey] = deepClone(originValue[sKey], map)
}
return newObject
}
// deepClone({name: "why"})
// 测试代码
let s1 = Symbol("aaa")
let s2 = Symbol("bbb")
const obj = {
name: "why",
age: 18,
friend: {
name: "james",
address: {
city: "广州"
}
},
// 数组类型
hobbies: ["abc", "cba", "nba"],
// 函数类型
foo: function(m, n) {
console.log("foo function")
console.log("100代码逻辑")
return 123
},
// Symbol作为key和value
[s1]: "abc",
s2: s2,
// Set/Map
set: new Set(["aaa", "bbb", "ccc"]),
map: new Map([["aaa", "abc"], ["bbb", "cba"]])
}
obj.info = obj
const newObj = deepClone(obj)
console.log(newObj === obj)
obj.friend.name = "kobe"
obj.friend.address.city = "成都"
console.log(newObj)
console.log(newObj.s2 === obj.s2)
console.log(newObj.info.info.info)
事件总线
可以基于此做一个小的状态管理库。
自定义事件总线其实属于一种发布订阅者模式,其中包括三个角色:
- 发布者(Publisher):发出事件(Event);
- 订阅者(Subscriber):订阅事件(Event),并且会进行响应(Handler);
- 事件总线(EventBus):无论是发布者还是订阅者都是通过事件总线作为中台的;
发布订阅者模式其实是一种特殊的观察者模式,观察者模式没有事件总线这样的中间控制人。
class HYEventBus {
constructor() {
this.eventBus = {}
}
on(eventName, eventCallback, thisArg) {
let handlers = this.eventBus[eventName]
if (!handlers) {
handlers = []
this.eventBus[eventName] = handlers
}
handlers.push({
eventCallback,
thisArg
})
}
off(eventName, eventCallback) {
const handlers = this.eventBus[eventName]
if (!handlers) return
const newHandlers = [...handlers]
for (let i = 0; i < newHandlers.length; i++) {
const handler = newHandlers[i]
if (handler.eventCallback === eventCallback) {
const index = handlers.indexOf(handler)
handlers.splice(index, 1)
}
}
}
emit(eventName, ...payload) {
const handlers = this.eventBus[eventName]
if (!handlers) return
handlers.forEach(handler => {
handler.eventCallback.apply(handler.thisArg, payload)
})
}
}
const eventBus = new HYEventBus()
// main.js
eventBus.on("abc", function() {
console.log("监听abc1", this)
}, {name: "why"})
const handleCallback = function() {
console.log("监听abc2", this)
}
eventBus.on("abc", handleCallback, {name: "why"})
// utils.js
eventBus.emit("abc", 123)
// 移除监听
eventBus.off("abc", handleCallback)
eventBus.emit("abc", 123)