递归深拷贝
但是存在一个问题,无法拷贝函数
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 判断传入的值是否是个对象// 如果是对象则继续往下,如果不是说明是个基本类型值,递归到底了,则可直接返回当前属性值给新对象的keyif (!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 判断传入的值是否是个对象// 如果是对象则继续往下,如果不是说明是个基本类型值,递归到底了,则可直接返回当前属性值给新对象的keyif (!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 判断传入的值是否是个对象// 如果是对象则继续往下,如果不是说明是个基本类型值,递归到底了,则可直接返回当前属性值给新对象的keyif (!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 keyconst 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 valuereturn (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, 那么创建一个新的Symbolif (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/Mapset: new Set(["aaa", "bbb", "ccc"]),map: new Map([["aaa", "abc"], ["bbb", "cba"]])}obj.info = objconst 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) returnconst 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) returnhandlers.forEach(handler => {handler.eventCallback.apply(handler.thisArg, payload)})}}const eventBus = new HYEventBus()// main.jseventBus.on("abc", function() {console.log("监听abc1", this)}, {name: "why"})const handleCallback = function() {console.log("监听abc2", this)}eventBus.on("abc", handleCallback, {name: "why"})// utils.jseventBus.emit("abc", 123)// 移除监听eventBus.off("abc", handleCallback)eventBus.emit("abc", 123)
