区别
深拷贝和浅拷贝的主要区别就是在内存中的存储类型不同。
何谓堆?何为栈?
栈(stack)为自动分配的内存空间,它由系统自动释放;而堆(heap)则是动态分配的内存,大小不定也不会自动释放。
那么接下来回顾一下js中的数据类型,主要分为基本数据类型和引用类型:
- 基本数据类型:
undefined, boolean, number, string, null.
- 引用类型:
object.
基本数据类型
-
基本数据类型存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。
-
基本数据类型值不可变,字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值,并不会改变原始值。
js中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值。对数字和布尔值来说显然如此 —— 改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组,我们期望可以通过指定索引来假改字符串中的字符。
var str = "abc";
console.log(str[1]="2"); // 2
console.log(str); // abc
- 基本类型的比较是值的比较,只要它们的值相等那么就认为它们是相等的,比较的时候最好使用严格等,因为
== 是会进行类型转换的。
var a = 1;
var b = 1;
console.log(a === b); //true
var c = 1;
var d = true;
console.log(c == d); //true
引用类型
-
引用类型存放在堆中,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配。
-
引用类型值可变,可以直接改变其值的,如:
var a = [1,2,3];
a[1] = 10;
console.log(a[1]); // 10
- 引用类型的比较是引用的比较,对 js 中的引用类型进行操作的时候,都是操作其对象的引用(保存在栈内存中的指针),所以比较两个引用类型,是看其的引用是否指向同一个对象。如:
var a = [1,2,3];
var b = [1,2,3];
console.log(a === b); // false
传值与传址
在赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。如:
var a = 10;
var b = a;
a ++ ;
console.log(a); // 11
console.log(b); // 10
注:基本类型的赋值的两个变量是两个独立相互不影响的变量。
引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。如:
var a = {}; // a保存了一个空对象的实例
var b = a; // a和b都指向了这个空对象
a.name = 'arissy';
console.log(a.name); // 'arissy'
console.log(b.name); // 'arissy'
b.age = 18;
console.log(b.age);// 18
console.log(a.age);// 18
console.log(a == b);// true

浅拷贝
赋值(=)和浅拷贝是有区别的,千万不要认为赋值就是浅拷贝,看个例子就明白了:
var obj1 = {
'name' : 'zhangsan',
'age' : '18',
'language' : [1,[2,3],[4,5]],
};
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 = "lisi";
obj3.age = "20";
obj2.language[1] = ["二","三"];
obj3.language[2] = ["四","五"];
console.log(obj1);
//obj1 = {
// 'name' : 'lisi',
// 'age' : '18',
// 'language' : [1,["二","三"],["四","五"]],
//};
console.log(obj2);
//obj2 = {
// 'name' : 'lisi',
// 'age' : '18',
// 'language' : [1,["二","三"],["四","五"]],
//};
console.log(obj3);
//obj3 = {
// 'name' : 'zhangsan',
// 'age' : '20',
// 'language' : [1,["二","三"],["四","五"]],
//};
先定义个一个原始的对象 obj1,然后使用赋值得到第二个对象 obj2,然后通过浅拷贝,将 obj1 里面的属性都赋值到 obj3 中。也就是说:
- obj1:原始数据
- obj2:赋值操作得到
- obj3:浅拷贝得到
| 标题 |
和原数据是否指向同一对象 |
第一层数据为基本数据类型 |
原数据中包含子对象 |
| 赋值 |
是 |
改变会使原数据一同改变 |
改变会使原数据一同改变 |
| 浅拷贝 |
否 |
改变不会使原数据一同改变 |
改变会使原数据一同改变 |
| 深拷贝 |
否 |
改变不会使原数据一同改变 |
改变不会使原数据一同改变 |
深拷贝
深拷贝是对对象以及对象的所有子对象进行拷贝,思路就是递归调用刚刚的浅拷贝,把所有属于对象的属性类型都遍历赋给另一个对象即可。
来看一段 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 方法判断的第一个参数传入的是一个布尔值,判断是否进行深拷贝。
附:只有一层的对象可使用简单的方法, 如JSON.parse(JSON.stringify()), Object.assign(), Object.create()
参考文章:https://juejin.im/post/59ac1c4ef265da248e75892b#heading-3
区别
深拷贝和浅拷贝的主要区别就是在内存中的存储类型不同。
何谓堆?何为栈?
那么接下来回顾一下js中的数据类型,主要分为基本数据类型和引用类型:
undefined,boolean,number,string,null.object.基本数据类型
基本数据类型存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。
基本数据类型值不可变,字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值,并不会改变原始值。
==是会进行类型转换的。引用类型
引用类型存放在堆中,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配。
引用类型值可变,可以直接改变其值的,如:
传值与传址
在赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。如:
注:基本类型的赋值的两个变量是两个独立相互不影响的变量。
引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。如:
浅拷贝
赋值(=)和浅拷贝是有区别的,千万不要认为赋值就是浅拷贝,看个例子就明白了:
先定义个一个原始的对象 obj1,然后使用赋值得到第二个对象 obj2,然后通过浅拷贝,将 obj1 里面的属性都赋值到 obj3 中。也就是说:
深拷贝
深拷贝是对对象以及对象的所有子对象进行拷贝,思路就是递归调用刚刚的浅拷贝,把所有属于对象的属性类型都遍历赋给另一个对象即可。
来看一段 Zepto 中深拷贝的代码:
在 Zepto 中的 $.extend 方法判断的第一个参数传入的是一个布尔值,判断是否进行深拷贝。
附:只有一层的对象可使用简单的方法, 如JSON.parse(JSON.stringify()), Object.assign(), Object.create()
参考文章:https://juejin.im/post/59ac1c4ef265da248e75892b#heading-3