
自己创建一个新对象,来接收你要重新复制或引用的对象值。如果对象属性是基本数据类型,复制的就是基本数据类型的值给新对象;如果属性是引用数据类型,复制的就是内存中的地址,如果一个对象改变了这个属性值,那么会影响到另一个对象。
对象的每一个属性都有一个描述对象,用来描述和控制该对象的属性行为
使用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"}'
- *
- let obj2 = {...obj1}
- let arr2 = [...arr1] //跟arr.slice()一样的效果
- let arr1 = [1,2,3]
- let arr2 = arr1.concat()
- 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;
- }
- };
JSON.parse(JSON.stringify(target))
缺点:
1:拷贝的对象中如果存在undefined,function,symbol这几种类型,经过JSON.stringify()序列化后的字符串的这几个键会消失。
2: 拷贝Date引用类型会变成字符串
3: 无法拷贝不可枚举属性
4: 无法拷贝对象的原型链
5: 拷贝RexExp引用类型会变成空对象
6: 含有NaN,Infinity,-Infinity 经过JSON序列化后会变成null
7: 无法拷贝对象循环引用,记对象成环。
- 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:没有解决对象成环
- 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,等
- function deepClone (val) {
- if (isSet(val)) {
- const set = new Set()
- val.forEach(item => {
- set.add(deepClone(item))
- })
- }
- }
Map类型和Set类型类似,所以不再重复说明。
- 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
- }
- }
其他不可遍历类型类似,每个不可遍历类型有自己的特性,抓住数据类型的特性进行克隆就行。
lodash对函数的处理为直接返回,这点根据函数的特性也能理解,克隆函数实际并无意义。如果必须实现的话需要考虑箭头函数和普通函数,箭头函数是没有原型的。克隆箭头函数比较简单,直接调用函数的toString方法,然后eval解析即可,普通函数需要正则匹配函数体,再通过new Function生成。
地址:前端面试题库
地址:前端技术导航大全
地址 :开发者颜色值转换工具