javascript中的深浅拷贝

理解深浅拷贝,首先要明白两种赋值方式: 传值赋值和引用赋值,而传值赋值和引用赋值的区别就是“值”的类型,传值赋值中的值是基本类型,包括六种:string, number, bool, null, undefined, symbol;引用赋值中的值是对象,好比function, object, array等等,而要理解传递赋值和引用赋值的区别,就不得不说下javascript中的堆(heap)和栈(stack)javascript

什么是堆栈

js中内存主要分为栈内存和堆内存,一般栈内存存放的是对象的地址,而堆内存存放的是对象的具体内容。对于基本类型来讲,其地址和内容都存在栈内存中,而引用类型其地址存在栈内存中,其内容则存在堆内存中。java

栈内存和堆内存区别。栈内存运行效率比较高,空间相对比较小,堆内存则相反。因此将构造简单的基本类型放在栈内存中,而将复杂的引用类型放在堆内存中。数组

盗图一张

变量名和内存地址

咱们知道了不一样的数据类型存储在不一样的内存区域,那么变量名是如何和该内存地址作一一关联的呢?任何高级语言都有编译的过程,而这个编译的过程就会创建变量名和内存地址的一一对应关系,变量名只是方便咱们使用而已,在编译后,计算机实际使用的是内存地址在操做。(大概原理,编译原理也都忘了)函数

传值赋值和引用赋值

结合上面的堆栈举个栗子code

基本类型对象

var a = "a"; //基本类型,为a分配栈内存地址和存储内容
var b = a; //基本类型,为b分配新的栈内存地址和内容
a = "a1"; //a对应内存地址内容修改成'a1',b对应的内存地址内容不变
console.log(a, b); //a1, a

引用类型递归

var a = {name: 'a'}; //引用类型,为a分配栈内存地址,为对象{name: 'a'}分配堆内存空间,并将内存地址做为a地址的内容
var b = a; //引用类型,为b分配栈内存空间,将a中对象引用的地址存到b地址中
a.name = 'a1'; //根据a用存放的地址引用,修改堆内存中对象的属性
console.log(a.name, b.name, a===b); //此时a和b仍旧同时引用同一个对象, ===直接对比的内存地址

深拷贝和浅拷贝

深拷贝和浅拷贝不一样的地方,就是对对象属性操做的不一样。简单来讲,浅拷贝,新生成对象对象属性是源对象的引用,共同指向同一块堆内存;深拷贝,新生成的对象属性是一个新对象,里面属性和源对象中的一致。图片

在操做层面,浅拷贝,只须要拷贝源对象的第一层属性;而深拷贝则不断递归遍历源对象中的对象属性,遇到对象则先新建一个对象,并将源对象中基本类型中赋值给新建对象。ip

举个栗子内存

浅拷贝

var a = {name: 'a', toys: ['dog', 'cat']};
function simpleClone(obj) {
    var _obj = {};
    for(var p in obj) {
        _obj[p] = obj[p]; //只拷贝第一层
    }
    return _obj;
}
var b = simpleClone(a);
a.toys.push('bird');
console.log(a.toys, b.toys, a.toys===b.toys); //["dog", "cat", "bird"] ["dog", "cat", "bird"] true

从上面能够看出修改了a以后,b也随之改动,说明在浅拷贝中,对象属性是在目标和源之间是同一个引用。

深拷贝

var a = {name: 'a', toys: ['dog', 'cat']};
function simpleDeepClone(obj) {
    var _obj = {};
    for (var p in obj) {
        //对象类型,递归处理,其余对象类型暂不处理
        if(obj[p].constructor === object) {
            _obj[p] = simpleDeepClone(obj[p])
        } else {
            //基本类型
            _obj[p] = ojb[p]
        }
    }
    return _obj    
}

从上面能够看出深拷贝中对于对象属性总会有一个new的过程,因此新生成的属性和源属性再也不是同一个引用。

lodash中的深浅拷贝

浅拷贝

function baseClone(value) {
    if (!isObject(value)) { //非object和function直接返回,基本数据类型直接返回
      return value;
    }

      if (isArr) {//数组对象
        result = initCloneArray(value); //调用该数组对象的构造函数,构造出等长的新空数组
        if (!isDeep) {
          return copyArray(value, result);//遍历源数组值,依次赋值给新的数组
        }
      }

    if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
          result = initCloneObject(isFunc ? {} : value); //函数对象都返回空对象
          if (!isDeep) {
            /**baseAssign就是遍历源对象属性依次赋值给上面新建立的空对象,因为symbol类型做为属性的特殊性,它不能被for...in,for...of,Object.keys(),Object.getOwnPropertyNames()返回,只能经过Object.getOwnPropertySymbols这个特殊获取,因此须要再次单独处理一下**/
            return copySymbols(value, baseAssign(result, value)); 
          }
        }
    }
  }

因此lodash浅拷贝的整个流程 1)若是源为基本类型直接赋值(包括symbol),数组对象建立一个内容同样的新数组 2)若是源为对象且原型为object或function,则依次变量其属性,将其赋值给新建立的空对象(这个过程当中基本类型属于直接赋值,对象属性则属于引用赋值)

深拷贝

深拷贝就是遍历执行浅拷贝的流程(浅拷贝遇到对象会生成一个新对象,深拷贝的属性若是是对象,就将该对象做为浅拷贝的源,依次遍历。)

javascript中容易忽略的深浅拷贝

  1. 函数参数都是传值赋值,不管对象与否

  2. 不改变原数组方法

  • slice,map, filter等方法都是返回这个新数组,可是新数组是源数组的浅拷贝(修改对象元素,源会跟着改变)
var a = [{name: 'a'}, 1,2,3]
var b = a.slice(0, 2);
console.log(a,b) //[ { name: 'a' }, 1, 2, 3 ] [ { name: 'a' }, 1 ]
b[0].name = 'b';
console.log(a,b)//[ { name: 'b' }, 1, 2, 3 ] [ { name: 'b' }, 1 ]

var c = a.map(function(key) {
    if(typeof key == 'object') {
        return key.name = 'c'
    }else {
        return key
    }
})
console.log(c, a)//[ { name: 'c' }, 1, 2, 3 ] [ { name: 'c' }, 1, 2, 3 ]
相关文章
相关标签/搜索