[深刻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

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息