关于js中深拷贝跟浅拷贝的一点总结

深拷贝 vs 浅拷贝

1.先总结

关于深拷贝跟浅拷贝的区别,以前看过各类博客、论坛对于这个问题的分析以及文章,总结出了一句很是 “精辟” 的解释:“浅拷贝只是拷贝对象或者数组一级属性上的基本类型,深拷贝是对对象以及对象的全部子对象进行拷贝”。(想了一下,仍是不太准确。。。)javascript

2.基本类型跟引用类型

先了解下基本数据类型跟引用类型的概念java

  • 基本类型:undefined,null,Boolean,String,Number,Symbol(基本类型:基本类型值在内存中占据固定大小,保存在栈内存中(不包含闭包中的变量))git

  • 引用类型:Object,Array,Date,Function,RegExp等(引用类型的值是对象,保存在堆内存中。而栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址(引用),引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中得到实体。)github

3.数组中的拷贝

3.1 slice

let arr = [1,2,3,4,5,6]
let copyArr = arr.slice(0,2)
copyArr[0] = 9
console.log(arr[0]) //1
console.log(copyArr[0])//9
复制代码

slice能够实现拷贝,若是是二维数组呢?数组

let arr = [1,[2,3],4,5]
let copyArr = arr.slice(0,2)
copyArr[1][0] = 9
console.log(arr[1][0]) //9
console.log(copyArr[1][0])//9
复制代码

因此slice是浅拷贝闭包

3.2 concat(方法用于链接两个或多个数组,不会改变原数组)

let arr = [1,[2,3],4,5]
let copyArr = arr.concat()
copyArr[1][0] = 9
console.log(arr[1][0]) //9
console.log(copyArr[1][0])//9
复制代码

concat 也属于浅拷贝函数

3.3 扩展运算符(...)

let arr1 = [1,[2,3],4]
let copyArr = [...arr1]
copyArr[1][0] = 9
console.log(arr1[1][0])//9
console.log(copyArr[1][0])//9
复制代码

扩展运算符也属于浅拷贝ui

4.对象上的拷贝

关于对象上的拷贝就不依次上代码了,得出结论是:this

浅拷贝:Object.assign()和扩展运算符spa

深拷贝:JSON.parse()JSON.stringify() 、经过递归

5.JSON方法有哪些缺点

5.1 Map、Set、Date、function、undefined等类型会丢失

let obj = {
    a: 1,
    printstr: function() {
        console.log(0)
    }
}
obj.date = new Date()
obj.reg = new RegExp(/^\d{3}\-\d{3,8}$/)
obj.b = undefined
obj.buffer = new ArrayBuffer(8)
let objCopy = JSON.parse(JSON.stringify(obj))
console.log(obj)
console.log(objCopy)
复制代码

1566465604_1_.png

SetMapRegExpArrayBuffer会被转为空对象,Functionundefined会被忽略

补充:Symbol()也会忽略

5.2 不能处理循环引用

const x = {}
const y = {x}
x.y = y // Cycle: x.y.x.y.x.y.x.y.x...
const copy = JSON.parse(JSON.stringify(x)) // throws!
复制代码

6.如何实现一个简单的深拷贝(主要是深拷贝对象与数组)

function deepClone(obj) {
    function isObject(o) {
        return (typeof o === 'object' || typeof o === 'function') && o !== null
    }
    if (!isObject(obj)) {
        throw new Error('非对象')
    }
    let isArray = Array.isArray(obj)
    let newObj = isArray ? [...obj] : { ...obj}
    Reflect.ownKeys(newObj).forEach(key => {
        newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    })
    return newObj
}
复制代码

非原创,以前保存的一个方法;这里主要是用isArray去判断ArrayObjectReflect.ownKeys方法获取对象上全部属性的key

这个方法实现的只是一个简单的深拷贝,考虑获得的状况比较少,若是项目中涉及到比较复杂的状况,能够考虑一些比较成熟的库,好比lodashUnderscore等;这些库对于Symbols、原型链上面的属性拷贝以及循环引用都有考虑到

参考文章

lodash是如何实现深拷贝的

7.关于数据类型的判断

由于深拷贝中涉及到类型的判断,这里总结了下js中数据类型的类型判断

7.1 typeofinstanceof

typeof:这个方法能够看做是一个js的bug,该方法不能区分数组、对象、正则、Date等类型,还有一个比较特殊的:Null,typeof null返回的也是object

instanceof:用于判断一个变量是否属于某个对象的实例,会依次从原型链上面去查找

let b = new Array()
alert(b instanceof Array)  // true
alert(b instanceof Object)  // true

// instanceof的检测类型必须是对象
let num = 1
num instanceof Number // false

num = new Number(1)
num instanceof Number // true
复制代码

ArrayDateRegExpFunction等数据类型的原型链上层都是Object

得出结论:上述两个方法都不能准确的判断数据类型

7.2 构造函数 consstructor

constructor是原型对象上的一个属性,默认指向这个原型的构造函数

let arr = [1]
let object = {}
let fun = function() {}
let set = new Set()
let date = new Date()
console.log(arr.constructor)
console.log(object.constructor)
console.log(fun.constructor)
console.log(set.constructor)
console.log(date.constructor)
复制代码

1566543331_1_.png

从上面的代码能够看出constructor能够判断引用类型的数据类型,但这个方法也有一些缺点:

(1)undefined和null是不可以判断出类型的

(2)constructor属性是能够被修改的,可能会致使判断不许确

7.3 Object.prototype.toString

toString是Object原型上面的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是toString运行时this指向的对象类型, 返回的类型

console.log(Object.prototype.toString.call("jerry"))//[object String]
console.log(Object.prototype.toString.call(12))//[object Number]
console.log(Object.prototype.toString.call(true))//[object Boolean]
console.log(Object.prototype.toString.call(undefined))//[object Undefined]
console.log(Object.prototype.toString.call(null))//[object Null]
console.log(Object.prototype.toString.call({name: "jerry"}))//[object Object]
console.log(Object.prototype.toString.call(function(){}))//[object Function]
console.log(Object.prototype.toString.call([]))//[object Array]
console.log(Object.prototype.toString.call(new Date))//[object Date]
console.log(Object.prototype.toString.call(/\d/))//[object RegExp]
function Person(){}
console.log(Object.prototype.toString.call(new Person))//[object Object]
复制代码

这个方法能够准确的判断js中任意数据的类型

最后

才疏学浅,写的比较简单,喜欢的记得点个哦~~

文中有哪些没有涉及到的,欢迎补充~~

相关文章
相关标签/搜索