Open xxleyi opened 3 years ago
前端面试有一个高频考点:对象深拷贝。
这是一个典型的开放题,有很多很多点,几乎不可能完全覆盖,体现的是个人对未知问题的界定与思考。
我自己的思考如下:
既然是对象子类型 ,那就需要统一枚举 for(let key in obj) ...,至于原型链上的问题,可以考虑通过技术手段进行规避。
for(let key in obj) ...
然后针对函数,如何拷贝呢?实现语义上的拷贝即可,在外面包一层普通函数或箭头函数,使用 toString 区分是否是普通函数。
然后针对 Set 和 Map 还需要将迭代部分的 v 或 k, v 补进去,同时需要注意,Map 类型的数据,其 key 也可能是对象,是否应该深拷贝呢?应该也需要。
总而言之,需要考虑和权衡的东西蛮多,代码逻辑可以遵循主干,写一个后续无需修改,只需扩展的处理逻辑。
我尝试写了这么一个:
const toStringTag = (v) => Object.prototype.toString.call(v).slice(8, -1) function cloneFunction(fun) { if (fun.toString().startsWith('function')) return function(...args) { return fun.call(this, ...args) } return (...args) => fun(...args) } function clone(target, map = new Map()) { const stringTag = toStringTag(target) // 如果是基本数据类型,则直接返回 if (!(target instanceof Object)) return target // 否则处理 ['Array', 'Object', 'Set', 'Map', 'Function'] 以及更多,比如 String, Number, Date, RegExp // 更多情况默认返回 target let deepContainers = { 'Array': [], 'Object': {}, 'Set': new Set(), 'Map': new Map(), 'Function': stringTag === 'Function' ? cloneFunction(target) : null, } for (let k of ['String', 'Number', 'Date', 'RegExp']) { deepContainer[k] = new target.constructor(target) } const deepContainer = deepContainers[stringTag] || target // 意料之外的对象子类型,当作不可变数据处理 if (deepContainer === target) return target if (map.get(target)) return map.get(target) map.set(target, deepContainer) for (let key in target) deepContainer[key] = clone(target[key], map) switch (stringTag) { case 'Set': for (let v of target) deepContainer.add(clone(v, map)) break case 'Map': for (let [k, v] of target) deepContainer.set(clone(k, map), clone(v, map)) break } return deepContainer }
后续只需要扩展 deepContainers 这个映射关系以及相应的 cloneSubObject 函数即可。
deepContainers
cloneSubObject
比如,想要深拷贝 Date 类型,则只需要添加这么一条映射关系:
Date
{ 'Date': stringTag === 'Date' ? cloneDate(target) : null }
以及相应的克隆函数:
function cloneDate(date) { return new Date(+date) }
再比如,想要深拷贝 RegExp 类型,则只需要添加这么一条映射关系:
{ 'RegExp': stringTag === 'RegExp' ? cloneRegExp(target) : null }
function cloneRegExp(reg) { return new RegExp(reg.valueOf()) }
经过上面这两个例子,似乎有了通用的模式:new target.constructor(target.valueOf()) 来对付这类子对象类型。
new target.constructor(target.valueOf())
经过我进一步研究,对于 Date 和 RegExp 类型来说,直接 new target.constructor(target) 就可以实现拷贝,然后重点是就是枚举 key 然后进行深拷贝。
new target.constructor(target)
更新:分析奇怪代码时,尝试使用了一下这个深拷贝,遇到一些问题:浏览器内置的一些数据类型,如 navigator.permissions PermissionStatus 会导致我的程序报错,需要改进一番。改进措施就是不要全部使用 new target.constructor(target),这一招应该只适用于 String, Number, Date, RegExp
String, Number, Date, RegExp
前端面试有一个高频考点:对象深拷贝。
这是一个典型的开放题,有很多很多点,几乎不可能完全覆盖,体现的是个人对未知问题的界定与思考。
我自己的思考如下:
既然是对象子类型 ,那就需要统一枚举
for(let key in obj) ...
,至于原型链上的问题,可以考虑通过技术手段进行规避。然后针对函数,如何拷贝呢?实现语义上的拷贝即可,在外面包一层普通函数或箭头函数,使用 toString 区分是否是普通函数。
然后针对 Set 和 Map 还需要将迭代部分的 v 或 k, v 补进去,同时需要注意,Map 类型的数据,其 key 也可能是对象,是否应该深拷贝呢?应该也需要。
总而言之,需要考虑和权衡的东西蛮多,代码逻辑可以遵循主干,写一个后续无需修改,只需扩展的处理逻辑。
我尝试写了这么一个:
后续只需要扩展
deepContainers
这个映射关系以及相应的cloneSubObject
函数即可。比如,想要深拷贝
Date
类型,则只需要添加这么一条映射关系:以及相应的克隆函数:
再比如,想要深拷贝 RegExp 类型,则只需要添加这么一条映射关系:
以及相应的克隆函数:
经过上面这两个例子,似乎有了通用的模式:
new target.constructor(target.valueOf())
来对付这类子对象类型。经过我进一步研究,对于 Date 和 RegExp 类型来说,直接
new target.constructor(target)
就可以实现拷贝,然后重点是就是枚举 key 然后进行深拷贝。更新:分析奇怪代码时,尝试使用了一下这个深拷贝,遇到一些问题:浏览器内置的一些数据类型,如 navigator.permissions PermissionStatus 会导致我的程序报错,需要改进一番。改进措施就是不要全部使用
new target.constructor(target)
,这一招应该只适用于String, Number, Date, RegExp