1025

68 阅读6分钟

1、什么是深拷贝和浅拷贝?

在面试时经常会碰到面试官问:什么是深拷贝和浅拷贝,请举例说明?如何区分深拷贝与浅拷贝,简单来

说,假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果B没

变,那就是深拷贝;我们先看两个简单的案例:

//案例1

var a1 = 1, a2= a1;

console.log(a1) //1

console.log(a2) //1

a2 = 2; //修改 a2

console.log(a1) //1

console.log(a2) //2

//案例2

var o1 = {x: 1, y: 2}, o2 = o1;

console.log(o1) //{x: 1, y: 2}

console.log(o2) //{x: 1, y: 2}

o2.x = 2; //修改o2.x

console.log(o1) //{x: 2, y: 2}

console.log(o2) //{x: 2, y: 2}

按照常规思维,o1应该和a1一样,不会因为另外一个值的改变而改变,而这里的o1 却随着o2的改变而

改变了。同样是变量,为什么表现不一样呢?为了更好的理解js的深浅拷贝,我们先来理解一些js基本的

概念 —— 目前JavaScript有五种基本数据类型(也就是简单数据类型),它们分别是:Undefined,

Null,Boolean,Number和String。还含有一种复杂的数据类型(也叫引用类型),就是对象;

引用类型常见的有:Object、Array、Function等

2、基本类型和引用类型

ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保

存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值是指那些保存堆内

存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保

存对象。

那什么又是堆?什么又是栈?

3、JS中的堆内存与栈内存

计算器语言有一个处理的过程,写的代码会进行解释或编译执行,这个过程是在内存中,内存的使用和

分配,涉及到堆和栈,任何语言都有堆和栈 ,堆和栈都存放在内存中

栈:javascript的基本类型就5种:Undefined、Null、Boolean、Number和String,它们都是直接按值存

储在栈中,每种类型的数据占用的内存空间的大小是确定的

栈由系统自动分配, 例如,声明在函数中一个局部变量var a; 系统自动在栈中为a开辟空间

只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出

堆:javascript中其他类型的数据被称为引用类型的数据 : 如对象(Object)、数组(Array)、函数(Function)

…,

它们是通过拷贝和new出来的,这样的数据存储于堆中

其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访

问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要

的数据。

image.png 引用关系的拷贝过程

var obj1 = {id:1,name:'obj1'};

obj2 = obj1; //相当于只是复制了一个指针地址,obj1和obj2实际指向的是同一数据对象

所以

obj2.id = 2;

obj1.id 的结果会同步变化

image.png 4、内存分配和垃圾回收

一般来说栈内存线性有序存储,容量小,系统分配效率高。而堆内存首先要在堆内存新分配存储区域,

之后又要把指针存储到栈内存中,效率相对就要低一些了。

垃圾回收方面,栈内存变量基本上用完就回收了,而推内存中的变量因为存在很多不确定的引用,只有

当所有调用的变量全部销毁之后才能回收。

5、引用类型如何实现深拷贝 既然已经知道了深拷贝与浅拷贝的来由,那么该如何实现深拷贝?我们先分别看看Array和Object自有方

法是否支持:

1、Array

对于数组我们可以使用slice() 和 concat() 方法来解决上面的问题

  1. slice

var arr1 = ['a', 'b'], arr2 = arr1.slice();

console.log(arr1); // ["a", "b"]

console.log(arr2); // ["a", "b"]

arr2[0] = 'c'; //修改arr2

console.log(arr1); // ["a", "b"]

console.log(arr2); // ["c", "b"]

此时,arr2的修改并没有影响到arr1

  1. concat

var arr1 = ['a', 'b'], arr2 = arr1. concat ();

console.log(arr1); // ["a", "b"]

console.log(arr2); // ["a", "b"]

arr2[0] = 'c'; //修改arr2

console.log(arr1); // ["a", "b"]

console.log(arr2); // ["c", "b"]

3)多维数组也可以吗?

我们把arr1改成二维数组再来看看:

var arr1 = ['a', 'b', ['c', 'd']], arr2 = arr1.concat();

arr2[2][1] = 100;

console.log(arr1); //['a', 'b', ['c', 100]]

console.log(arr2); //['a', 'b', ['c', 100]]

咦,arr2又改变了arr1,看来slice()/concat()只能实现一维数组的深拷贝

4)借助其它库

除了上面两种方法外,我们还可以借用JQ的extend方法。 $.extend( [deep ], target, object1 [, objectN ] )

deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝

target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。

var a=[0,1,[2,3],4],b=$.extend(true,[],a);

a[0]=1;

a[2][0]=1;  

console.log(a); //[1,1,[1,3],4]

console.log(b); //[0,1,[2,3],4]

不过这种方法需要依赖JQ库。

2、Object

  1. 利用对象的深拷贝实现原理

定义一个新的对象,遍历源对象的属性 并 赋给新对象的属性

var obj = {

  name:'fangfang',

  wx:'18040505058',

  age: 18

}

var obj2 = new Object();

obj2.name = obj.name;

obj2.age = obj.age

obj.name = 'alice';

console.log(obj); //Object {name: "'alice'", age: 18}

console.log(obj2); //Object {name: "'fangfang'", age: 18}

有什么方式可以实现深拷贝呢

1)JSON.parse(JSON.stringify(obj

var obj = {

  name: 'fangfang',

  wx:'18040505058',

  age: 18

}

var obj2 = JSON.parse(JSON.stringify(obj));

obj.name = 'alice';

console.log(obj) // {name: "alice", age: 18}

console.log(obj2) // {name: "fangfang", age: 18}

JSON.parse(JSON.stringify(obj))深拷贝的坑

利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象;

注意以下几点:

1、如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。

2、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。

3、如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。

4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。

5、JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使

用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。

6、如果对象中存在循环引用的情况也无法正确实现深拷贝。

2)封装一个方法 deep来实现对象的深拷贝

`var Animal={

    name: "tom",

    skin: ["red", "green"],

    child: {

        xxx: "xxx"

    },

    say: function(){

        console.log("I am ", this.name, " skin:", this.skin)

    }

}

function deep(dest, obj){

    var o = dest;

    for (var key in obj) {  

        if (typeof obj[key] === 'object'){   //判断是不是对象

            o[key] = (obj[key].constructor===Array)?[]:{};

            deep(o[key], obj[key])

        } else {

            o[key] = obj[key]

        }

    }

    return o;

}`