Front-end-learning-to-organize-notes
Front-end-learning-to-organize-notes copied to clipboard
深拷贝/浅拷贝问题
浅拷贝
Object.assign()
语法:Object.assign(target, ...sources) ES6中拷贝对象的方法,接受的第一个参数是拷贝的目标 target ,剩下的参数是拷贝的源对象 sources(可以是多个)。
详细介绍,可以阅读文档《MDN Object.assign》。
let obj = {
name: 'Chocolate',
score: {
web: 99,
math: 100
}
}
let newObj = Object.assign({},obj);
newObj.score.web = 100;
console.log(obj.name); // 'Chocolate'
console.log(newObj.name); // 'Chocolate'
console.log(obj.score.web); // 100
console.log(newObj.score.web); // 100
浅拷贝只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是对象的话只会拷贝一份相同的内存地址。
Object.assign() 使用注意:
- 只拷贝源对象的自身属性(不拷贝继承属性);
- 不会拷贝对象不可枚举的属性;
- 属性名为
Symbol值的属性,可以被Object.assign拷贝; undefined和null无法转成对象,它们不能作为Object.assign参数,但是可以作为源对象。
Object.assign(undefined); // 报错
Object.assign(null); // 报错
Object.assign({}, undefined); // {}
Object.assign({}, null); // {}
let user = {name: "Chocolate"};
Object.assign(user, undefined) === user; // true
Object.assign(user, null) === user; // true
Array.prototype.slice()
语法:arr.slice([begin[, end]])slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括 end )。原始数组不会被改变。
详细介绍,可以阅读文档《MDN Array slice》。
// 示例 数组深拷贝
let user = ["leo", "pingan", {name: "pingan8787"}];
let leo = Array.prototype.slice.call(user);
leo[0] = "pingan888";
leo[2]["name"] = "pingan999";
console.log(leo[0]); // "pingan888" ⚠️ 差异!
console.log(user[0]); // "leo" ⚠️ 差异!
console.log(leo[2]["name"]); // "pingan999"
console.log(user[2]["name"]); // "pingan999"
Array.prototype.concat()
语法:var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])
concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
详细介绍,可以阅读文档《MDN Array concat》。
let user = [{name: "leo"}, {age: 18}];
let user1 = [{age: 20},{addr: "fujian"}];
let user2 = user.concat(user1);
user1[0]["age"] = 25;
console.log(user); // [{"name":"leo"},{"age":18}]
console.log(user1); // [{"age":25},{"addr":"fujian"}]
console.log(user2); // [{"name":"leo"},{"age":18},{"age":25},{"addr":"fujian"}]
Array.prototype.concat 也是一个浅拷贝,只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是对象的话只会拷贝一份相同的内存地址。
拓展运算符(...)
语法:var cloneObj = { ...obj };
扩展运算符也是浅拷贝,对于值是对象的属性无法完全拷贝成2个不同对象,但是如果属性都是基本类型的值的话,使用扩展运算符也是优势方便的地方。
let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = {...user};
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name); // "leo1" ⚠️ 差异!
console.log(user.name); // "leo" ⚠️ 差异!
console.log(leo.skill.CSS); // 90
console.log(user.skill.CSS);// 90
手写浅拷贝
实现原理:新的对象复制已有对象中非对象属性的值和对象属性的「引用」,也就是说对象属性并不复制到内存。
function cloneShallow(source) {
let target = {};
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
「for in」
for...in语句以任意顺序遍历一个对象自有的、继承的、可枚举的、非Symbol的属性。对于每个不同的属性,语句都会被执行。
「hasOwnProperty」
该函数返回值为布尔值,所有继承了 Object 的对象都会继承到 hasOwnProperty 方法,和 in 运算符不同,该函数会忽略掉那些从原型链上继承到的属性和自身属性。语法:obj.hasOwnProperty(prop)
其中 prop 是要检测的属性「字符串名称」或者Symbol。
本文参考:
深拷贝
复制变量值,对于引用数据,则递归至基本类型后,再复制。深拷贝后的对象「与原来的对象完全隔离」,互不影响,对一个对象的修改并不会影响另一个对象。
JSON.parse(JSON.stringify())
其原理是把一个对象序列化成为一个 JSON 字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用 JSON.parse() 反序列化将 JSON 字符串变成一个新的对象。
let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = JSON.parse(JSON.stringify(user));
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name); // "leo1" ⚠️ 差异!
console.log(user.name); // "leo" ⚠️ 差异!
console.log(leo.skill.CSS); // 90 ⚠️ 差异!
console.log(user.skill.CSS);// 80 ⚠️ 差异!
JSON.stringify() 使用注意:
- 拷贝的对象的值中如果有函数,
undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失; - 无法拷贝不可枚举的属性,无法拷贝对象的原型链;
- 拷贝
Date引用类型会变成字符串; - 拷贝
RegExp引用类型会变成空对象; - 对象中含有
NaN、Infinity和-Infinity,则序列化的结果会变成null; - 无法拷贝对象的循环应用(即
obj[key] = obj)。
ES5深拷贝函数封装
var obj = {
name: '一百个Chocolate',
age: 18,
info: {
hobby: ['game', 'music', {
a: 1
}],
career: {
teacher: 0,
engineer: 1
}
}
}
function deepClone(origin, target) {
var tar = target || {};
var toStr = Object.prototype.toString;
var arrType = '[object Array]';
for (let k in origin) {
if (origin.hasOwnProperty(k)) {
if (typeof origin[k] === 'object' && origin[k] !== null) {
tar[k] = toStr.call(origin[k]) === arrType ? [] : {};
deepClone(origin[k], tar[k]);
} else {
tar[k] = origin[k];
}
}
}
return tar;
}
const newObj = deepClone(obj, {});
newObj.info.hobby[2].a = 123;
console.log(obj,newObj);
ES6 深拷贝
var obj = {
name: '一百个Chocolate',
age: 18,
info: {
hobby: ['game', 'music', {
a: 1
}],
career: {
teacher: 0,
engineer: 1
}
}
}
function deepClone(origin){
if(origin == undefined || typeof origin !== 'object'){
return origin;
}
if(origin instanceof Date){
return new Date(origin);
}
if(origin instanceof RegExp){
return new RegExp(origin);
}
const target = new origin.constructor();
for(let k in origin){
if(origin.hasOwnProperty(k)){
target[k] = deepClone(origin[k]);
}
}
return target;
}
const newObj = deepClone(obj);
newObj.info.hobby[2].a = 123;
console.log(obj,newObj);
ES6 深拷贝 WeakMap解决死循环问题
var obj = {
name: '一百个Chocolate',
age: 18,
info: {
hobby: ['game', 'music', {
a: 1
}],
career: {
teacher: 0,
engineer: 1
}
}
}
function deepClone(origin, hashMap = new WeakMap()) {
if (origin == undefined || typeof origin !== 'object') {
return origin;
}
if (origin instanceof Date) {
return new Date(origin);
}
if (origin instanceof RegExp) {
return new RegExp(origin);
}
const hashKey = hashMap.get(origin);
if (hashKey) {
return hashKey;
}
const target = new origin.constructor();
hashMap.set(origin, target);
for (let k in origin) {
if (origin.hasOwnProperty(k)) {
target[k] = deepClone(origin[k], hashMap);
}
}
return target;
}
const newObj = deepClone(obj);
newObj.info.hobby[2].a = 123;
console.log(obj, newObj);
本文参考:
