|
当阅读团队公众号内文章时皆会收获颇多。公众号内的硬菜也使人大快朵颐,本期给大家献上闲庭信步聊前端的第二弹,《一文摸清ES拷贝的深浅》。下面先给您呈上一份茶点添些谈资(转转熊镇楼~~~)为什么用深浅拷贝深浅拷贝的谈资在圈内可不算少,记录下整理这块资料时涉及到的知识点;当对象需要从可变状态转为不可变状态时,变更后的对象不影响原对象。当对象需要从不可变状态转为可变状态时,变更后的对象不影响原对象。深浅拷贝场景图浅品深浅拷贝由来拷贝(copy)原意为多,当一份文件、一篇文章有了抄本、副本、复制件时,就“不再是一本”了,而变成了“多”本。数据类型基本类型:String、Number、Boolean、Null、Undefined、Symbol引用类型:Object内存管理(堆与栈)内存管理是指软件运行时对计算机内存资源的分配和使用的技术。说到这,不得不就要提到JavaScript引擎,这里做个简单说明。JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中。JavaScript本身是一门解释性语言,弱类型,只有在程序运行时才会进行编辑。分布图在JavaScript引擎使用分布可以看出主要引擎为ChromeV8,Node.js也是一个基于ChromeV8引擎的JavaScript运行环境。[注意]:JavaScript引擎不要和浏览器内核渲染引擎搞混了,两者是浏览器处理网页过程的核心部分,属于相互合作的关系。JavaScript没有输入或输出的概念。它是一个在宿主环境(HostEnvironment)下运行的脚本语言,任何与外界沟通的机制都是由宿主环境(浏览器环境、node环境等)提供的。在此基础上,我们来看看JavaScript引擎`中的栈与堆:栈堆存储类型基本数据类型引用数据类型分配方式静态分配动态分配空间大小小大访问方式按值访问按址访问JavaScript变量是没有数据类型的,值才有数据类型,变量可以随时持有任意数据类型;总结:深浅拷贝是对js引用数据类型的拷贝。深浅拷贝发生的内存空间是堆空间。js中的栈空间与堆空间由js引擎提供。访问堆空间的数据时,其实是访问其指针,指针是不变的,但数据时可变的,故才有了深浅拷贝。如何实现深浅拷贝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~,此次同样没有符合预期,那试试深拷贝?深拷贝-JSON.parse()继续使用上述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(){})orJSON.stringify(undefined).对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。所有以symbol为属性键的属性都会被完全忽略掉,即便replacer参数中强制指定包含了它们。Q:什么方法可以解决此问题?A:封装拷贝函数功能。深拷贝-封装函数Symbol:每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。WeakMap: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,一个有温度的技术公众号(温馨提示:部分文章末尾有彩蛋礼物喔~~~)
|
|