不要再问我JS Clone的问题了

开篇

亦舒的海角json

又回到这个老生常谈,新生绝望的问题,一般遇到这种你们都比较熟悉的问题,反而不知道怎么列大纲,怕不够深刻也怕脱离主题~segmentfault

emm..数组

此文系 不要再问我XX系列之 不要再问我JS Clone的问题了bash

为何会存在这三种状况?三者有何差别

clone原本很简单,只是由于JS中不一样的数据类型存储方式(堆和栈)的差别,咱们才会以为它貌似有点‘复杂’数据结构

基本类型和引用类型的差别如上图所示了 它们共同的目标就是以一个对象为原型clone出另一个新对象,由于自身的问题产生一些反作用,三者的差别其实就体如今反作用的差别上post

差别(堆和栈)

  • 栈(stack)为自动分配的内存空间,它由系统自动释放
  • 而堆(heap)则是动态分配的内存,大小不定也不会自动释放

基础类型: 值存放在栈中,比较是值的比较 引用类型: 值存放在堆中,变量其实是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每一个空间大小不同,要根据状况开进行特定的分配,引用类型的比较是引用的比较ui

var person1 = [1,2,3];
var person2 = [1,2,3];
console.log(a === b); // false
复制代码

赋值

赋值的概念 即便刚入行也不陌生,天天都在用的'='spa

原理

  • 基本类型:在内存中新开辟一段栈内存,而后再把再将值赋值到新的栈中,是两个独立相互不影响的变量
  • 引用类型:赋值是传址,是对象保存在栈中的地址的赋值,这样的话两个变量就指向堆内存的同一个对象,所以二者之间操做互相有影响

Demo

var obj1 = {
  name:'maying',
  age:22,
  sex:'女',
  language : [1,[2,3],[4,5],[9,0]]
}
var sringD = 'pre';
var obj3 = sringD;
sringD = 'post';

var obj2 = obj1;
obj1.name = 'gaile',
obj1.language[0] = 'jjj'
console.log('obj1',obj1)
       /*
        {
            age: 22
            language: (4) ["jjj", Array(2), Array(2), Array(2)]
            name: "gaile"
            sex: "女"
        }
       */
console.log('obj2',obj2)
        /*
            age: 22
            language: (4) ["jjj", Array(2), Array(2), Array(2)]
            name: "gaile"
            sex: "女"
        */
console.log('sringD',sringD) //post
console.log('obj3',obj3) //pre
复制代码

理解浅拷贝

以前的不少年,我认为赋值差很少等于浅拷贝 写个小demo 发现它们之间的差别指针

var obj2 = obj1;
var obj3 = {...obj1};
obj1.name = 'gaile',
obj1.language[0] = 'jjj'
console.log('obj1',obj1)
console.log('obj2',obj2)
console.log('obj3',obj3)
复制代码

赋值对象,是将对象指针直接赋值给另外一个变量 浅拷贝,是从新建立了新对象,因此你更改 obj1.name的时候不会影响到它,可是改变引用类型时就不能幸免了

所谓的浅拷贝就是:code

  • 当对简单的数据类型进行赋值的时候,其实就是直接在栈中新开辟一个地方专门来存储同样的值
  • 当对引用类型进行浅拷贝,后面的对象和前面的对象在第一层数据结构中指向同一个堆地址,可是若是前面的数据不止有一层(属性值是一个指向对象的引用只拷贝那个引用值),相似
language : [1,[2,3],[4,5],[9,0]]
复制代码

内部的子对象的指针仍是同一个地址

若是要实现一直往下复制 就引出了接下来要说的深拷贝

结论:浅复制要比复制来的深入一点,至少它开辟了一个新对象,一起新的堆内存

目前可行的实现方式

站在巨人的肩膀上,咱们能够轻松实现浅拷贝

  • 数组的浅拷贝
1. b = [...a]
2. b = a.slice(0) / [].slice.call(a,0)
3. b = a.concat() / [].concat.call(a)
复制代码
  • 对象的浅拷贝
1. b = Object.assign({},a)
2. b = {...a}
复制代码

若是要你本身实现呢

原理:遍历对象的每一个属性进行逐个拷贝

function copy(obj) {
  if (!obj || typeof obj !== 'object') {
    return
  }

  var newObj = obj.constructor === Array ? [] : {}
  for (var key in obj) {
       if(obj.hasOwnProperty(key)){
          newObj[key] = obj[key]
        }
  }
  return newObj
}
复制代码

理解深拷贝

深拷贝的意义,就是彻底复制,若是你读了上文,应该就没有什么疑问了

将a对象复制一份给对象b,无论a中的数据结构嵌套有多深,当改变a对象中的任意深度的某个值后,b中的该值不会受任何影响

目前可行的实现方式

  • JSON.stringify()``和JSON.parse()的混合配对使用
var obj4 = JSON.parse(JSON.stringify(obj1)) 

obj1.name='yishu',

obj1.language[1] = ["二","三"];
obj4.language[2] = ["四","五"];


console.log(obj1);   
console.log(obj4); 
复制代码

deepclone

obj1,obj4 是两个独立的对象,更改数据互不影响,达到了咱们要的目的

它粗暴,有用,可是也有缺点

  1. 在JSON.stringify()作序列化时,undefinedfunction以及symbol值,会被忽略

例如

var obj = {
  a: {b: 'old'}, 
  c:undefined, 
  d: function () {},
  e:  Symbol('')
 }
var newObj = JSON.parse(JSON.stringify(obj))
newObj.a.b = 'new'
console.log(obj)
console.log(newObj)
复制代码

结果

若是要你本身实现呢

原理:使用递归,遍历每个对象属性进行拷贝

var obj = {
  a: {b: 'old'}, 
  c:undefined, 
  d: function () {},
  e:  Symbol('')
 }


function copy(obj) {
  if (!obj || typeof obj !== 'object') {
    return
  }
  var newObj = obj.constructor === Array ? [] : {}
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (typeof obj[key] === 'object' && obj[key]) {
        newObj[key] = copy(obj[key])
      } else {
        newObj[key] = obj[key]
      }
    }
  }
  return newObj
}

var newObj = copy(obj)
newObj.a.b = 'new'
console.log(obj)
console.log(newObj)
复制代码

jsonquedian

总结

  • 赋值:引用复制 执向同一个对象
  • 浅拷贝 :生成一个新对象,只能拷贝一层,当属性值是一个指向对象的引用只拷贝那个引用值
  • 深拷贝:彻底拷贝,先后对象没有任何关系

参考连接

相关文章
相关标签/搜索