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

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

一、面试官灵魂三连问:

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

二、浅拷贝方法

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

1.Object.assign()

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

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

对象的每一个属性都有一个描述对象,用来描述和控制该对象的属性行为

使用Object.getOwnPropertyDescriptor() 方法来获取描述对象

通过Object.definedProperty() 来设置-----是不是很熟悉,这是vue中数据绑定的方法

例如

let obj = {
    name : '123'
}
console.log(Object.getOwnPropertyDescriptor(obj,'name'))
/*
  输出内容
  configurable:true 能否通过delete删除此属性
  enumerable : true  表示属性是可以枚举 即是否通过for in 或 Object.keys() 返回属性
  value: '123'
  writable: true 表示能否修改属性的值
*/
// 设置属性
Object.defineProperty(obj,'名字',{
    value:'不可枚举属性',
    enumerable:false
})

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

// 方法一:看颜色
console.log(obj)
/*
  输出:
  {
    name: "123" // 控制台里是深色字体
    名字: "不可枚举属性" // 控制台里是浅色字体
  }
*/
// 方法二:使用以下四个方法
/*
  四个操作会忽略enumerable为false
  for in 
  Object.keys()
  Object.assign()
  JSON.stringify()
*/
// 我们实测下
for(let prop in obj){
  console.log(prop)
}
/*
  输出:name
*/
console.log(Object.keys(obj))
/*
  输出: ['name']
*/
console.log(Object.assign({},obj))
/*
  输出: {name: '123'}
*/
console.log(JSON.stringify(obj))
/*
  输出: '{"name":"123"}'
*

2.扩展运算符方式

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

3.concat拷贝

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

4.slice拷贝数组

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

手动实现浅拷贝

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

三、深拷贝实现方法

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.递归实现深拷贝

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

测试下

// test用例1: null
var a = null // false
// test用例2: 不可枚举属性
var b = {name:'张三',like:['girl','cat']}
Object.defineProperty(b,'age',{
    value: '18不可枚举属性',
    enumerable: false
})
for(let prop in b){
  console.log(prop)
}
/*
'name'
'like'
*/
// test用例3: 非数组、对象的引用类型
var c = new Date()
var d = deepCopy(c) 
console.log(d)
/*
  {}
*/
// test用例4: 
  const e = {}
  e.e = e
  deepClone(e)
/*
  出现内存泄漏
*/
// test用例5: 对象、数组深层嵌套
var i = {rep:'apple',like:['women',{boy:{name:'王五',like:[69,'sex']}}]}
// 可以

通过测试可以看出

缺点:

1: 不可复制不可枚举属性以及symbol类型

2:只针对普通的引用类型做递归

3:没有解决对象成环

3.使用WeakMap解决对象成环

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

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

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

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

 

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

扩展:获取数据类型

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

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

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

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

1.拷贝Set

  function deepClone (val) {
      if (isSet(val)) {
        const set = new Set()
        val.forEach(item => {
          set.add(deepClone(item))
        })
      }
  }

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

2.拷贝正则和Date

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

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

3.拷贝函数

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

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

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

地址:前端面试题库

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

地址:前端技术导航大全

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

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

相关文章
|
8天前
|
存储 缓存 安全
只会“有序无序”?面试官嫌弃的List、Set、Map回答!
小米,一位热衷于技术分享的程序员,通过与朋友小林的对话,详细解析了Java面试中常见的List、Set、Map三者之间的区别,不仅涵盖了它们的基本特性,还深入探讨了各自的实现原理及应用场景,帮助面试者更好地准备相关问题。
45 20
|
4月前
|
Java
【Java集合类面试二十三】、List和Set有什么区别?
List和Set的主要区别在于List是一个有序且允许元素重复的集合,而Set是一个无序且元素不重复的集合。
|
1月前
|
SQL Oracle 关系型数据库
[Oracle]面试官:你举例几个内置函数,并且说说如何使用内置函数作正则匹配
本文介绍了多种SQL内置函数,包括单行函数、非空判断函数、日期函数和正则表达式相关函数。每种函数都有详细的参数说明和使用示例,帮助读者更好地理解和应用这些函数。文章强调了字符串操作、数值处理、日期计算和正则表达式的使用方法,并提供了丰富的示例代码。作者建议读者通过自测来巩固学习成果。
22 1
[Oracle]面试官:你举例几个内置函数,并且说说如何使用内置函数作正则匹配
|
1月前
|
Java Python
gc模块的set_threshold函数
gc模块的set_threshold函数
|
4月前
|
机器学习/深度学习
【机器学习】如何判断函数凸或非凸?(面试回答)
文章介绍了如何判断函数是凸函数还是非凸函数,包括凸函数的定义、几何意义、判定方法(一元函数通过二阶导数判断,多元函数通过Hessian矩阵的正定性判断),以及凸优化的概念和一些经典的凸优化问题。
245 1
【机器学习】如何判断函数凸或非凸?(面试回答)
|
4月前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。
|
4月前
|
JavaScript
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
这篇文章解释了为什么在Vue中组件的`data`属性必须是一个函数而不是一个对象。原因在于组件可能会有多个实例,如果`data`是一个对象,那么这些实例将会共享同一个`data`对象,导致数据污染。而当`data`是一个函数时,每次创建组件实例都会返回一个新的`data`对象,从而确保了数据的隔离。文章通过示例和源码分析,展示了Vue初始化`data`的过程和组件选项合并的原理,最终得出结论:根实例的`data`可以是对象或函数,而组件实例的`data`必须为函数。
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
|
5月前
|
存储 JSON 关系型数据库
mysql中find_in_set()函数用法详解及增强函数
总结而言,`FIND_IN_SET()`是MySQL中处理由逗号分隔的字符串列表的一种便捷方法,尤其适用于列表相对较短且不经常更改的场景。然而,对于更为复杂的需要高性能和可扩展性的数据库设计,它可能不是最优选择,应考虑使用更加正规化的数据库结构。
705 2
mysql中find_in_set()函数用法详解及增强函数
|
5月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
62 8
|
5月前
|
Android开发 Kotlin
Android面试题之kotlin中怎么限制一个函数参数的取值范围和取值类型等
在Kotlin中,限制函数参数可通过类型系统、泛型、条件检查、数据类、密封类和注解实现。例如,使用枚举限制参数为特定值,泛型约束确保参数为Number子类,条件检查如`require`确保参数在特定范围内,数据类封装可添加验证,密封类限制为一组预定义值,注解结合第三方库如Bean Validation进行校验。
86 6