• 【面试题】面试官:请你实现一个深拷贝,那如果是正则/set/函数怎么拷贝?


    一、面试官灵魂三连问:

    1. 你知道哪些拷贝的方法?
    2. 让你实现一个深拷贝怎么实现?
    3. 那像正则、Set、Map、函数等如何拷贝?

    二、浅拷贝方法

    自己创建一个新对象,来接收你要重新复制或引用的对象值。如果对象属性是基本数据类型,复制的就是基本数据类型的值给新对象;如果属性是引用数据类型,复制的就是内存中的地址,如果一个对象改变了这个属性值,那么会影响到另一个对象。

    1.Object.assign()

    • 不会拷贝对象的继承属性
    • 不会拷贝对象的不可枚举属性
    • 可以拷贝symbol类型

    扩展:什么是对象的不可枚举属性?


    对象的每一个属性都有一个描述对象,用来描述和控制该对象的属性行为
    使用Object.getOwnPropertyDescriptor() 方法来获取描述对象
    通过Object.definedProperty() 来设置-----是不是很熟悉,这是vue中数据绑定的方法


    例如

    1. let obj = {
    2. name : '123'
    3. }
    4. console.log(Object.getOwnPropertyDescriptor(obj,'name'))
    5. /*
    6. 输出内容
    7. configurable:true 能否通过delete删除此属性
    8. enumerable : true 表示属性是可以枚举 即是否通过for in 或 Object.keys() 返回属性
    9. value: '123'
    10. writable: true 表示能否修改属性的值
    11. */
    12. // 设置属性
    13. Object.defineProperty(obj,'名字',{
    14. value:'不可枚举属性',
    15. enumerable:false
    16. })

    扩展:如何知道是“不可枚举属性”?

    1. // 方法一:看颜色
    2. console.log(obj)
    3. /*
    4. 输出:
    5. {
    6. name: "123" // 控制台里是深色字体
    7. 名字: "不可枚举属性" // 控制台里是浅色字体
    8. }
    9. */
    10. // 方法二:使用以下四个方法
    11. /*
    12. 四个操作会忽略enumerable为false
    13. for in
    14. Object.keys()
    15. Object.assign()
    16. JSON.stringify()
    17. */
    18. // 我们实测下
    19. for(let prop in obj){
    20. console.log(prop)
    21. }
    22. /*
    23. 输出:name
    24. */
    25. console.log(Object.keys(obj))
    26. /*
    27. 输出: ['name']
    28. */
    29. console.log(Object.assign({},obj))
    30. /*
    31. 输出: {name: '123'}
    32. */
    33. console.log(JSON.stringify(obj))
    34. /*
    35. 输出: '{"name":"123"}'
    36. *

    2.扩展运算符方式

    1. let obj2 = {...obj1}
    2. let arr2 = [...arr1] //跟arr.slice()一样的效果

    3.concat拷贝

    1. let arr1 = [1,2,3]
    2. let arr2 = arr1.concat()

    4.slice拷贝数组

    1. let arr1 = [1,2,3]
    2. let arr2 = arr1.slice()

    手动实现浅拷贝

    1. var deepClone = target => {
    2. //判断是否是对象类型,不是对象类型的话,直接返回本身
    3. if ((typeof target === "object" || typeof target === 'function') && target !== null) {
    4. //判断目标是数组还是对象
    5. const cloneTarget = Array.isArray(target) ? [] : {};
    6. for (let prop in target) {
    7. //只拷贝自身属性,不拷贝继承属性,所以使用hasOwnProperty(),当属性是继承属性则返回false
    8. if (target.hasOwnProperty(prop)) {
    9. cloneTarget[prop] = target[prop];
    10. }
    11. }
    12. return cloneTarget;
    13. } else {
    14. return target;
    15. }
    16. };

    三、深拷贝实现方法

    1. JSON.stringify() 实现深拷贝

    JSON.parse(JSON.stringify(target)) 
    

    缺点:
    1:拷贝的对象中如果存在undefined,function,symbol这几种类型,经过JSON.stringify()序列化后的字符串的这几个键会消失。
    2: 拷贝Date引用类型会变成字符串
    3: 无法拷贝不可枚举属性
    4: 无法拷贝对象的原型链
    5: 拷贝RexExp引用类型会变成空对象
    6: 含有NaN,Infinity,-Infinity 经过JSON序列化后会变成null
    7: 无法拷贝对象循环引用,记对象成环。

    2.递归实现深拷贝

    1. function deepCopy(target) {
    2. if((typeof target !== 'object' || typeof target !== 'function') && target === null) return false
    3. let res = Array.isArray(target) ? [] : {}
    4. for(let k in target) {
    5. // 如果目标数据上有属性(键)(key)
    6. if(target.hasOwnProperty(k)) {
    7. // 如果目标数据上属性的值,为object,就递归,不是object,就取到属性值,并放入我们新建的空数组/对象中
    8. res[k] = typeof target[k] === 'object' ? deepCopy(target[k]) : target[k]
    9. }
    10. }
    11. return res
    12. }

    测试下

    1. // test用例1: null
    2. var a = null // false
    3. // test用例2: 不可枚举属性
    4. var b = {name:'张三',like:['girl','cat']}
    5. Object.defineProperty(b,'age',{
    6. value: '18不可枚举属性',
    7. enumerable: false
    8. })
    9. for(let prop in b){
    10. console.log(prop)
    11. }
    12. /*
    13. 'name'
    14. 'like'
    15. */
    16. // test用例3: 非数组、对象的引用类型
    17. var c = new Date()
    18. var d = deepCopy(c)
    19. console.log(d)
    20. /*
    21. {}
    22. */
    23. // test用例4:
    24. const e = {}
    25. e.e = e
    26. deepClone(e)
    27. /*
    28. 出现内存泄漏
    29. */
    30. // test用例5: 对象、数组深层嵌套
    31. var i = {rep:'apple',like:['women',{boy:{name:'王五',like:[69,'sex']}}]}
    32. // 可以

    通过测试可以看出

    缺点:
    1: 不可复制不可枚举属性以及symbol类型
    2:只针对普通的引用类型做递归
    3:没有解决对象成环

    3.使用WeakMap解决对象成环

    1. const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && typeof target !== null
    2. function deepClone(target,map = new WeakMap()) {
    3. //当weakmap 中已经存在这个对象,直接返回即可,不用在进行拷贝
    4. if(map.get(target)) return target
    5. if(!isObject(target)) return target
    6. // 没有存在 就在weakmap中添加
    7. map.set(target,true)
    8. let res = Array.isArray(target) ? [] : {}
    9. for(let k in target) {
    10. if(target.hasOwnProperty(k)) {
    11. res[k] = typeof target[k] === 'object' ? deepClone(target[k],map) : target[k]
    12. }
    13. }
    14. return res
    15. }

    使用weakmap的原因是 weakmap并不会给对象增加引用次数,即对象可以被垃圾回收机制清除,不会占据内存,造成浪费性能。

    四、对非一般引用类型的拷贝方法

    由于其他类型较多,所以我们可以将这些类型分类后拷贝,首先看一下lodash 枚举出的一些类型。
     

    按照类型分为可遍历类型和不可遍历类型。可遍历类型比如Set和Map等,不可遍历类型为RegExp和Date等。

    扩展:获取数据类型

    typeof:能准确判断基本数据类型,但一般复杂数据类型无法判断

    instanceof:能准确判断复杂数据类型,但基本数据类型不行

    Object.property.toString.call:全部可以

    现在,假定我们封装好了isObject,isSet,isDate,等

    1.拷贝Set

    1. function deepClone (val) {
    2. if (isSet(val)) {
    3. const set = new Set()
    4. val.forEach(item => {
    5. set.add(deepClone(item))
    6. })
    7. }
    8. }

    Map类型和Set类型类似,所以不再重复说明。

    2.拷贝正则和Date

    1. function deepClone (val) {
    2. const Ctor = val.constructor
    3. if (isDate(val)) {
    4. return new Ctor(+val)
    5. } else if (isRegExp(val)) {
    6. const reFlags = /\w*$/;
    7. // 此处不用flags的原因在于flags方法返回的修饰符是按照字母顺序排列的
    8. const reg = new Ctor(val.source, reFlags.exec(val))
    9. reg.lastIndex = val.lastIndex
    10. return reg
    11. }
    12. }

    其他不可遍历类型类似,每个不可遍历类型有自己的特性,抓住数据类型的特性进行克隆就行。

    3.拷贝函数

    lodash对函数的处理为直接返回,这点根据函数的特性也能理解,克隆函数实际并无意义。如果必须实现的话需要考虑箭头函数和普通函数,箭头函数是没有原型的。克隆箭头函数比较简单,直接调用函数的toString方法,然后eval解析即可,普通函数需要正则匹配函数体,再通过new Function生成。

     总结给大家推荐一个实用面试题库

     1、前端面试题库 (面试必备)            推荐:★★★★★

    地址:前端面试题库

    2、前端技术导航大全      推荐:★★★★★

    地址:前端技术导航大全

    3、开发者颜色值转换工具   推荐:★★★★★

    地址 :开发者颜色值转换工具

  • 相关阅读:
    图片批量压缩工具哪个好用?这3个工具可以解决你的压缩烦恼
    上手阿里低代码引擎lowcode-engine
    什么是埃及COC认证?埃及COC认证是什么意思?
    BGP的拓展特性
    Windows 10 也能安装Kafka?这篇教程让你轻松掌握!
    代码随想录训练营第41天|LeetCode 343. 整数拆分、 96.不同的二叉搜索树
    Flink入门 (二)--Flink程序的编写
    【代码随想录】栈与队列专栏(java版本)
    论文解读(CBL)《CNN-Based Broad Learning for Cross-Domain Emotion Classification》
    【Java中23种面试常考的设计模式之适配器模式(Adapter)---结构型模式】
  • 原文地址:https://blog.csdn.net/weixin_42981560/article/details/126584886