堆和栈的区别
其实深拷贝和浅拷贝的主要区别就是其在内存中的存储类型不同。
堆和栈都是内存中划分出来用来存储的区域。
栈(stack)为自动分配的内存空间,它由系统自动释放; 而堆(heap)则是动态分配的内存,大小不定也不会自动释放。
//1 var a = 10; b = a; b += 20; alert(a); //10 //2 var obj = { name : "jack" }; b = obj; b.name = "Lily"; alert( obj.name ); //Lily //3 var arr = [1,2,3]; brr = arr; brr[0] = 10; alert( arr[0] ); //10 //4 var a = 20; function fun(a){ a += 30; } fun( a ); alert( a ); // 20 //5 var arr = [1,2,3]; function fun(brr){ brr[0] = 20; } fun(arr); alert(arr[0]); // 20 function test(person) { person = { name: 'yyy', age: 30 } person.age = 26 return person } const p1 = { name: 'yck', age: 25 } const p2 = test(p1) console.log(p1) // -> ? // { name: 'yck',age: 26} console.log(p2) // -> ? // person = { name: 'yyy',age: 30} 复制代码
总结: JS中的基本类型按值传递,对象类型按共享传递的,共享传递和按引用传递的不同在于:在共享传递中对函数形参的赋值,不会影响实参的值
ECMAScript 的数据类型
重新回顾一下 ECMAScript 中的数据类型,主要分为
(1)基本数据类型
基本数据类型主要是:undefined,boolean,number,string,null,Symbol(es6新增)。
** 基本数据类型存放在栈中** 存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。
基本数据类型值不可变
基本数据类型的值是不可变的,动态修改了基本数据类型的值,它的原始值也是不会改变的,例如:
var str = "abc"; console.log(str[1]="f"); // f console.log(str); // abc 复制代码
在 js 中没有方法是可以改变布尔值和数字的。倒是有很多操作字符串的方法,但是这些方法都是返回一个新的字符串,并没有改变其原有的数据。
所以,记住这一点:基本数据类型值不可变。
基本类型的比较是值的比较
基本类型的比较是值的比较,只要它们的值相等就认为他们是相等的。
(2)引用类型
引用类型存放在堆中
引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配。
引用类型值可变
引用类型是可以直接改变其值的,例如:
var a = [1,2,3]; a[1] = 5; console.log(a[1]); // 5复制代码
引用类型的比较是引用的比较
所以每次我们对 js 中的引用类型进行操作的时候,都是操作其对象的引用(保存在栈内存中的指针),所以比较两个引用类型,是看其的引用是否指向同一个对象。例如:
var a = [1,2,3]; var b = [1,2,3]; console.log(a === b); // false 虽然变量 a 和变量 b 都是表示一个内容为 1,2,3 的数组,但是其在内存中的位置不一样,也就是说变量 a 和变量 b 指向的不是同一个对象,所以他们是不相等的。复制代码
传值与传址
基本数据类型的赋值 是传值,所以基本类型的赋值的两个变量是两个独立相互不影响的变量。
引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。
赋值(=)、浅拷贝和深拷贝的区别
浅拷贝实现代码
(1)遍历复制
function shallowCopy(src) { let dst= Array.isArray ? []: {}; for (var prop in src) { //一般情况下,for in 循环只会遍历我们自定义的属性,原型上默认的属性不会遍历出来,但会将原型中新增的属性和方法遍历出来。 if (src.hasOwnProperty(prop)) { //Object的hasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。 dst[prop] = src[prop]; } } return dst;}var x = { a: 1, b: { f: { g: 1 } }, c: [ 1, 2, 3 ]};var y = shallowCopy(x);console.log(y.b.f === x.b.f); // true复制代码
(2)Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
var x = { a: 1, b: { f: { g: 1 } }, c: [ 1, 2, 3 ]};var y = Object.assign({}, x);console.log(y.b.f === x.b.f); // true复制代码
(3)通过展开运算符 ... 来实现浅拷贝
let a = { age: 1}let b = { ...a }a.age = 2console.log(b.age) // 1复制代码
深拷贝
首先我们尝试使用递归去解决深拷贝:
function deepClone(obj) { let objClone = Array.isArray(obj) ? [] : {}; if(obj && typeof obj === "object") { for(key in obj) { if(obj.hasOwnProperty(key)) { // 判断 obj 子元素是否为对象,如果是,递归复制 if(obj[key] && typeof obj[key] === "object") { objClone[key] = deepClone(obj[key]); } else { // 如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone;}let a = [1, 2, 3, 4];let b = deepClone(a);a[0] = 2;console.log(a, b);// Console// a = [2, 2, 3, 4];// b = [1, 2, 3, 4];复制代码
写法2:
function deepClone(obj) { function isObject(o) { return (typeof o === 'object' || typeof o === 'function') && o !== null } if (!isObject(obj)) { throw new Error('非对象') } let isArray = Array.isArray(obj) let newObj = isArray ? [...obj] : { ...obj } Reflect.ownKeys(newObj).forEach(key => { newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return newObj}let obj = { a: [1, 2, 3], b: { c: 2, d: 3 }}let newObj = deepClone(obj)newObj.b.c = 1console.log(obj.b.c) // 2复制代码
使用 JSON 对象的 parse 和 stringify
注意:采用 JSON 进行的深拷贝,无法拷贝到 undefined、function、symbol 这类数据,它是有小 bug 的深拷贝。
function deepClone(obj) { let _obj = JSON.stringify(obj); let objClone = JSON.parse(_obj); return objClone}let a = [0, 1, [2, 3], 4];let b = deepClone(a);a[0] = 1;a[2][0] = 1;console.log(a, b);// Console// a = [1, 1, [1, 3], 4];// b = [0, 1, [2, 3], 4];复制代码
参考:
https://juejin.im/post/59ac1c4ef265da248e75892b 复制代码
参考: 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。