KieSun / all-of-frontend

你想知道的前端内容都在这

Home Page:https://yuchengkai.cn/docs/frontend

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

第二十题:实现深拷贝函数

KieSun opened this issue · comments

commented

请实现深拷贝函数,并有如下要求:

  1. 需要支持数组、正则、函数以及 JSON 支持的数据类型
  2. 需要解决循环引用问题

完成以上内容后,请说明如果存在爆栈的情况该如何解决。

去答题

新建了一个大厂真题每日打卡群,有意愿学习打卡的再进,请备注打卡

前几天刚写过:

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
};

引自: 如何写出一个惊艳面试官的深拷贝

剩下的等下再补充

commented
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()

commented
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)])
      )
    )
  }
}

科普

  1. 浅拷贝是指想对深拷贝,只克隆了第一层级的数据,对象中还有一层对象,将不会被拷贝到
  2. JSON.stringify 正则 函数 日期等都会出现问题,没办法正常转化为字符串
  3. 一般项目中使用 JSON.parse(JSON.stringify(obj)) 这样就能做到,但是需要确认对象中不存在日期,函数,正则
  4. 面试的时候就要写递归,面试的时候可以说参考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
    }