深拷贝、浅拷贝
深拷贝和浅拷贝的区别
一、了解数据结构
1.基本数据类型(值类型)
- number (数字)
- string (字符串)
- boolean (布尔值)
- null (空)
- undefined(未定义)
- symbol (es6新增表示独一无二的值)
基本数据类型的特点:直接存储在栈(stack)中的数据
2.引用数据类型(对象类型)
- Array (数组)
- Function (函数)
- Object (对象)
- Date (日期)
- Regexp (正则)
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
二、了解堆栈
1.栈内存
- 主要用于存放基本类型和对象变量的指针,算是一种简单的储存; 栈内存自动分配相对固定大小的内存空间,并由系统自动释放
2.堆内存
- 主要用于存放引用类型,存储的对象类型数据对于大小在这方面都是未知的; 堆内存是动态分配内存,内存大小不一,也不会自动释放
三、如何判断数据类型
1.typeof
-
在基本数据类型(null除外)和Function时,返回其对应类型;对于引用数据类型(Function除外)都返回为object;
-
判断基本数据类型(null返回object,其余返回对应数据类型)
const string = '321'; const number = 321; const boolean = true; const enty = null; const unassigned = undefined; const symbol = Symbol(); const bigin = BigInt(1236587489845500); console.log(typeof string) // string console.log(typeof number) // number console.log(typeof boolean) // boolean console.log(typeof enty) // object console.log(typeof unassigned) // undefined console.log(typeof symbol) // symbol console.log(typeof bigin) // bigin -
判断引用数据类型(function 返回 function 其余均返回object;)
const arr = [1,2,3,4]; const obj = {name: '1212'}; const fun = () => {}; const dates = new Date(); const regexp = /[0-9]{1,2}/ console.log(typeof arr) // object console.log(typeof obj ) // object console.log(typeof fun ) // function console.log(typeof dates ) // object console.log(typeof regexp ) // object
-
2.instanceof
-
运算符用于检测构造函数的
prototype属性是否出现在某个实例对象的原型链上; 基本数据类型 不存在prototype因此不能使用instanceof来判断基本数据类型const arr = [1,2,3,4]; const obj = {name: '1212'}; const fun = () => {}; const dates = new Date(); const regexp = /[0-9]{1,2}/ console.log(arr instanceof Array) // true console.log(obj instanceof Object) // true console.log(fun instanceof Function) // true console.log(dates instanceof Date) // true console.log(regexp instanceof RegExp) // true console.log(regexp instanceof Date) // false console.log(regexp instanceof Function) // false
❗❗ 以上 使用instanceof 进行判断引用数据类型,实则是有问题的
instanceof进行判断引用数据类型(存在的问题)
const arr = [1,2,3,4];
const obj = {name: '1212'};
const fun = () => {};
const dates = new Date();
const regexp = /[0-9]{1,2}/;
// 所有引用数据都是 Object 的实例,因此通过 instanceof 操作符检测任何引用数据和Object 构造函数都会返回 true
console.log(arr instanceof Object) // true
console.log(obj instanceof Object) // true
console.log(fun instanceof Object) // true
console.log(dates instanceof Object) // true
console.log(regexp instanceof Object) // true
console.log(regexp instanceof Date) // false
console.log(regexp instanceof Function) // false
-
instanceof的原理:
instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上;// 其中 value 表示 instance 左边的数据, target表示 instance 右边边的数据 function _instance(value, target) { let proto = value.__proto__; // 对象的原型 let prototype = target.prototype; // 构造函数的 prototype 对象 // 判断构造函数的 prototype 对象是否在对象的原型链上 while (true) { if (!proto) return false; //TODO: 重点:当 proto 严格等于 prototype 时,返回 true if (proto === prototype) return true; proto = proto.__proto__; } } // 实验一下 console.log(_instance([1,2,3], Array)); // true console.log(_instance([1,2,3], Function)); // false
3.constructor
-
判断基本数据类型
// constructor 判断基本数据类型 const string = '321'; const number = 321; const boolean = true; const enty = null; const unassigned = undefined; const symbol = Symbol(); const bigin = BigInt(1236587489845500); // null 与 undefined 不存在 原型 console.log(string.constructor === String) // true console.log(string.constructor === String) // true console.log(number.constructor === Number ) // true console.log(boolean.constructor === Boolean ) // true console.log(symbol.constructor === Symbol) // true console.log(bigin.constructor === BigInt) // true -
判断引用数据
// constructor 判断引用数据类型 const arr = [1,2,3,4]; const obj = {name: '1212'}; const fun = () => {}; const dates = new Date(); const regexp = /[0-9]{1,2}/ console.log(arr.constructor === Array) // true console.log(obj.constructor === Object ) // true console.log(fun.constructor === Function ) // true console.log(dates.constructor === Date) // true console.log(regexp.constructor === RegExp) // true -
存在的问题
function Functions() {} // 改变原型 Functions.prototype = new Array(); let tempF = new Functions(); console.log(tempF.constructor === Functions); // false console.log(tempF.constructor === Array); // true
如果对象改变了原型,那么使用constructor不再准确;
4.Object.prototype.toString
-
判断基本数据
// 由于返回格式“[object Xxx]”的字符串,需要对Object.prototype.toString进行处理 // 检测数据类型功能函数 const checkedType = (target) => Object.prototype.toString.call(target).replace(/\[object (\w+)\]/, "$1").toLowerCase(); // 判断基本数据类型 const string = '321'; const number = 321; const boolean = true; const enty = null; const unassigned = undefined; const symbol = Symbol(); const bigin = BigInt(1236587489845500); console.log(checkedType(string)); // string console.log(checkedType(number)); // number console.log(checkedType(boolean)); // boolean console.log(checkedType(enty)); // null console.log(checkedType(unassigned)); // undefined console.log(checkedType(symbol)); // symbol console.log(checkedType(bigin)); // bigin -
判断引用数据
// 由于返回格式“[object Xxx]”的字符串,需要对Object.prototype.toString进行处理 // 检测数据类型功能函数 const checkedType = (target) => Object.prototype.toString.call(target).replace(/\[object (\w+)\]/, "$1").toLowerCase(); // 判断引用数据类型 const arr = [1,2,3,4]; const obj = {name: '1212'}; const fun = () => {}; const dates = new Date(); const regexp = /[0-9]{1,2}/ console.log(checkedType(arr)); // array console.log(checkedType(obj)); // object console.log(checkedType(fun)); // function console.log(checkedType(dates)); // date console.log(checkedType(regexp)); // regexp
四、深拷贝浅拷贝
1.深拷贝
- 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象(新旧对象不共享同一块内存),且修改新对象不会影响原对象(深拷贝采用了在堆内存中申请新的空间来存储数据,这样每个可以避免指针悬挂)
2.浅拷贝
- 如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址(新旧对象共享同一块内存),所以如果其中一个对象改变了这个地址,就会影响到另一个对象(只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构的时,会造成同一份资源析构2次,即delete同一块内存2次,造成程序崩溃)
3.浅拷贝与赋值的区别
-
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的
-
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源
// 对象赋值 let obj1 = { name: 'Chen', age: 18, hobby: ['see a film', 'write the code', 'play basketball', 'tourism'] } let obj2 = obj1; obj2.name = 'Forever'; obj2.hobby[1] = 'swim'; obj2.hobby[2] = 'alpinism'; console.log('obj1===>', obj1); console.log('obj2===>', obj2); // 浅拷贝 let obj1 = { name: 'Chen', age: 18, hobby: ['see a film', 'write the code', 'play basketball', 'tourism'] } let obj3 = {...obj1}; obj3.name = 'Forever'; obj3.hobby[1] = 'swim'; obj3.hobby[2] = 'alpinism'; console.log('obj1===>', obj1); console.log('obj3===>', obj3);
4.浅拷贝的实现
-
展开运算符
// 展开运算符... 实现浅拷贝 let obj1 = { name: 'Chen', hobby: ['see a film', 'write the code', 'play basketball', 'tourism'] } let obj2 = {...obj1}; obj2.hobby[1] = 'swim'; obj2.hobby[2] = 'alpinism'; obj2.name = 'Forever'; console.log('obj1===>', obj1); // obj1===> { name: 'Chen',hobby: [ 'see a film', 'swim','alpinism', 'tourism']} console.log('obj2===>', obj2); // obj2===> { name: 'Forever',hobby: [ 'see a film', 'swim','alpinism', 'tourism']} -
object.assign
// Object.assign() 实现浅拷贝 let obj1 = { name: "Chen", hobby: ["see a film", "write the code", "play basketball", "tourism"], }; let obj2 = Object.assign({}, obj1); obj2.hobby[1] = "swim"; obj2.hobby[2] = "alpinism"; obj2.name = "Forever"; console.log("obj1===>", obj1); // obj1===> {name: 'Chen',hobby: [ 'see a film', 'swim', 'alpinism', 'tourism' ]} console.log("obj2===>", obj2); // obj2===> {name: 'Forever',hobby: [ 'see a film', 'swim', 'alpinism', 'tourism' ]}
当object只有一层的时候,是深拷贝;所以当原数据进行浅拷贝,改变obj2的name 原数据obj1中的name不会改变;
-
Array.prototype.concat
// Array.prototype.concat() 实现浅拷贝 let arr1 = [ { name: 'Chen' }, 'see a film', 'write the code', 'play basketball', 'tourism' ]; let arr2 = arr1.concat([]); arr2[0].name = 'Forever'; arr2[1] = 'play games'; console.log('arr1===>', arr1); // arr1===> [{ name: 'Forever' },'see a film','write the code','play basketball', 'tourism'] console.log('arr2===>', arr2); // arr2===> [{ name: 'Forever' },'play games','write the code', 'play basketball', 'tourism'] -
Array.prototype.slice
// Array.prototype.concat() 实现浅拷贝 let arr1 = [ { name: 'Chen' }, 'see a film', 'write the code', 'play basketball', 'tourism' ]; let arr2 = arr1.slice(); arr2[0].name = 'Forever'; arr2[1] = 'play games'; console.log('arr1===>', arr1); // arr1===> [{ name: 'Forever' },'see a film','write the code','play basketball', 'tourism'] console.log('arr2===>', arr2); // arr2===> [{ name: 'Forever' },'play games','write the code', 'play basketball', 'tourism']
5.深拷贝的实现
-
JSON.parse(JSON.stringify())
// JSON.parse(JSON.stringify())实现深拷贝Object let obj1 = { name: 'Chen', hobby: ['see a film', 'write the code', 'play basketball', 'tourism'] } let obj2 = JSON.parse(JSON.stringify(obj1)); console.log(obj1 === obj2); // false obj2.name = 'Forever'; obj2.hobby[1] = 'swim'; obj2.hobby[2] = 'alpinism'; console.log('obj1===>', obj1); // obj1===> { name: 'Chen',hobby: ['see a film', 'write the code', 'play basketball', 'tourism']} console.log('obj2===>', obj2); // obj2===> { name: 'Forever',hobby: ['see a film', 'swim', 'alpinism', 'tourism']} // JSON.parse(JSON.stringify())实现深拷贝Array let arr1 = [ { name: 'Chen' }, 'see a film', 'write the code', 'play basketball', 'tourism' ]; let arr2 = JSON.parse(JSON.stringify(arr1)); console.log(arr1 === arr2); // false arr2[0].name = 'Forever'; arr2[1] = 'play games'; console.log('arr1===>', arr1); // arr1===> [{ name: 'Chen' },'see a film','write the code','play basketball', 'tourism'] console.log('arr2===>', arr2); // arr2===> [{ name: 'Forever' },'play games','write the code', 'play basketball', 'tourism']
-
递归实现
function cloneDeepDi(obj){ const newObj = {}; let keys = Object.keys(obj); let key = null; let data = null; for(let i = 0; i<keys.length;i++){ key = keys[i]; data = obj[key]; if(data && typeof data === 'object'){ newObj[key] = cloneDeepDi(data) }else{ newObj[key] = data; } } return newObj }-
解决循环引用的递归问题
function deepCopy(obj, parent = null) { // 创建一个新对象 let result = {}; let keys = Object.keys(obj), key = null, temp = null, _parent = parent; // 该字段有父级则需要追溯该字段的父级 while (_parent) { // 如果该字段引用了它的父级则为循环引用 if (_parent.originalParent === obj) { // 循环引用直接返回同级的新对象 return _parent.currentParent; } _parent = _parent.parent; } for (let i = 0; i < keys.length; i++) { key = keys[i]; temp = obj[key]; // 如果字段的值也是一个对象 if (temp && typeof temp === 'object') { // 递归执行深拷贝 将同级的待拷贝对象与新对象传递给 parent 方便追溯循环引用 result[key] = DeepCopy(temp, { originalParent: obj, currentParent: result, parent: parent }); } <span class="hljs-keyword">else</span> { result[key] = temp; } } <span class="hljs-keyword">return</span> result; }
-
-
lodash的_.clonedeep
var _ = require('lodash'); var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; var obj2 = _.cloneDeep(obj1); console.log(obj1.b.f === obj2.b.f);// false