Js的浅拷贝与深拷贝

堆和栈的区别

堆和栈都是内存中划分出来的用于存储的区域。

深拷贝与浅拷贝的区别就是其在内存中存储的类型不同。

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


ECMAScript的数据类型

基本数据类型 (number string boolean undefined null)。

上面5个是ECMAScript中的5种基本数据类型

基本数据类型存放在栈中

存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。

基本数据类型数据值不可改变

javascript中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值。对数字和布尔值来说显然如此 —— 改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组,我们期望可以通过指定索引来假改字符串中的字符。实际上,javascript 是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。

例如:

//  基本类型的值不可改变

var str='html'
str[0]='c'
console.log(str)    //  html

基本类型的比较是值的比较

基本类型的比较是值的比较,只要它们相等就认为它们是相等的。

//  基本类型的比较是值的比较

var a='html'
var b='html'

console.log(a===b)    //  true

但是注意,比较的时候请使用 === 三个等号,表示严格等于。

比如:

//  js会默认做类型转换

var a=1
var b=true

console.log(a==b)    //  true



引用类型

引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。 每个空间大小不一样,要根据情况进行特定的分配,例如。

var person1 = {name:'apple'};
var person2 = {name:'xiaomi'};
var person3 = {name:'huawei'};
object

引用类型值可变

//  引用类型值可变

var arr=[1,2,3,4,5]
arr[0]=10

console.log(arr)    //  [10, 2, 3, 4, 5]
引用类型的比较是引用的比较

所以每次我们对js中的引用类型进行操作的时候,都是操作其对象的引用。(保存在栈内存中的指针),所以比较两个引用类型,是看其引用是否指向同一个对象。 例如:

 var a = [1,2,3];
 var b = [1,2,3];
 console.log(a === b); // false

虽然变量 a 和变量 b 都是表示一个内容为 1,2,3 的数组,但是其在内存中的位置不一样,也就是说变量 a 和变量 b 指向的不是同一个对象,所以他们是不相等的。

它们两个是不相等的



传值与传址

在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。例如:

var a = 10;
var b = a;

a ++ ;
console.log(a); // 11
console.log(b); // 10
基本数据类型的赋值

所以说,基本类型的赋值的两个变量是两个独立相互不影响的变量。

但是引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如:

//  引用类型的赋值

var a={}
var b=a

a.name='jozo'

console.log(a.name)    //  jozo
console.log(b.name)    //  jozo

引用类型的赋值



浅拷贝

上面的=只是引用,不是浅拷贝。

赋值(=)与浅拷贝的区别

下面是一个例子:

//  赋值(=)与浅拷贝的区别

var obj1={
    name: 'xiaomi',
    age: 13,
    language:['English','Chinese','Germany']
}


var obj2=obj1

var obj3=shallowcopy(obj1)

function shallowcopy(src){
    var dst={}
    for(var prop in src){
        if(src.hasOwnProperty(prop)){
            dst[prop]=src[prop]
        }
    }
    return dst
}


obj2.name="huawei"
obj3.age=18

obj2.language='Germany'
obj3.language='Greek'


console.log(obj1)
/*
{
    age: 13
    language: (3) ["Greek", "French", "Germany"]
    name: "huawei"
    __proto__: Object
}
*/


console.log(obj2)
/*
    {
        age: 13
        language: Array(3)
        0: "Greek"
        1: "French"
        2: "Germany"
        length: 3
        __proto__: Array(0)
      name: "huawei"
    }
*/


console.log(obj3)
/* 
    {
        age: 18
        language: Array(3)
        0: "Greek"
        1: "French"
        2: "Germany"
        length: 3
        __proto__: Array(0)
      name: "xiaomi"
    }
*/

先定义个一个原始的对象 obj1,然后使用赋值得到第二个对象 obj2,然后通过浅拷贝,将 obj1 里面的属性都赋值到obj3 中。也就是说:

  • obj1:原始数据
  • obj2:赋值操作得到
  • obj3:浅拷贝得到

然后我们改变 obj2name 属性和 obj3name 属性,可以看到,改变赋值得到的对象 obj2 同时也会改变原始值 obj1,而改变浅拷贝得到的的 obj3 则不会改变原始对象 obj1。这就可以说明赋值得到的对象 obj2 只是将指针改变,其引用的仍然是同一个对象,而浅拷贝得到的的 obj3 则是重新创建了新对象。

然而,我们接下来来看一下改变引用类型会是什么情况呢,我又改变了赋值得到的对象 obj2 和浅拷贝得到的 obj3 中的 language 属性的第二个值和第三个值(language 是一个数组,也就是引用类型)。结果见输出,可以看出来,无论是修改赋值得到的对象 obj2 和浅拷贝得到的 obj3 都会改变原始数据。

这是因为浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。所以就会出现改变浅拷贝得到的 obj3 中的引用类型时,会使原始数据得到改变。

深拷贝:将 B 对象拷贝到 A 对象中,包括 B 里面的子对象,

浅拷贝:将 B 对象拷贝到 A 对象中,但不包括 B 里面的子对象

深拷贝与浅拷贝的区别



深拷贝

深拷贝是对对象以及对象的所有子对象进行拷贝。

怎么进行深拷贝呢?

思路就是递归调用刚刚的浅拷贝,把所有属于对象的属性类型都遍历赋给另一个对象即可。我们直接来看一下 Zepto 中深拷贝的代码:


    // 内部方法:用户合并一个或多个对象到第一个对象
    // 参数:
    // target 目标对象  对象都合并到target里
    // source 合并对象
    // deep 是否执行深度合并
    function extend(target, source, deep) {
        for (key in source)
            if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
                // source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
                if (isPlainObject(source[key]) && !isPlainObject(target[key]))
                    target[key] = {}

                // source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
                if (isArray(source[key]) && !isArray(target[key]))
                    target[key] = []
                // 执行递归
                extend(target[key], source[key], deep)
            }
            // 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
            else if (source[key] !== undefined) target[key] = source[key]
    }

    // Copy all but undefined properties from one or more
    // objects to the `target` object.
    $.extend = function(target){
        var deep, args = slice.call(arguments, 1);

        //第一个参数为boolean值时,表示是否深度合并
        if (typeof target == 'boolean') {
            deep = target;
            //target取第二个参数
            target = args.shift()
        }
        // 遍历后面的参数,都合并到target上
        args.forEach(function(arg){ extend(target, arg, deep) })
        return target
    }

在 Zepto 中的 $.extend 方法判断的第一个参数传入的是一个布尔值,判断是否进行深拷贝。

$.extend 方法内部,只有一个形参 target,这个设计你真的很巧妙。因为形参只有一个,所以 target 就是传入的第一个参数的值,并在函数内部设置一个变量 args 来接收去除第一个参数的其余参数,如果该值是一个布尔类型的值的话,说明要启用深拷贝,就将 deep 设置为 true,并将 target 赋值为 args 的第一个值(也就是真正的 target)。如果该值不是一个布尔类型的话,那么传入的第一个值仍为 target 不需要进行处理,只需要遍历使用 extend 方法就可以。

而在 extend 的内部,是拷贝的过程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,161评论 1 32
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,894评论 2 9
  • 在复杂数据类型(object,Array)中,数据存储在堆内存中,那么对于数据的拷贝就出现了两种情况:拷贝引用和拷...
    楼下的黑猫不太冷阅读 134评论 0 0
  • 每天都是睡会就醒来,醒来之后就没有办法在继续睡,
    Ms_张阅读 230评论 0 0
  • DNS服务器,一个关机重启绝对修不好的网络定位器。 平时大家上网总会遇到各种各样的问题,可能是网线没插好,也可能是...
    HeyBensir阅读 1,010评论 0 4