js的深拷貝和淺拷貝

南風一濺發表於2018-12-28

前言

上一篇一道面試題引發的js資料型別傳參思考,研究了下js資料在記憶體中的儲存,在最後自然而然的想到了深拷貝和淺拷貝,篇幅所限,就放在這裡了。

何為淺拷貝

沿用上一篇的圖

js的深拷貝和淺拷貝

var obj1 = {x:1, y:2};
var obj2 = obj1;
obj2.x = 3;
console.log(obj1); // {x: 3, y: 2}
複製程式碼

形如上面這個例子,簡單的把物件obj1賦值給obj2,他們在記憶體中都指向相同的堆區,改變任何一個都會影響另一個,這就是淺拷貝:賦值後的資料會相互影響

深拷貝

為了解決淺拷貝的問題,自然引申出了深拷貝:賦值後的資料彼此獨立,不相互影響。即obj2擁有obj1的初始資料,但obj2的資料放在堆區的另一個位置。

深拷貝原理很簡單:遞迴遍歷物件,一一賦值

直接上完整版程式碼:

  function deepClone(from, to) {
    var record = {} // 記錄引用,解決物件迴圈引用的問題
    function innerDP(from, to) {
      for (var i in from) {
        if (!from.hasOwnProperty(i)) {
          // for...in會遍歷到原型鏈上的可列舉屬性,hasOwnProperty只訪問物件例項本身的屬性
          continue
        }
        if (from[i] && typeof from[i] === 'object' && Object.keys(from[i]).length > 0 && !(record[i] && record[i] === from[i])) {
          to[i] = {}
          record[i] = from[i]
          innerDP(from[i], to[i])
        } else {
          to[i] = from[i]
        }
      }
    }

    innerDP(from, to)
  }

  var staff1 = {
    name: '張三',
    company: {
      title: '總監',
      cid: '10006',
      wage: 50000,
      service: [1, 2, 3, {level: 'gold'}],
    },
    age: undefined,
    x: null,
    y: '',
    getName: function () {
      return this.name
    }
  }
  var staff2 = {}
  deepClone(staff1, staff2)
  console.log('staff2拷貝staff1成功:', staff2)
  staff2.x = 3
  staff2.company.title = '程式猿'
  staff2.company.service[3].level = 'silver'
  console.log('staff2改變:', staff2)
  console.log('staff1是否被影響:', staff1)

  var btn1 = $('#btn1') // $('#btn1').context迴圈引用
  var btn2 = {}
  deepClone(btn1, btn2)
  // btn2.length = 2
  console.log(btn2)
  console.log(btn1)
}
複製程式碼

物件迴圈引用的問題

形如這樣的迴圈引用物件:

var a = {};  var b = {a: a};  a.b = b; // a中有b,b中有a,無限巢狀
複製程式碼

深拷貝時會導致堆溢位報錯:Uncaught RangeError: Maximum call stack size exceeded
所以deepClone採用了record記錄:

!(record[i] && record[i] === from[i])
複製程式碼

型別判斷

Object.prototype.toString.call方法

萬物皆物件,Object.prototype.toString.call(value)返回的格式都是"[Object 型別]"

function valueType(value) {
  var str = Object.prototype.toString.call(value)
  str = str.split(' ')[1].replace(']', '')
  
  return str
}
console.log(valueType(1)); // "Number"  
console.log(valueType('1')); // "String"
console.log(valueType(undefined)); // "Undefined"  
console.log(valueType(false)); // "Boolean"  
console.log(valueType(Function)); // "Function"
console.log(valueType(null)); // "Null"
console.log(valueType([])); // "Array"
console.log(valueType([])); // "Object"
console.log(valueType($('#btn1'))); // "Object"
console.log(valueType($('#btn1').context)); // "HTMLDocument"
複製程式碼

typeof判斷

typeof 1; // "number"
typeof '1'; // "string"
typeof undefined; // "undefined"
typeof false; // "boolean"
typeof Function; // "function"
typeof null; // "object"
typeof []; // "object"
typeof {}; // "object"
複製程式碼

相關文章