[深入09] 深浅拷贝

2021-08-02  本文已影响0人  woow_wu7

导航

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数

[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染

前置知识

堆栈

image image

数据类型

数据类型的案例

引用类型和原始类型的案例

var a = 1 // 基本类型的数据
var b = {name: 'woow_wu7'} // 引用类型的数据
var aa = a // a 和 aa 是不同的数据
var bb = b // b 和 bb 指向堆中的同一份数据,修改堆中数据,b和bb的指向没变,则引用的值也会跟着改变
a = 2
b.name = 'wang'

console.log(a, aa, 'a和aa是不同的数据') // 改变后不等
console.log(b.name, bb.name, 'b和bb两个变量中的指针 => 都同时指向了同一个堆内存中的数据') // 改变后相等
console.log(b === bb) // true,说明两个变量指向了同一个堆内存

Map数据结构


const mapArrKey = [1,2];
const mapKeyAddress = ['chongqign']

const mapInstance = new Map([
  ['name', 'woow_wu'],
  [[1,2], 20],
  [mapArrKey, 20],
  [{age: 20}, {age: 20}],
])
console.log(mapInstance, 'mapInstance')
console.log(mapInstance.size, 'size') // 4
console.log(mapInstance.get(mapArrKey), 'Map.prototype.get(key) => key是一个数组')
console.log(mapInstance.get([1,2]), 'Map.prototype.get(key)') // undefined 必须是同一个数组

mapInstance.set(mapKeyAddress, '地址')
console.log(mapInstance.get(mapKeyAddress))
console.log(mapInstance.has(mapKeyAddress), 'Map.prototype.has(key) => key是否存在,布尔值') // true
console.log(mapInstance.delete(mapKeyAddress), 'Map.prototype.delete(key) => 删除键,返回布尔值,表示是否删除成功') // true
console.log(mapInstance.get(mapKeyAddress)) // undefined
console.log(mapInstance.clear(), 'Map.prototype.clear() => 删除所有键,没有返回值')
console.log(mapInstance)

Reflect

运算符的结合性

三元运算符右结合

let name = null

1 === true ? name = 'wang' : 1 < 0 ? name = 'zhang' : name = 'woow_wu7';
//相当于:1 === true ? name = 'wang' : (1 < 0 ? name = 'zhang' : name = 'woow_wu7')

console.log(name, 'name')
// 'woow_wu7'
// 1 === true // false,类型不一样都是false

typeof返回值

typeof
- 返回值有(7种):number,string,boolean,undefined,symbol,function,object
- 基础数据类型(6种):number,string,boolean,undefined,symbol,null

typeof NaN ---------------------- 'number'
typeof Symbol() ----------------- 'symbol'
typeof function(){} ------------- 'function'
typeof []  ---------------------- 'object'
typeof {}  ---------------------- 'object'
typeof null --------------------- 'object'

浅拷贝和深拷贝的区别

浅拷贝和赋值的区别

之所以不易区分浅拷贝和赋值,是因为拷贝后一般都伴随者赋值

赋值和浅拷贝的区别实例

赋值和浅拷贝的区别

var a = {
  name: 'woow_wu',
  score: {
    ch: 90,
    en: 80
  }
};
var b = a
var c = {...a}
console.log(a===b, '赋值 => 不会开创新的堆空间,修改相互影响') // true,说明是同一份堆数据
console.log(a===c, '浅拷贝 => 会开创新的堆空间,修改原始值属性互不干扰,修改引用值属性,相互影响') // false,不同堆数据
a.name = 'wang'
console.log(b, 'b') // 相互影响
console.log(c, 'c => 浅拷贝,修改属性值为基本类型 => 互不干扰') // 互不干扰
a.score.en = 100
console.log(c, 'c => 浅拷贝,修改属性值为引用类型 => 相互影响') // 相互影响

浅拷贝

对象的浅拷贝

数组浅拷贝

const arr = [1, 2, 3]
---

1. slice 
- slice(start, end)
  - 截取目标数组的一部分,----------------------------------- ( 返回一个新数组,不改变原数组 )
  - start起始位置,从0开始,可以取到
  - end终止位置,注意取不到end // arr.slice(0, 2) // [1, 2]
- 下面三种写法等价
  - arr.slice() 
  - arr.slice(0)  
  - arr.slice(0, 3) 三者的结果一样
  

2. concat
- concat
  - 用于多个数组的合并,它将新数组的成员,添加在原属数组的尾部,---- ( 返回一个新数组,不改变原数组 )
  - 参数
    - concat的参数除了是 ( 数组 ) 还可以是 ( 其他任意类型的值 )

  
3. 数组的浅拷贝
- arr.concat() // 不加参数
- arr.slice() // 不加参数
- [...arr]

深拷贝

方法一 JSON.parse(JSON.stringify())

const objComplex = {
  name: 'woow_wu7',
  address: {
    city: 'chongqing',
    district: 'yubei',
    town: 'jiazhou',
    detail: ['chongqing', 'yubei', 'jiazhou']
  },
  arr: [1,2, {l:20, r: 30}],
  fn: function(){}, // Function
  date: new Date(), // Date
  err: new Error(), // Error
  reg: new RegExp(), // RegExp
  number: 1,
  string: '',
  boolean: true,
  null: null,
  undefined: undefined,
  symbol: Symbol('symbol'), // Symbol
}
const copy = JSON.parse(JSON.stringify(objComplex))
console.log(objComplex, 'objComplex')
console.log(copy, 'copy')

如下图:
JSON.parse(JSON.stringify()) 不能拷贝function,Date,Error,RegExp,等对象
image

方法二

基础版 - for...in循环递归(1)

const objComplex = {
  name: 'woow_wu7',
  address: {
    city: 'chongqing',
    district: 'yubei',
    town: 'jiazhou',
    detail: ['chongqing', 'yubei', 'jiazhou']
  },
  score: [100, 200]
}

function deepClone(parameter) {
  const parameterType = Object.prototype.toString.call(parameter).slice(8, -1)
  // 获取类型字符串
  // Array.prototype.slice(8, -1) 从下标为8的字符开始截取,直到倒数第2个值
  // 因为第一个参数位置可以取到,第二个参数位置取不到
  const cloneObj = null
  // 参数是数组,赋值[]
  // 参数是对象,赋值{}
  // 其他类型:直接返回
  if (parameterType === 'Array') {
    cloneObj = []
  }
  else if (parameterType === 'Object') {
    cloneObj = {}
  }
  else {
    return parameter
  }

  for(let key in parameter) {
    // for...in 
    // 1. 循环用来遍历对象 ( 所有可遍历的属性 ),会 ( 跳过不可遍历的属性 )
    // 2. 不仅可以遍历 ( 自身属性 ),还可以遍历 ( 继承的属性 )
    // 3. 所以一般情况下,( for...in都要结合hasOwnProperty来遍历自身的属性 )
    
    if (parameter.hasOwnProperty(key)) {
      // 是否是自身属性
      if (typeof parameter[key] === 'object') {
        // 对象或数组,继续判断
        // 这里使用typeof没有去区是分数组或对象,因为会在deepClone中去做判断
        cloneObj[key] = deepClone(parameter[key])
      } else {
        // typeof不是objet
        // 则有可能是:number string boolean undefined symbol function
        // 这里没有考虑 function 的拷贝
        cloneObj[key] = parameter[key]
      }
    }
  }

  return cloneObj
}
const res = deepClone(objComplex)
console.log(res, 'res')
----------
更精简的写法

const obj = {
  name: 'woow_wu7',
  address: {
    city: 'chongqing',
    districe: 'yubei',
    town: 'jiazhou',
    detail: ['chongqign', 'yubei', 'jiazhou']
  },
  arr: [1,2]
}

function deepClone(parameter) {
  if (typeof parameter === 'object') {
    // 这里只考虑 对象和数组
    // typeof返回值是字符串,有7种
    // number string boolean undefined symbol function object
    const objClone = Array.isArray(parameter) ? [] : {}
    for (let key in parameter) {
      if (parameter.hasOwnProperty(key)) {
        objClone[key] = deepClone(parameter[key])
        // 不管是对象类型还是基本数据类型,都去调用deepClone(parameter[key])
        // 在deepClone()函数中会去判断对象类型是数组还是对象,基本数据类型直接返回并赋值给objClone[key]
      }
    }
    return objClone
  }
  else {
    // 不是数组和对象直接返回形参
    // 注意形参是新声明的变量
    return parameter
  }
}

const res = deepClone(obj)
console.log(obj)
console.log(res)

Map 解决循环引用 - for...in循环递归(2)

(1) 什么是循环引用?

const obj = {name: 'wang'}
obj.circle = obj
// obj新增circle属性,值是obj对象本身
// 这样的情况,像上面的代码例子中,for..in循环中deepClone(parameter[key])会不断重复执行
// 最终造成内存溢出


----------
(2) 如何解决循环引用?
1. 检查map实例中是否有克隆过的对象
2. 如果存在,直接返回
3. 如果不存在,就赋值键值对,key是传入的对象,value是克隆的对象

var objComplex = {
  address: {
    city: 'chongqing',
    town: 'jiazhou',
  },
  score: [100, 200],
}
objComplex.circular = objComplex

function deepClone(objComplex, mapx = new Map()) { // 默认值,是一个空的map实例

  if (typeof objComplex !== 'object') {
    // 不是对象和数组直接返回
    return objComplex
  }
  const objClone = Array.isArray(objComplex) ? [] : {}
  
  if (mapx.get(objComplex)) {
    // 存在被克隆的对象,直接返回
    return mapx.get(objComplex)
  }
  // 不存在,就添加键值对,将被克隆对象作为key,克隆的对象作为value
  mapx.set(objComplex, objClone)
 
  for(let key in objComplex) {
   objClone[key] = deepClone(objComplex[key], mapx)
   // 注意:mapx要传入做判断
   // 不管objComplex[key]是什么类型,都调用deepClone(),因为在deepClone()中会判断
  }
  return objClone
}
const res = deepClone(objComplex) // 这样就不会内存溢出了
console.log(res, 'res')

Reflect 解决Symbol数据类型复制 - Reflect.ownKeys()循环递归(3)


用 Reflect 解决 Symbol类型数据的复制

var objComplex = {
  address: {
    city: 'chongqing',
    town: 'jiazhou',
  },
  score: [100, 200],
}
objComplex.circular = objComplex
objComplex[Symbol()] = 'symbol'

function deepClone(objComplex, mapx = new Map()) {

  if (typeof objComplex !== 'object') {
    return objComplex
  }

  const objClone = Array.isArray(objComplex) ? [] : {}
  if (mapx.get(objComplex)) {
    return mapx.get(objComplex)
  }
  mapx.set(objComplex, objClone)
  // for(let key in objComplex) {
  //   objClone[key] = deepClone(objComplex[key], mapx)
  // }
  Reflect.ownKeys(Array.isArray(objComplex) ? [...objComplex] : { ...objComplex }).forEach(key => {
    // Reflect.ownKeys(obj)返回对象参数的所有属性,包括symbol类型的属性
    objClone[key] = deepClone(objComplex[key], mapx)
  })
  return objClone
}
const res = deepClone(objComplex)
console.log(res, 'res')
<script>
      // deep clone
      const obj = {
        name: "woow_wu7",
        address: {
          city: "chongqing",
          districe: "yubei",
          town: "jiazhou",
          detail: ["chongqign", "yubei", "jiazhou"],
        },
        arr: [1, 2],
        [Symbol("unique")]: "unique",
      };
      obj.circle = obj;

      const deepClone = (param, map = new Map()) => {
        let typeParams = Object.prototype.toString.call(param);

        if (typeof param !== "object" && typeof param !== "function") {
          return param;
        }

        if (map.get(param)) {
          return map.get(param);
        }

        let resObj = Array.isArray(param) ? [] : {};

        map.set(param, resObj);

        // for (let key in param) {
        //   resObj[key] = deepClone(param[key], map);
        // }
        Reflect.ownKeys(param).forEach((key) => { // ownKeys参数中,不用做判断是不是数组了
          resObj[key] = deepClone(param[key], map);
        });

        return resObj;
      };

      const newObj = deepClone(obj);
      newObj.name = "222";
      console.log(`newObj`, newObj);
      console.log(`obj`, obj);
    </script>

结构化克隆算法解决其他对象的拷贝 (4)

function Message() {
  this.sex = 'man'
}
Message.prototype.age = 1000

var objComplex = {
  address: {
    city: 'chongqing',
    town: 'jiazhou',
  },
  score: [100, 200],
  [Symbol()]: 'symbol',
  date: new Date(),
  reg: new RegExp(),
  fn: function () { },
  err: new Error(),
  message: new Message()
}
objComplex.circle = objComplex

function deepClone(objComplex, mapx = new Map()) {

  if (typeof objComplex !== 'object') {
    return objComplex
  }

  let objClone = Array.isArray(objComplex) ? [] : {}
  if (mapx.get(objComplex)) {
    return mapx.get(objComplex)
  }
  mapx.set(objComplex, objClone)
  
  // for(let key in objComplex) {
  //   objClone[key] = deepClone(objComplex[key], mapx)
  // }
  
  // Reflect.ownKeys(Array.isArray(parameter) ? [...parameter] : { ...parameter }).forEach(key => {
  //   objClone[key] = deepClone(parameter[key], mapx)
  // })
  
  switch (objComplex.constructor) { // 传入的参数对象的构造函数
    case Date:
    case RegExp:
    case Message: // 自定义的构造函数
      objClone = new objComplex.constructor(objComplex)
      break
      // 如果是Date,RegExp,Message构造函数的请求
      // 就分别用这些构造函数生成实例,然后再赋值给拷贝对象
    default:
      Reflect.ownKeys(Array.isArray(objComplex) ? [...objComplex] : {...objComplex}).forEach(key => {
        objClone[key] = deepClone(objComplex[key], mapx)
      })
  }
  return objClone
}
const res = deepClone(objComplex)
console.log(res, 'res')
console.log(res.message.age, '克隆的对象')
console.log(objComplex.message.age, '原对象')

2021/07/17复习

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      // deepClone
      // 1. Map 解决循环引用 => 缓存
      // 2. Reflect 解决Symbol()数据类型 => Reflect.ownKeys()
      // 3. 结构化克隆解决特殊对象的拷贝 => Date RegExp
      //  - 虽然结构化克隆解决了 ( Date ) ( RegExp ) 对象的拷贝问题
      //  - 但是结构化克隆仍然没有解决 ( Error ) ( function ) ( DOM节点 ) 的拷贝问题

      const obj = {
        num: 1,
        str: "str",
        boo: true,
        und: undefined,
        [Symbol()]: Symbol(),
        arr: [1, 2, 3],
        obj: { name: "woow_wu7" },
        date: new Date(),
        regexp: new RegExp(),
      };
      obj.circle = obj;

      const deepClone = (params, map = new Map()) => {
        if (typeof params !== "object") {
          return params;
        }
        let resObj = Array.isArray(params) ? [] : {};

        if (map.get(params)) {
          return map.get(params);
        }

        map.set(params, resObj);

        // for (let key in params) {
        //   if (params.hasOwnProperty(key)) {
        //     resObj[key] = deepClone(params[key], map);
        //   }
        // }

        // Reflect.ownKeys(params).forEach(key => {
        //   resObj[key] = deepClone(params[key], map)
        // })

        switch (params.constructor) {
          case Date:
          case RegExp:
            resObj = new params.constructor(params);
            break;
          default:
            Reflect.ownKeys(params).forEach((key) => {
              resObj[key] = deepClone(params[key], map);
            });
            break;
        }

        return resObj;
      };

      const res = deepClone(obj);
      console.log(`res`, res);
      console.log(`obj`, obj);
    </script>
  </body>
</html>

基本:https://juejin.im/post/6844904197595332622
深入:https://juejin.im/post/6844903929705136141
知乎(lodash deepClone源码分析):https://zhuanlan.zhihu.com/p/41699218
stack,heap:https://segmentfault.com/a/1190000002789651
https://juejin.im/post/6844903885472022535#heading-0
我的简书 https://www.jianshu.com/p/a2306eba08bf

上一篇下一篇

猜你喜欢

热点阅读