原文:ES6时代,你真的会克隆对象吗(二)前端
上一篇,咱们从Symbol和是否可枚举以及属性描述符的角度分析了ES6下怎么浅拷贝一个对象,发表在掘金和segmentfault上,从评论看,部分人觉着看不懂,今天,咱们用更简单的方式来聊聊深拷贝的问题jquery
深拷贝的话题好像历来没有中止过讨论,JavaScript并无一个能够实现深拷贝的方法,咱们常见的实现方式是递归和JSON.parse(JSON.stringify())
(据说底层仍是用了递归),然而通常库函数也只能处理常见的需求(不常见的需求真的存在吗?真的须要用深拷贝吗?真的不认可是你代码的问题吗?)。今天,我就仔细、认真,细致(也不是很细致),负责(也不敢太保证)的态度来研究一下怎么实现一个深拷贝吧,虽然一度放弃,事实也的确是放弃了,但不把这么多天的付出写出来怎么对得起那个在这个寒冷的冬天忍住瑟瑟发抖的在键盘上敲击的我...git
JSON.parse(JSON.stringify())
的确是一种很简单易用的方式呢,惋惜的是,JSON是一个颇有原则的男人,他可不会对你言听计从。在遇到不安全的JSON值会自动将其忽略,在数组中则会返回null(以保证单元位置不变)。es6
不安全的 JSON 值: undefined 、 function 、 symbol (ES6+)和包含循环引用(对象之间相互引用,造成一个无限循环)的 对象 都不符合 JSON 结构标准,支持 JSON 的语言没法处理它们github
上一篇讲浅拷贝的时候,咱们在开始引入了一个浅拷贝的例子,如今咱们把它改为一件简单的深拷贝。算法
function deepCopy (obj) {
if (typeof obj !== 'object') {
return
}
var newObj = obj instanceof Array ? [] : {}
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
}
}
return newObj
}
复制代码
好像也还不错,简单易懂还能用,通常的场景的确是一种不错的方法呢,可是,今天咱们来看看不通常的场景。segmentfault
咱们先来挑挑毛病:数组
function
类型没有处理(大概,或许,应该是真的不必吧,下面我也并不打算讨论这货,有兴趣的去看看call
、apply
、bind
)typeof
和instanceof
靠谱吗?(特别注意typeof null
的坑)上面多处说到了循环引用的问题,咱们先来看看什么是循环引用:浏览器
var a = {}
a.b = a
复制代码
是的,就是这么一个反人类的存在,可是倒是咱们不能忽略的一个大问题。咱们是应该返回空呢、undefined
呢,仍是它的引用,仍是什么呢?好像没有标准答案呢,嗯,那就Follow Your Heart吧!安全
思考一下:
typeof null // "object"
null instanceof Object // false
复制代码
进行类型判断是无可避免的,然而咱们彷佛并无什么完美的方式获得咱们须要的类型,咱们先来看看几种经常使用的方式:
typeof
: 返回一个表达式的数据类型的字符串,返回结果为js基本的数据类型,包括number
,boolean
,string
,object
,undefined
,function
,symbol
instanceof
: 判断一个对象是否为某一数据类型,或一个变量是否为一个对象的实例;返回boolean类型。内建类型只有经过构造器才能用instanceofconstructor
: 是每个实例对象都拥有的属性,而这个属性也至关因而一个指针,它指向于建立当前对象的对象Object.prototype.toString.call(obj).slice(8,-1)
: 返回的是类名typeof
的问题就很明显了:
typeof null // "object"
typeof function () {} // "function"
typeof [] // "object"
复制代码
instanceof
考虑一下多全局对象(多个frame
或多个window
之间的交互),在浏览器中,咱们的脚本可能须要在多个窗口之间进行交互。多个窗口意味着多个全局环境,不一样的全局环境拥有不一样的全局对象,从而拥有不一样的内置类型构造函数。这可能会引起一些问题。好比,表达式 [] instanceof window.frames[0].Array
会返回false
,由于 Array.prototype !== window.frames[0].Array.prototype
constructor
属性获得的仅仅是构造函数,并且是能够被手动更改的,constructor.name
只是返回的构造函数的名字,它并不返回类名。
Object.prototype.toString.call
算是比较公认靠谱的方法了吧,然而,它一样有可能被人为仿造,鸭子类型嘛,但它仍是比较安全的方式。
鸭子类型: "若是它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子"。动态类型的语言倾向于你让它作什么它就是什么
讨论铺垫的内容应该够细了吧,接下来咱们看看js的复杂数据类型到底有多复杂。
咱们常见的有:
基本包装类型(Boolean、String、Number)、function、Array、Date
你常见,但你不必定想的起的:
RegExp,Arguments,Error、NodeList
你不必定常见,你也不必定知道的:
Blob、File、FileList、ImageData
ES6:
Map、Set、WeakMap、WeakSet、ArrayBuffer对象、TypedArray视图和DataView视图、Float32Array、Float64Array、Int8Array...
或许列举的少了很多,可是已经够让人担心深克隆的复杂程度了,一一实现他们不是一件简单的事情,甚至是一件彻底没有必要的事情(固然可让你了解更多),推荐几个很优秀的方案供参考:
克隆的部分就写的差很少了,原本想写点Map、Set的内容的,无赖,并无找到合适的地方,MDN、阮一峰的ECMAScript 6 入门都介绍的挺好的。
好吧,就这样吧,前端界的小学生,不足之处,还请指正