当阅读团队公众号内文章时皆会收获颇多。公众号内的硬菜也使人大快朵颐,本期给大家献上闲庭信步聊前端的第二弹,《一文摸清ES拷贝的深浅》。下面先给您呈上一份茶点添些谈资(转转熊镇楼~~~)
深浅拷贝的谈资在圈内可不算少,记录下整理这块资料时涉及到的知识点;
拷贝(copy)原意为多,当一份文件、一篇文章有了抄本、副本、复制件时,就“不再是一本”了,而变成了“多”本。
基本类型 : String、Number、Boolean、Null、Undefined、Symbol
引用类型 : Object
内存管理是指软件运行时对计算机内存资源的分配和使用的技术。说到这,不得不就要提到 JavaScript引擎
,这里做个简单说明。
JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中。
JavaScript本身是一门解释性语言,弱类型,只有在程序运行时才会进行编辑。
在JavaScript引擎使用分布可以看出主要引擎为Chrome V8,Node.js 也是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
[ 注意 ]:JavaScript引擎不要和浏览器内核渲染引擎搞混了,两者是浏览器处理网页过程的核心部分,属于相互合作的关系。JavaScript 没有输入或输出的概念。它是一个在宿主环境(Host Environment)下运行的脚本语言,任何与外界沟通的机制都是由宿主环境(浏览器环境、node环境等)提供的。
在此基础上,我们来看看JavaScript引擎`中的栈与堆:
栈 | 堆 | |
---|---|---|
存储类型 | 基本数据类型 | 引用数据类型 |
分配方式 | 静态分配 | 动态分配 |
空间大小 | 小 | 大 |
访问方式 | 按值访问 | 按址访问 |
JavaScript变量是没有数据类型的,值才有数据类型,变量可以随时持有任意数据类型;
总结:
Q:什么时候会用到深浅拷贝?
A:当数据类型为引用数据类型,需要在不影响源数据时对数据的操作。
浅拷贝:适用于引用类型内值为基本类型;
深拷贝:适用于引用类型内值包含引用类型;
来观察下面一个很常规的场景:
// foo与bar变量的值是引用类型,引用类型内存储值是基本类型。
var foo = {
name: 'foo',
age: '20'
};
var bar = [1, '2', 3];
// 尝试通过正常方式拷贝
var fooCopy = foo;
fooCopy.name = 'fooCopy';
foo.age = '21';
console.log(foo); // { name: "fooCopy", age: "21" } 原有的 name 被改变了
console.log(fooCopy); // { name: "fooCopy", age: "21" }
结果并不符合预期,尝试浅拷贝方式处理。
// 1. 对象拷贝 -- ES6方式
var fooEs6Copy = {...foo};
fooEs6Copy.name = 'fooEs6Copy'; // 进行操作
console.log(foo); // {name: "foo", age: "20"}
console.log(fooEs6Copy); // {name: "fooEs6Copy", age: "20"}
// 2. 对象拷贝 -- Object.assign() 方式
var fooAssignCopy = Object.assign({}, foo);
fooAssignCopy.name = 'fooAssignCopy'; // 进行操作
console.log(foo); // {name: "foo", age: "20"}
console.log(fooAssignCopy); // {name: "fooAssignCopy", age: "20"}
// 3. 数组拷贝 -- arrayObject.concat()方式
var barConcatCopy = Array.prototype.concat(bar);
barConcatCopy.push(4); // 进行操作
console.log(bar); // [1, "2", 3]
console.log(barConcatCopy); // [1, "2", 3, 4]
// 4. 数组拷贝 -- arrayObject.slice()方式
var barSliceCopy = Array.prototype.slice.apply(bar);
barSliceCopy.push(4); // 进行操作
console.log(bar); // [1, "2", 3]
console.log(barSliceCopy); // [1, "2", 3, 4]
结果已符合预期,不过改动下数据结构再试试?
// foo与bar变量的值是引用类型,引用类型内存储值是引用类型。
var foo = {
name: 'foo',
age: '20',
other: {
job: '程序员'
}
};
// 尝试通过浅拷贝方式拷贝
var fooEs6Copy = {...foo};
fooEs6Copy.other.job = '猿程序'; // 进行操作
console.log(foo); // {name: "foo", age: "20", other: {job: '猿程序'}}
console.log(fooEs6Copy); // {name: "foo", age: "20", other: {job: '猿程序'}}
emm~,此次同样没有符合预期,那试试深拷贝?
继续使用上述foo代码片段;
JSON.stringify() 将值转换为相应的JSON格式;
JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。
var fooJsonCopy = JSON.parse(JSON.stringify(foo));
fooJsonCopy.job.push('猿程序');
console.log(foo); // {name: "foo", age: "20", other: {job: '程序员'}}
console.log(fooJsonCopy); // {name: "foo", age: "20", other: {job: '猿程序'}}
此方式可以满足需求,不过并不是最优的解决方法,继续浏览以下代码片段:
var foo = {
name: 'foo',
age: Symbol('20'),
fn: function() {},
other: undefined
};
var fooJsonCopy = JSON.parse(JSON.stringify(foo));
console.log(foo); // {name: "foo", age: Symbol(20), other: undefined}
console.log(fooJsonCopy); // {name: "foo"}
查阅JSON.stringify()函数的描述:
undefined
、任意的函数以及 symbol
值,在序列化过程中会被忽略(出现在非数组对象的-属性值中时)或者被转换成 null
(出现在数组中时)。函数、undefined
被单独转换时,会返回 undefined
,如JSON.stringify(function(){})
or JSON.stringify(undefined)
.symbol
为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。Q:什么方法可以解决此问题?
A:封装拷贝函数功能。
Symbol()
返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。WeakMap
对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。选择使用WeakMap
而不是Map
是由于WeakMap
持有的是每个键对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。
function handleDeepCopy(param, storage = new WeakMap()) {
// 不是对象或数组直接返回
if (!(param instanceof Object)) return param;
// 是否是数组
let result = Array.isArray(param) && [] || {};
// 注:实际项目中应不会存在不限引用
// 循环引用 - 返回存储的引用数据
if (storage.has(param)) return storage.get(param);
// 循环引用 - 设置临时存储值,用于解决循环引用
storage.set(param, result);
// 是否包含Symbol类型
let isSymbol = Object.getOwnPropertySymbols(param);
// 若包含Symbol类型
if (isSymbol.length) {
isSymbol.forEach(item => {
// 检查Symbol储存的类型 是否是对象或者数组
if (param[item] instanceof Object) {
result[item] = handleDeepCopy(param[item], storage);
return;
}
result[item] = param[item];
});
}
// 此处的循环时不包含Symbol类型
for (let key in param) {
if (!Object.prototype.hasOwnProperty.call(param, key)) continue;
result[key] = (param[key] instanceof Object) &&
handleDeepCopy(param[key], storage) ||
param[key]
}
return result;
};
大致就介绍到这里了,希望此篇文章能给你带来帮助,若您想了解更多前端的知识,可以关注我们大转转FE,一个有温度的技术公众号(温馨提示:部分文章末尾有彩蛋礼物喔~~~)