[深刻09] 深浅拷贝

导航

[深刻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
[react] Hooks前端

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

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程react

前置知识

堆栈

  • stack栈
    • 栈区 包含了:变量的标识符变量的值
    • 栈区:
      • 指的是内存中的栈内存
      • 基本类型的数据(值类型数据)保存在栈中
    • 比较
      • 基本类型数据的比较是(值)得比较
      • 基本类型的数据(不可变),不能添加属性和方法
  • heap堆
    • 堆区
      • 引用类型的数据保存在栈和堆中
      • 栈区保存:变量标识符 和 指向堆内存中对象的 指针
      • 堆区保存:具体的对象数据
    • 比较
      • 引用类型数据的比较是(引用)的比较
      • 引用类型的数据(可变),能够添加属性和方法

数据类型

  • 基本数据类型(值类型):number,string,boolean,null,undefined,symbol
  • 引用类型的数据:object,array,function等
  • 区别:
    • 基本类型没有属性和方法,大小固定,保存在栈区
    • 引用类型有属性和方法,大小不固定,保存在栈区和堆区,栈中保存指向堆中数据的地址

数据类型的案例

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

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数据结构

  • Object对象的key只能是字符串
    • 字符串-值对应
  • Map数据结构的key能够是任意类型
    • 值-值对应
  • Map相似于对象,也是key,value的键值对的集合
  • Map是一种更完善的hash结构实现,若是你须要键值对的数据结构,Map比Object更合适
  • Map.prototype.set(key, value) // key能够是任意类型
  • Map.prototype.get(key)
  • Map.prototype.has(key) // 返回布尔值
  • Map.prototype.delete(key) // 删除某个键,但返回布尔值,表示是否删除成功
  • Map.prototype.clear() // 清除全部成员,没有返回值
  • Map.prototype.keys() values() entries() forEach()
  • Map构造函数能够接受数组为参数,成员必须是一个个表示键值对的数组
  • Map能保证对象key的惟一性
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

  • 操做对象的api
  • reflect:反映,反射的意思
  • Reflect.get(target, name, receiver)
    • 获取target对象的name属性,若是没有该属性返回undefined
    • 若是name属性部署了getter函数,getter函数中的this指向receiver参数对象
    • 若是target参数不是对象,Reflect.get()会报错
  • Reflect.set(target, name, value, receiver)
    • 设置target对象的name属性为value
    • 若是name属性部署了settter函数,setter函数中的this指向receiver参数对象
  • Reflect.deleteProperty(obj, name)
    • 删除obj的name属性
    • 至关于: delete obj.name
  • Reflect.constructor(target, args)
    • 执行构造函数target,传入target构造函数的参数是args
    • 若是target不是函数,就会报错
  • Reflect.getPrototypeOf(obj)
    • 相等于:Object.getPrototypeOf(obj)
  • Reflect.setProrotypeOf(obj, prototypeObj)
  • Reflect.apply(func, thisArg, args)
    • 等于:Function.prototype.apply.call(func, thisArg, arges)
  • Reflect.ownKeys(target)
    • 返回对象参数的全部属性,注意:包括Symbol类型的属性
    • 等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和
    • 包括symbol类型的属性!!!

运算符的结合性

  • 一元运算符,三元运算符,赋值运算符是右结合,其余的都是左结合
  • 三元运算符是右结合:即从右向左算,先算右边的三元表达式,再算左边的三元表达式
三元运算符右结合

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能够用来判断基本数据类型,返回值是字符串
  • typeof不能区分对象类型的数据:如对象仍是数组
  • typeof 的返回值一共有7种类型
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'
复制代码

浅拷贝和深拷贝的区别

  • 浅拷贝是只进行一层拷贝,深拷贝是拷贝全部层级,直到属性对应的值是原始数据为止
  • 浅拷贝
    • 建立一个新对象(开创一个新的堆空间),该新对象有着原始对象属性值的精确拷贝
    • 属性值是基本类型,拷贝的就是基本类型的值(即拷贝的是值,互不干扰
    • 属性值是引用类型,拷贝的就是指向堆内存的指针(即拷贝的是指针,相互干扰
  • 深拷贝
    • 在堆内存中建立一个新空间,把对象完成的拷贝到新空间中,相互独立,互不干扰

浅拷贝和赋值的区别

之因此不易区分浅拷贝和赋值,是由于拷贝后通常都伴随者赋值webpack

  • 赋值:两个变量对象的指针,指向同一个堆内存中的对象数据,不会开创新的堆空间
  • 浅拷贝:开创一个新的堆内存空间(即建立一个新对象),新对象是对原始对象的一个精确拷贝,属性是基本类型的值拷贝的就是基本类型的值,若是属性是引用类型的值,拷贝的就是堆内存的指针
  • 一句话总结:

    (1)赋值不会开创新的堆内存空间,而浅拷贝会开创新的堆内存空间;
    (2)赋值:改变对象属性相互影响;
    (3)浅拷贝:改变属性值是原始类型时,互不干扰。改变的属性值是引用类型时,相互影响git

赋值和浅拷贝的区别实例

赋值和浅拷贝的区别

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 => 浅拷贝,修改属性值为引用类型 => 相互影响') // 相互影响
复制代码

浅拷贝

对象的浅拷贝

  • Object.assign()
  • {...} 展开运算符

数组浅拷贝

  • Array.prototype.slice() // 不传参
  • Array.prototype.concat()
  • [...] 展开运算符

深拷贝

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

  • 缺点:
  • 只能深拷贝对象和数组,但不能拷贝函数,循环引用,原型链上的属性和方法(Date, RegExp, Error等)
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,等对象
复制代码

方法二

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

  • 要求:能够拷贝对象和数组
  • 未解决:循环引用,其余对象的复制如 Date,Error,Regexp,Symbol数据类型等
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(9, -1)
  // 获取类型字符串
  // Array.prototype.slice(9, -1) 从第9个字符开始截取,直到倒数第2个值
  const cloneObj = null
  // 参数是数组,赋值[]
  // 参数是对象,赋值{}
  // 其余类型:直接返回
  if (parameterType === 'Array') {
    cloneObj = []
  }
  else if (parameterType === 'Object') {
    cloneObj = {}
  }
  else {
    return parameter
  }

  for(let key in parameter) {
    if (parameter.hasOwnProperty(key)) {
      // 是不是自身属性
      if (typeof parameter[key] === 'object') {
        // 对象或数组,继续判断
        cloneObj[key] = deepClone(parameter[key])
      } else {
        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)

  • 要求: 能够拷贝对象和数组,并解决循环引用问题,并解决Symbol数据类型
  • 解决 Symbol 数据类型
  • Symbol不能用 new 去调用,参数能够是数组
  • Reflect.ownKeys(obj)返回参数对象的全部属性,包括symbol数据类型的属性
  • 缺点:Reflect不能取到原型链上的属性和方法
用 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')
复制代码

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

  • 要求: 能够拷贝对象和数据,并解决循环引用问题(Map),并解决Symbol数据类型(Reflect),解决其余对象的拷贝(结构化克隆),构造函数生成实例的原型对象的拷贝
  • 好比:Date,Regexp,构造函数生成的实例的原型对象的属性拷贝
  • 缺点:不能处理Error,不能处理Function,不能处理DOM节点
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, '原对象')
复制代码

基本:juejin.im/post/5b5dcf…
深刻:juejin.im/post/5d6aa4…
知乎(lodash deepClone源码分析):zhuanlan.zhihu.com/p/41699218
stack,heap:segmentfault.com/a/119000000…
juejin.im/post/5d235d…
个人简书 www.jianshu.com/p/a2306eba0…web

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息