第二十题:实现深拷贝函数
KieSun opened this issue · comments
请实现深拷贝函数,并有如下要求:
- 需要支持数组、正则、函数以及 JSON 支持的数据类型
- 需要解决循环引用问题
完成以上内容后,请说明如果存在爆栈的情况该如何解决。
新建了一个大厂真题每日打卡群,有意愿学习打卡的再进,请备注打卡。
前几天刚写过:
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
function isObject(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
}
function getType(target) {
return Object.prototype.toString.call(target);
}
function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
}
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe);
default:
return null;
}
}
function clone(target, map = new WeakMap()) {
// 克隆原始类型
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
} else {
return cloneOtherType(target, type);
}
// 防止循环引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value, map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value, map));
});
return cloneTarget;
}
// 克隆对象和数组
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
module.exports = {
clone
};
引自: 如何写出一个惊艳面试官的深拷贝
剩下的等下再补充
function deepClone(data) {
const map = new Map(); // 用map将已经访问过得对象存起来。
const clone = (obj) => { // 如果是已经访问过得对象,直接返回 避免循环引用导致栈溢出
if (map.has(obj)) {
return obj;
}
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Boolean) return new Boolean(obj.valueOf());
if (obj instanceof Number) return new Number(obj.valueOf());
if (obj instanceof String) return new String(obj.valueOf());
if (obj instanceof RegExp) return new RegExp(obj.valueOf());
if (obj instanceof Date) return new Date(obj.valueOf());
var cpObj = obj instanceof Array ? [] : {};
map.set(obj, 1);
for (var key in obj) cpObj[key] = clone(obj[key]);
return cpObj;
};
return clone(data);
};
const temp = {};
const obj = {
a: temp,
b: undefined,
c: new RegExp(/\w/),
d: () => {},
}
temp.b = obj;
const finall = deepClone(obj);
console.log(finall);
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
const temp = map.get(obj)
if (temp) return temp
// 其他包装类型
switch (Object.prototype.toString.call(obj)) {
case '[object String]':
case '[object Number]':
case '[object Boolean]':
case '[object RegExp]':
case '[object Date]':
case '[object Error]':
return new obj.constructor(obj.valueOf())
}
const resObj = obj instanceof Array ? [] : {}
map.set(obj, resObj)
for (let k in obj) { // for in 会遍历到原型链上的属性,但是原型链上的属性其实是公用的
if (obj.hasOwnProperty(k)) {
resObj[k] = deepClone(obj[k], map)
}
}
return resObj
}
const obj = {
a: [1, 2, 3],
b: {
c: 1
},
c: 1,
date: new Date(),
regExp: new RegExp(/a/),
fn: () => { console.log('fn') },
symbol: Symbol()
}
obj.self = obj
const resObj = deepClone(obj)
console.log(resObj)
function deepClone(obj, hash = new WeakMap()) {
// 记录拷贝前和拷贝后的对应关系
if (obj == null) return obj
if (obj instanceof RegExp) return new RegExp(obj)
if (obj instanceof Date) return new Date(obj)
// ....
if (typeof obj !== 'object') return obj
// 对象类型 obj 数组 :[] 和 对象: {}
if (hash.has(obj)) return hash.get(obj) // 返回上次拷贝的结果 不在递归了
const copy = new obj.constructor()
hash.set(obj, copy) // 引用类型
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key], hash)
}
}
return copy
}
// 如果拷贝过的对象 不需要再次拷贝
var obj1 = { a: '1' }
obj1.b = {}
obj1.b.a = obj1.b
console.log(deepClone(obj1))
function deepClone(obj, newObj){
let typeArr = ['[object Function]', '[object RegExp]', '[object Date]']; // 这里可以根据实际情况自己加类型呀
let toStr = '';
for(let i in obj){
if(obj[i] == null || typeof obj[i] != 'object'){
newObj[i] = obj[i]
}else{ // 为什么这里不用else if,主要是为了少执行一点 Object.prototype.toString.call(obj[i])
toStr = Object.prototype.toString.call(obj[i]);
if(typeArr.includes(toStr)){
newObj[i] = obj[i]
}else {
newObj[i] = Array.isArray(obj[i]) ? [] : {}
deepClone(obj[i], newObj[i])
}
}
}
}
验证
var obj1 = {
a: 11,
b: [1,2,{a:1}],
c: {
a:1,
b: {
a:1,
b:2
}
},
d: null,
e: undefined,
f: new Date(),
g: function(){},
}
var obj2 = {}
deepClone(obj1,obj2)
obj1.c.b.a=2;
console.log("obj2",obj2)
循环引用?不会
const isObject = (a) => Object.prototype.toString.call(a) === "[object Object]";
const isReg = (a) => Object.prototype.toString.call(a) === "[object RegExp]";
const isFunction = (a) =>
Object.prototype.toString.call(a) === "[object Function]";
const isString = (a) => typeof a === "string";
const isNumber = (a) => typeof a === "number";
const isArray = (a) => Array.isArray(a);
function cloneRegExp(regexp) {
const reFlags = /\w*$/;
const result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
result.lastIndex = regexp.lastIndex;
return result;
}
const deepClone = (data, hash = new Map()) => {
if (hash.has(data)) {
return hash.get(data);
} else {
hash.set(data, true);
}
const o = {};
if (isObject(data)) {
for (const key in data) {
o[key] = deepClone(data[key], hash);
}
}
if (isArray(data)) {
return data.map((item) => deepClone(item, hash));
}
if (isNumber(data)) return data;
if (isString(data)) return data;
if (isFunction(data)) return data;
if (isReg(data)) return cloneRegExp(data);
return o;
};
// test
var l1 = {};
var l2 = {};
l1.l2 = l2;
l2.l1 = l1;
const data = {
reg: new RegExp(/a/g),
func: function () {},
arr: [1, 2, 4],
a: {
b: 123
},
l1
};
const newData = deepClone(data);
console.log(newData);
console.log(newData.func === data.func); // true
console.log(newData.reg === data.reg); // false
console.log(newData.arr === data.arr); // false
破题:
- 使用递归
deepClone
,如果不是对象,则直接赋值,否则继续递归,直至最深处的非对象类型 - 使用
for in
遍历对象 - 使用
Child instanceof Parent
判断对象的类型
function deepClone(object, hash = new WeakMap()) {
if (object instanceof RegExp) return new RegExp(object);
if (object instanceof Date) return new Date(object);
if (object instanceof Function) return object;
let clonedObj = {};
if (hash.has(object)) return hash.get(object);
hash.set(object, clonedObj);
for (const key in object) {
if (object[key] instanceof Object) {
clonedObj[key] = deepClone(object[key], hash);
} else {
clonedObj[key] = object[key];
}
}
return clonedObj;
}
如何解决循环应用问题呢 ?
new WeakMap()
function deepCopy(obj, map = new WeakMap()) {
const type = val => Object.prototype.toString.call(val).slice(8, -1)
if (map.has(obj)) return map.get(obj)
const types = [
'RegExp',
'Date',
'Number',
'String',
'Boolean',
'Function',
'Symbol'
]
if (types.includes(type(obj))) return obj
if (Array.isArray(obj)) {
return obj.map(x => deepCopy(x, map))
}
if (typeof obj === 'object' && obj !== null) {
let res = {}
map.set(obj, res)
return Object.assign(
res,
Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, deepCopy(v, map)])
)
)
}
}
科普
- 浅拷贝是指想对深拷贝,只克隆了第一层级的数据,对象中还有一层对象,将不会被拷贝到
- JSON.stringify 正则 函数 日期等都会出现问题,没办法正常转化为字符串
- 一般项目中使用 JSON.parse(JSON.stringify(obj)) 这样就能做到,但是需要确认对象中不存在日期,函数,正则
- 面试的时候就要写递归,面试的时候可以说参考lodash的深克隆 进行学习
答案
function deepClone(obj) {
if (obj === null) return null;
if (typeof obj !== "function") return new Function(obj);
if (typeof obj !== "object") return obj;
if (obj instanceof RegExp) {
return new RegExp(obj);
}
if (obj instanceof Date) {
return new Date(obj);
}
let newObj = new obj.constructor; // 不直接创建一个空的对象,克隆的结果保持和之前相同的所属类
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
爆栈
参考群里老哥的说法,描述的很好
function isObjectType(obj) {
return obj instanceof Object
}
function deepClone(obj, map = new Map()){
if(!isObjectType(obj)) return obj
if(obj instanceof Date) return new Date(obj)
if(obj instanceof RegExp) return new RegExp(obj)
// 对于函数的复制,重新bind一下得到一个新的函数,使用new Function是不可行的
if(obj instanceof Function) return obj.bind(null)
// 通过map解决循环引用问题
if(map.has(obj)) return map.get(obj)
const res = new obj.constructor()
map.set(obj, res)
// 使用Reflect.ownKeys保证能拷贝symbol
Reflect.ownKeys(obj).forEach(key => {
res[key] = deepClone(obj[key], map)
})
return res
}
function add(a, b, c) {
return a + b + c;
}
const obj1 = {
a: 1,
b: 2,
date: new Date(),
arr: [1, 2, 3],
func: add,
[Symbol()]: 'symbol',
u: undefined,
};
obj1.circular= obj1
console.log(deepClone(obj1))
/**
*
{
a: 1,
b: 2,
date: 2021-04-02T07:55:46.877Z,
arr: [ 1, 2, 3 ],
func: [Function: bound add],
u: undefined,
circular: [Circular],
[Symbol()]: 'symbol'
}
*/
如果解决了循环引用的问题就不会爆栈了吧
function deepclone(obj,map = new WeakMap()){
let result ={};
let type = Object.prototype.toString.call(obj).slice(8,-1);
if(map.has(obj)) return map.get(obj)
if(type === 'Function') return eavl('('+obj +')');
if(type != 'Array' || type != 'Object') return obj;
if(type==='Array'){
return obj.map(i=>{ return deepclone(i,map)})
}
if(type === 'Object'){
for( item in obj){
if(obj.hasOwnProperty(item)){
result[item] = deepclone(obj[item],map)
}
}
}
return result
}
function setClone(obj) {
let now = new Set();
obj.forEach((e, index) => {
now.add(e);
});
return now;
}
function mapClone(obj) {
let now = new Map();
obj.forEach((value, key) => {
now.set(key, value);
});
return now;
}
const deepClone = (obj) => {
// 非引用类型及函数将直接返回
if (!obj || typeof obj !== 'object') return obj;
// 特殊的引用类型处理
switch(Object.prototype.toString.call(obj).slice(8, -1)) {
case 'Date':
return new Date(obj);
break;
case 'RegExp':
return new RegExp(obj);
break;
case 'String':
return new String(obj);
break;
case 'Number':
return new Number(obj);
break;
case 'Boolean':
return new Boolean(obj);
case 'Symbol':
return Object(Symbol.prototype.valueOf.call(obj));
case 'Function':
return eval(obj[k].toString());
case 'Error':
return obj.bind();
case 'Map':
return setClone(obj);
case 'Set':
return mapClone(obj);
break;
}
const map = deepClone.map = deepClone.map || new Map();
// 使用map结构可以不必循环缓存,提高效率
if (map.get(obj)) {
return map.get(obj);
}
const result = obj instanceof Array ? [] : {};
map.set(obj, result);
for (let propName in obj) {
if (obj.hasOwnProperty(propName)) {
result[propName] = deepClone(obj[propName]);
}
}
return result;
}
不用递归循环用基础遍历
function deepClone(source) { //递归拷贝
if(source === null) return null; //null 的情况
if(source instanceof RegExp) return new RegExp(source);
if(source instanceof Date) return new Date(source);
if(typeof source !== 'object') {
//如果不是复杂数据类型,直接返回
return source;
}
//解决数组兼容
var target = Array.isArray(source) ? [] : {};
for(let key in source) {
//如果 source[key] 是复杂数据类型,递归
target[key] = deepClone(source[key]);
}
return target;
}
function deepCopy(newObj, oldObj) {
for (let key in oldObj) {
// 获取属性值 oldObj[key]
let item = oldObj[key];
if (item instanceof Array) {
newObj[key] = [];
deepCopy(newObj[key], item);
} else if (item instanceof Object) {
newObj[key] = {};
deepCopy(newObj[key], item);
} else {
newObj[key] = item;
}
}
}
解决了循环还会有爆栈,能想到的就是递归层级太深(chrome超过1000),那我就采用广度优先遍历的方式拷贝
不考虑Symbol等,因为题目没有要求
const toString = Object.prototype.toString
/**
* 拷贝正则表达式
* @param {RegExp} reg
*/
function cloneReg(reg) {
let flags = reg.flags || /\w*$/.exec(reg)
// 不用拷贝lastindex了吧
return new RegExp(reg.source, flags)
}
/**
* 拷贝一个函数
* @param {Function} fun 需要拷贝的函数
* @param {Boolean} isCloneKey 是否复制函数得自身属性
* ps: 拷贝函数作用是啥
*/
function cloneFun(fun, isCloneKey = true) {
let temp = null
// new Function 会丢失闭包相关变量,bind会导致this指向问题
eval(`temp = function ${fun.name}(...a){return fun.call(this, ...a)}`);
if (isCloneKey) {
// 这里又涉及到深拷贝了,哈哈不想管了
Object.keys(fun).forEach(key => {
temp[key] = fun[key]
})
}
return temp
}
/**
* 拷贝一个值
* @param {String | Number | Boolean | undefined | null | Function | RegExp} value 要拷贝的值
* @returns 拷贝后的数据
*/
function cloneValue(value) {
let baseTypes = ["String", "Number", "Boolean", "Undefined", "Null"]
let type = getType(value)
if (baseTypes.includes(type)) {
return value
}
switch (type) {
case "RegExp":
return cloneReg(value)
case "Function":
return cloneFun(value)
default:
console.warn("传入的类型不支持拷贝")
break;
}
}
/**
* bfs方式 深拷贝函数
* @param {}
*/
function deepCloneObj(obj, config = {}) {
let objectTypes = ["Object", "Array"]
let type = getType(obj)
if (!objectTypes.includes(type)) {
throw new Error('仅支持拷贝对象或者数组')
// return cloneValue(obj)
}
let result = {}
let map = new Map([[obj, result]]) // 对象自身的某个key可能会引用对象自己
let queue = Object.entries(obj).map(([key, value]) => ([result, key, value]))
while (queue.length > 0) {
let [target, key, value] = queue.shift()
let type = getType(value)
if (objectTypes.includes(type)) {
let temp = map.get(value) // 查找是否已经有拷贝过的值
if(temp) {
target[key] = temp
continue
}
let isArray = Array.isArray(value)
let newObj = isArray ? [] : {}
target[key] = newObj
map.set(value, newObj)
isArray ?
queue.push(...value.map((item, index) => ([newObj, index, item]))) :
queue.push(...Object.entries(value).map(([key, value]) => ([newObj, key, value])))
} else {
target[key] = cloneValue(value)
}
}
return result
}
let father = {
name: 'father',
age: 30,
gender: 'male',
reg: /age\d\d/g,
childrens: [],
say(){
console.log("my name is", this.name)
}
}
let son = {
name: 'son',
age: 4,
gender: 'male',
father: father,
say(){
console.log("my name is", this.name)
}
}
father.childrens.push(son)
let father2 = deepCloneObj(father)
console.log("原对象 father: ", father)
console.log("拷贝对象 father2: ", father2)
console.log("验证循环引用是否正确拷贝:", father2 === father2.childrens[0]["father"])
father2.say.flag = 'father2 say'
console.log("验证函数是否正确拷贝:", father.say.flag !== father2.say.flag)
console.log("验证函数名是否拷贝:", father2.name === father.name)
console.log("验证函数是否正确执行:")
father2.name = 'father2'
father2.childrens[0]["name"] = 'father2 son'
console.log("------father say------")
father.say()
father.childrens[0]["say"]()
console.log("------father2 say------")
father2.say()
father2.childrens[0]["say"]()
let str = "abcdage23sfage45"
father2.reg.test(str)
console.log("验证正则表达式是否正确拷贝: ", father2.reg.lastIndex !== father.reg.lastIndex)
/**
* 获取数据类型
* @param {any} value
*/
function getType(value) {
return toString.call(value).slice(8, -1)
}
学到了
// 来个不一样的.................
// 这个好说, 判断一个值是不是引用类型
const isObject = target => (typeof target === 'object' || typeof target === 'function') && target !== null
const deepClone = (obj, hash = new WeakMap()) => {
// 用 hash 可以解决循环引用的问题
if(hash.has(obj)) return hash.get(obj)
// constructors: 所有的构造函数,
const constructors = [Date, Map, WeakMap, Set, WeakSet, RegExp]
// 比如当一个值是new Date()时, 那么他的constructor 就是 Date
if(constructors.includes(obj.constructor)){
return new obj.constructor(obj)
}
// 获取对象所有属性描述
const allDesc = Object.getOwnPropertyDescriptors(obj)
// 继承所有的原型, 并用 allDesc 生成新的 key
const cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
hash.set(obj, cloneObj)
// Reflect.ownKeys 可以拿到所有的 key, 包括不可枚举的
for(let key of Reflect.ownKeys(obj)){
cloneObj[key] = isObject(obj[key]) && typeof obj[key] !== 'function'? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}