递归深拷贝

但是存在一个问题,无法拷贝函数

  1. const obj = {
  2. name: "zs",
  3. age: 29,
  4. friends: {
  5. frident_name: "ls",
  6. friend_age: 23,
  7. friend_fri: {
  8. h_name: "w5",
  9. h_age: 22,
  10. h_f: {}
  11. }
  12. }
  13. };
  14. // 状态转移方程:newObj =(newObj[基本类型key] = oldObj[基本类型key]) + (newObj[引用类型key] = oldObj[引用类型key])
  15. const deepCy = (obj) => {
  16. let o = {};
  17. Object.keys(obj).forEach((item) => {
  18. // 判断 value 是不是引用类型
  19. if (obj[item] instanceof Object) {
  20. o[item] = deepCy(obj[item]); // newObj[引用类型key] = oldObj中引用类型的值
  21. } else {
  22. o[item] = obj[item];
  23. }
  24. });
  25. return o;
  26. };
  27. const hh = deepCy(obj);
  28. console.log(hh);
  29. console.log(hh.friends.friend_fri.h_f === obj.friends.friend_fri.h_f); // true

基本实现

思路:
总体上一个对象属性分为引用类型和基本类型,基本类型可以直接拷贝,把对象引用属性单独拿来出还是一个对象,里面还是由基本属性和引用属性构成。
所以只要一层一层复制基本属性,引用属性就进入到引用属性中复制基本属性,再有引用属性就继续往下挖。这是典型的递归。
状态转移方程:对属性一一处理,当前属性值是对象吗?是,继续递归;不是,将属性值保存到新对象中。
判断值是否为对象的工具函数:

  1. // 工具函数,判断一个值是不是对象
  2. const isObject = (originValue) => {
  3. const typeValue = typeof originValue;
  4. return (typeValue != null) && (typeValue === "object" || typeValue === "function");
  5. }

深拷贝基本实现:

  1. const obj = {
  2. name: "zs",
  3. age: 29,
  4. friends: {
  5. frident_name: "ls",
  6. friend_age: 23,
  7. friend_fri: {
  8. h_name: "w5",
  9. h_age: 22,
  10. h_f: {}
  11. }
  12. }
  13. };
  14. // 工具函数,判断一个值是不是对象
  15. const isObject = (originValue) => {
  16. const typeValue = typeof originValue;
  17. return (typeValue != null) && (typeValue === "object" || typeValue === "function");
  18. }
  19. // 递归深拷贝
  20. const deepCopy = (originValue) => {
  21. // 2.1 判断传入的值是否是个对象
  22. // 如果是对象则继续往下,如果不是说明是个基本类型值,递归到底了,则可直接返回当前属性值给新对象的key
  23. if (!isObject(originValue)) return originValue;
  24. const newObj = {};
  25. // 1. 对传入的值中的基本类型属性进行复制
  26. for (const key in originValue) {
  27. // 1.1 动态添加属性的方式进行复制
  28. // 2. 递归调用 deepCopy,对源对象当前属性 key 的属性值进行递归深挖
  29. newObj[key] = deepCopy(originValue[key]);
  30. }
  31. return newObj;
  32. }
  33. const hh = deepCopy(obj);
  34. console.log(hh);
  35. console.log(hh.friends.friend_fri.h_f === obj.friends.friend_fri.h_f); // true

特殊值处理

数组

如果对象中有个属性值为数组,那么数组将会被复制成对象形式。
比如这样:arr: { '0': 'ls', '1': 'zs' }
因为我们给所有的引用类型的容器都是一个空对象const newObj = {},数组是个对象类型,但是容器不是花括号。
所以我们需要在设置空对象的时候要判断一下

  1. const newObj = Array.isArray(originValue) ? [] : {};

函数

对象中存在函数,对于函数有两种处理方式:

  1. 真真切切地拷贝函数
  2. 直接使用原函数

拷贝函数算是根正苗红了,实实在在地将函数赋值了一遍,但是其实函数不好复制。因为函数很复杂,有参数,有逻辑,有返回值。new Function() 生成函数对象后,将参数、函数体、返回值通过正则转成字符串复制过去太麻烦了。

函数这个东西,我们知道生来就是来复用逻辑的,它自己在堆内存中都是只有一个函数对象,所以复制函数属实没有必要。而且函数也没有和对象一样的引用问题,原函数改了,对后续复制的函数没有影响。所以我们可以直接使用源函数。

  1. const obj = {
  2. foo() { console.log(123); }
  3. };
  4. // 工具函数,判断一个值是不是对象。可以到处复用
  5. const isObject = (originValue) => {
  6. const typeValue = typeof originValue;
  7. return (typeValue != null) && (typeValue === "object" || typeValue === "function");
  8. }
  9. // 递归深拷贝
  10. const deepCopy = (originValue) => {
  11. // 3. 属性值是函数直接返回。
  12. // 注意:一定要写在判断对象的工具函数前面,因为里面将函数判断为对象了
  13. if(typeof originValue === "function") return originValue;
  14. // 2.1 判断传入的值是否是个对象
  15. // 如果是对象则继续往下,如果不是说明是个基本类型值,递归到底了,则可直接返回当前属性值给新对象的key
  16. if (!isObject(originValue)) return originValue;
  17. ...
  18. }

Symbol

Symbol 作为 key

12. 模板字符串、展开语法、Symbol…
Symbol 我们知道是无法迭代的,for in 拿不到。我们得使用Object.getOwnPropertySymbols()

  1. const s1 = Symbol("hh");
  2. const obj = {
  3. [s1]: "aaa"
  4. };
  5. // 递归深拷贝
  6. const deepCopy = (originValue) => {
  7. // 3. 属性值是函数直接返回。
  8. // 注意:一定要写在判断对象的工具函数前面,因为里面将函数判断为对象了
  9. if(typeof originValue === "function") return originValue;
  10. // 2.1 判断传入的值是否是个对象
  11. // 如果是对象则继续往下,如果不是说明是个基本类型值,递归到底了,则可直接返回当前属性值给新对象的key
  12. if (!isObject(originValue)) return originValue;
  13. const newObj = Array.isArray(originValue) ? [] : {};
  14. // 1. 对传入的值中的基本类型属性进行复制
  15. for (const key in originValue) {
  16. // 1.1 动态添加属性的方式进行复制
  17. // 2. 递归调用 deepCopy,对源对象当前属性 key 的属性值进行递归深挖
  18. newObj[key] = deepCopy(originValue[key]);
  19. }
  20. // 4. 处理 Symbol key
  21. const symbolKeys = Object.getOwnPropertySymbols(originValue);
  22. for (const symbolKey of symbolKeys) { // symbolKeys 是数组,所以要用 for of,for in 拿到的都是索引
  23. newObj[symbolKey] = deepCopy(originValue[symbolKey]);
  24. }
  25. return newObj;
  26. }

Symbol 作为 value

set、map

循环引用

obj.info = obj

在参数中用 WeakMap,存储一下 newObj。

  1. function isObject(value) {
  2. const valueType = typeof value
  3. return (value !== null) && (valueType === "object" || valueType === "function")
  4. }
  5. function deepClone(originValue, map = new WeakMap()) {
  6. // 判断是否是一个Set类型
  7. if (originValue instanceof Set) {
  8. return new Set([...originValue])
  9. }
  10. // 判断是否是一个Map类型
  11. if (originValue instanceof Map) {
  12. return new Map([...originValue])
  13. }
  14. // 判断如果是Symbol的value, 那么创建一个新的Symbol
  15. if (typeof originValue === "symbol") {
  16. return Symbol(originValue.description)
  17. }
  18. // 判断如果是函数类型, 那么直接使用同一个函数
  19. if (typeof originValue === "function") {
  20. return originValue
  21. }
  22. // 判断传入的originValue是否是一个对象类型
  23. if (!isObject(originValue)) {
  24. return originValue
  25. }
  26. if (map.has(originValue)) {
  27. return map.get(originValue)
  28. }
  29. // 判断传入的对象是数组, 还是对象
  30. const newObject = Array.isArray(originValue) ? []: {}
  31. map.set(originValue, newObject)
  32. for (const key in originValue) {
  33. newObject[key] = deepClone(originValue[key], map)
  34. }
  35. // 对Symbol的key进行特殊的处理
  36. const symbolKeys = Object.getOwnPropertySymbols(originValue)
  37. for (const sKey of symbolKeys) {
  38. // const newSKey = Symbol(sKey.description)
  39. newObject[sKey] = deepClone(originValue[sKey], map)
  40. }
  41. return newObject
  42. }
  43. // deepClone({name: "why"})
  44. // 测试代码
  45. let s1 = Symbol("aaa")
  46. let s2 = Symbol("bbb")
  47. const obj = {
  48. name: "why",
  49. age: 18,
  50. friend: {
  51. name: "james",
  52. address: {
  53. city: "广州"
  54. }
  55. },
  56. // 数组类型
  57. hobbies: ["abc", "cba", "nba"],
  58. // 函数类型
  59. foo: function(m, n) {
  60. console.log("foo function")
  61. console.log("100代码逻辑")
  62. return 123
  63. },
  64. // Symbol作为key和value
  65. [s1]: "abc",
  66. s2: s2,
  67. // Set/Map
  68. set: new Set(["aaa", "bbb", "ccc"]),
  69. map: new Map([["aaa", "abc"], ["bbb", "cba"]])
  70. }
  71. obj.info = obj
  72. const newObj = deepClone(obj)
  73. console.log(newObj === obj)
  74. obj.friend.name = "kobe"
  75. obj.friend.address.city = "成都"
  76. console.log(newObj)
  77. console.log(newObj.s2 === obj.s2)
  78. console.log(newObj.info.info.info)

事件总线

可以基于此做一个小的状态管理库。
自定义事件总线其实属于一种发布订阅者模式,其中包括三个角色:

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

发布订阅者模式其实是一种特殊的观察者模式,观察者模式没有事件总线这样的中间控制人。

  1. class HYEventBus {
  2. constructor() {
  3. this.eventBus = {}
  4. }
  5. on(eventName, eventCallback, thisArg) {
  6. let handlers = this.eventBus[eventName]
  7. if (!handlers) {
  8. handlers = []
  9. this.eventBus[eventName] = handlers
  10. }
  11. handlers.push({
  12. eventCallback,
  13. thisArg
  14. })
  15. }
  16. off(eventName, eventCallback) {
  17. const handlers = this.eventBus[eventName]
  18. if (!handlers) return
  19. const newHandlers = [...handlers]
  20. for (let i = 0; i < newHandlers.length; i++) {
  21. const handler = newHandlers[i]
  22. if (handler.eventCallback === eventCallback) {
  23. const index = handlers.indexOf(handler)
  24. handlers.splice(index, 1)
  25. }
  26. }
  27. }
  28. emit(eventName, ...payload) {
  29. const handlers = this.eventBus[eventName]
  30. if (!handlers) return
  31. handlers.forEach(handler => {
  32. handler.eventCallback.apply(handler.thisArg, payload)
  33. })
  34. }
  35. }
  36. const eventBus = new HYEventBus()
  37. // main.js
  38. eventBus.on("abc", function() {
  39. console.log("监听abc1", this)
  40. }, {name: "why"})
  41. const handleCallback = function() {
  42. console.log("监听abc2", this)
  43. }
  44. eventBus.on("abc", handleCallback, {name: "why"})
  45. // utils.js
  46. eventBus.emit("abc", 123)
  47. // 移除监听
  48. eventBus.off("abc", handleCallback)
  49. eventBus.emit("abc", 123)