Repository navigation
JavaScript专题之深浅拷贝 #32
Description
Activity
期待下一篇!!!
养肥了再看一遍
null应该特殊考虑一下吧,在深拷贝中,值为null会赋值一个空对象
@yunlzhang 感谢指出,现在的 deepCopy 方法确实有这个问题
deepCopy({ value: null })
的值为:
{value: {}}
这篇的目的在于讲解深浅拷贝的概念以及深浅拷贝的思路,下一篇 《JavaScript专题之从零实现jQuery的extend》 才是讲解深浅拷贝的详细实现,在下一篇的 extend 方法就有对于 null 的处理~
楼主对js的理解这么透彻,是怎么学的啊?
@mengxin-FE javaScript 我还有很多地方需要研究,倒不算透彻,不过谢谢夸奖哈~ 如果说学习方法的话,就是确定一个要研究的主题,然后大量阅读该主题相关的文章,尽量保证每篇文章都能理解,如果不能理解,第二天再看一遍,直到看懂为止,如果可以的话,再写写文章,将学到的知识梳理出来,与大家分享~
Reacted by 你好世界, hrmnrx, 998, bluefantasy728, 拾木 Ash, hu33, Wang Zhengchen, chqfly, Elsie23, 白霸天 and 120 moreReacted by Jeremy_Lvjr, Wang Zhengchen, 小动物保护协会, Wang Feixiang, SkipperP, liaojin1, riven, beexpectedone, JiangHao, zwsnail and 2 moreReacted by 白霸天, zimeigui, 小动物保护协会, Wang Feixiang, L, liaojin1, 小兰 , riven, beexpectedone, PaulaSham and 3 moreReacted by JiangHao and WDNMDHPHWJReacted by Alan.He and WDNMDHPHWJfunction deepClone (obj) { if (Array.isArray(obj)) { return obj.map(deepClone) } else if (obj && typeof obj === 'object') { var cloned = {} var keys = Object.keys(obj) for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i] cloned[key] = deepClone(obj[key]) } return cloned } else { return obj } }
这样也可以
Reacted by 山超, Junlin-Zhu, daomingQian, Sunshine, NieHang and chen_longshengReacted by 李云花Reacted by 李云花 and Sunshine@Tvinsh 确实可以,感谢分享哈~
我从《你不知道的 JavaScript》一书看到,工具函数 JSON.stringify(..) 在将JSON对象序列化为字符串时也用到了 ToString 。请注意, JSON 字符串化并非严格意义上的强制类型转换,因为其中也涉及 ToString 的相 关规则。
对大多数简单值来说, JSON 字符串化和 果总是字符串:toString()的效果基本相同,只不过序列化的结JSON.stringify( 42 ); // "42" JSON.stringify( "42" ); // ""42""(含有双引号的字符串) JSON.stringify( null ); // "null" JSON.stringify( true ); // "true"所有 安全的 JSON 值 (JSON-safe)都可以使用 JSON.stringify(..) 字符串化。 安全的 JSON 值是指能够呈现为有效 JSON 格式的值。
下面敲黑板划重点:
为了简单起见, 我们来看看什么是 不安全的 JSON 值 。 undefined 、 function 、 symbol (ES6+)和包含循环引用(对象之间相互引用,形成一个无限循环)的 对象 都不符合 JSON 结构标准,支持 JSON 的语言无法处理它们。
JSON.stringify(..) 在对象中遇到 undefined 、 function 和 symbol 时会自动将其忽略, 在 数组中则会返回 null (以保证单元位置不变)。
例如:
JSON.stringify( undefined ); JSON.stringify( function(){} ); JSON.stringify( [1,undefined,function(){},4] ); JSON.stringify({ a:2, b:function(){} } ); // undefined // undefined // "[1,null,null,4]" // "{"a":2}"对包含循环引用的对象执行 JSON.stringify(..) 会出错。
...Reacted by wan, 幸运儿~, GerritV, Komo, zimeigui, Sphinm, MarRoar, okzhang, fuiyu, zhongkang and 43 moreReacted by yingye, jingaier, wmo123 and w2xi@naihe138 非常感谢补充,o( ̄▽ ̄)d JSON.stringify 这部分确实写得太浅薄了。
@veedrin 可以,只是不知道为什么,我觉得怪怪的,好像一般不会这样描述……
请问深拷贝的时候遇到相互引用的情况怎么处理?
43 remaining items
为什么还要用
hasOwnProperty判断一下啊if (typeof obj !== 'object') return;
是不是return obj比较好function deepClone(obj) { function isObject(o) { return typeof o === "object" && o !== null; } if (!isObject(obj)) { throw new Error("非对象"); } const newObj = obj instanceof Array ? [] : {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { const cur = obj[key]; newObj[key] = isObject(cur) ? deepClone(cur) : cur; } } return newObj; }
为什么还要用
hasOwnProperty判断一下啊因为for in 不仅遍历对象自身属性,还会遍历继承的inumerable 属性,这里只拷贝自身属性。
Reacted by RobertChaw and Stephan_zhang为什么没有人说递归深拷贝有缺点的: 1. 栈溢出、2. 尾递归优化、3. 动态规划来解决.
大佬深拷贝没考虑环么?
有两个问题
typeof obj[key] === 'object' 无法判断null的情况
如果拷贝的是类的实例对象 方法无法拷贝 并且输出的对象的类型是Object而不是class
var deepCopy = function(obj) {
if (!(obj instanceof Object)) return obj;
var newObj = obj instanceof Array ? [] : Object.create(obj.proto);
for (var key of Object.getOwnPropertyNames(obj)) {
newObj[key] = obj[key] instanceof Object ? deepCopy(obj[key]) : obj[key];
}
return newObj;
}
进行了略微的修改,大佬你看看
文章写得很好,点个赞!深拷贝的实现中 好像数组还是浅拷贝
想咨询一下
var newObj = obj instanceof Array ? [] : {}; 这一句修改修改为:
var newObject = new obj.constructr(); 存在什么风险吗想咨询一下
var newObj = obj instanceof Array ? [] : {}; 这一句修改修改为:
var newObject = new obj.constructr(); 存在什么风险吗严格意义上讲两个风险差不多,从原型链方面考虑:1. 开发者会存在修改 obj 的原型的情况,此时 instanceof 判断失效,constructor 同样不起作用 2. 基于前者,开发者还可以自定义原型、修改 constructor 变量指向等
能用 typeof 和 Object.prototype.toString.call() 就优先使用这两个
完美深复制window.XMLHttpRequest这种类型的有啥办法吗? 上面的方法丢失构造方法或者原型方法
处理循环引用的版本:
function deepClone(target, map = new WeakMap()) { if (map.has(target)) { return map.get(target) } // 特殊处理:正则、日期 if (isRegExp(target) || isDate(target)) { return new target.constructor(target) } if (isObject(target)) { const cloneTarget = isArray(target) ? [] : {} map.set(target, cloneTarget) // 缓存循环引用的拷贝结果 for (const prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = deepClone(target[prop], map) } } return cloneTarget } else { return target } }
拷贝函数,有两种写法貌似可以:
eval(target.toString()) // or new Function(`return ${target.toString()}`)()
Reacted by Stephan_zhanghi,问下深拷贝中对于递归调用的怎么处理
hi,问下深拷贝中对于递归调用的怎么处理
@ethanzhongyi 用 WeakMap 缓存已处理的对象
前言
拷贝也是面试经典呐!
数组的浅拷贝
如果是数组,我们可以利用数组的一些方法比如:slice、concat 返回一个新数组的特性来实现拷贝。
比如:
用 slice 可以这样做:
但是如果数组嵌套了对象或者数组的话,比如:
我们会发现,无论是新数组还是旧数组都发生了变化,也就是说使用 concat 方法,克隆的并不彻底。
如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。
我们把这种复制引用的拷贝方法称之为浅拷贝,与之对应的就是深拷贝,深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。
所以我们可以看出使用 concat 和 slice 是一种浅拷贝。
数组的深拷贝
那如何深拷贝一个数组呢?这里介绍一个技巧,不仅适用于数组还适用于对象!那就是:
是一个简单粗暴的好方法,就是有一个问题,不能拷贝函数,我们做个试验:
我们会发现 new_arr 变成了:
浅拷贝的实现
以上三个方法 concat、slice、JSON.stringify 都算是技巧类,可以根据实际项目情况选择使用,接下来我们思考下如何实现一个对象或者数组的浅拷贝。
想一想,好像很简单,遍历对象,然后把属性和属性值都放在一个新的对象不就好了~
嗯,就是这么简单,注意几个小点就可以了:
深拷贝的实现
那如何实现一个深拷贝呢?说起来也好简单,我们在拷贝的时候判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数不就好了~
性能问题
尽管使用深拷贝会完全的克隆一个新对象,不会产生副作用,但是深拷贝因为使用递归,性能会不如浅拷贝,在开发中,还是要根据实际情况进行选择。
下期预告
难道到这里就结束了?是的。然而本篇实际上是一个铺垫,我们真正要看的是 jquery 的 extend 函数的实现,下一篇,我们会讲一讲如何从零实现一个 jquery 的 extend 函数。
专题系列
JavaScript专题系列目录地址:https://github.com/mqyqingfeng/Blog。
JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。