我们都知道JavaScript数据类型分为基本数据类型(String、Number、Boolean、Null、Undefined、Symbol,未来还会有BigInt)和引用数据类型(Object),当然Object还包括Date、function、Array、RegExp。
基本数据类型和引用数据类型在存储方式上是有很大差别的。
下面我们用代码和几张图来展示他们之间的差异:
let a = 1
let b = a
console.log(b) // 1
b = 2 // 改变变量 b ,变量 a 不变
console.log(a) // 1
console.log(b) // 2
const person1 = {
name: 'king',
age: 25,
}
const person2 = person1
person2.name = 'maoxiaoxing'
console.log(person2) // { name: 'maoxiaoxing', age: 25 }
console.log(person1) // { name: 'maoxiaoxing', age: 25 }
下面我们通过一段代码来看看区别:
// 赋值操作
const _ = require("lodash") // 引入lodash
const person1 = {
name: 'king',
age: 25,
hobby: ['篮球'],
}
const person2 = person1
person2.name = 'maoxiaoxing'
person2.hobby.push('读书')
console.log(person2) // { name: 'maoxiaoxing', age: 25, hobby: [ '篮球', '读书' ] }
console.log(person1) // { name: 'maoxiaoxing', age: 25, hobby: [ '篮球', '读书' ] }
上面的代码,我们可以看到我们无论是改变 person2 中的任何数据类型的属性值,person1 都会跟随发生变化。
下面我们再来看看浅拷贝
// 浅拷贝
const _ = require("lodash") // 引入lodash
const person1 = {
name: 'king',
age: 25,
hobby: ['篮球'],
}
const person3 = _.clone(person1) // lodash函数库中 clone 是一个浅拷贝方法
person3.name = 'maoxiaoxing'
person3.hobby.push('读书')
console.log(person1) // { name: 'king', age: 25, hobby: [ '篮球', '读书' ] }
console.log(person3) // { name: 'maoxiaoxing', age: 25, hobby: [ '篮球', '读书' ] }
可以看到,改变 person3 中的基本数据类型,原始数据中对象的属性是不会发生改变的,而 改变 person3 中的引用数据类型后,原始数据也会发生改变。
我们再来看看深拷贝
const _ = require("lodash") // 引入lodash
const person1 = {
name: 'king',
age: 25,
hobby: ['篮球'],
}
const person4 = _.cloneDeep(person1)
person4.name = 'maoxiaoxing'
person4.hobby.push('读书')
console.log(person1) // { name: 'king', age: 25, hobby: [ '篮球' ] }
console.log(person4) // { name: 'maoxiaoxing', age: 25, hobby: [ '篮球', '读书' ] }
可以看到,经过深拷贝后的新对象person4,无论是改变基本数据类型还是引用数据类型的属性,都不会影响到原始对象。
function oneCopy(obj) {
const resObj = Array.isArray(obj) ? [] : {}
for (let i in obj) {
resObj[i] = obj[i]
}
return resObj
}
const person1 = {
name: 'king',
age: 25,
hobby: ['篮球'],
}
const person5 = oneCopy(person1)
person5.name = 'maoxiaoxing'
person5.hobby.push('读书')
console.log(person1) // { name: 'king', age: 25, hobby: [ '篮球', '读书' ] }
console.log(person5) // { name: 'maoxiaoxing', age: 25, hobby: [ '篮球', '读书' ] }
const person1 = {
name: 'king',
age: 25,
hobby: ['篮球'],
}
const person6 = Object.assign({}, person1)
person6.name = 'maoxiaoxing'
person6.hobby.push('读书')
console.log(person1) // { name: 'king', age: 25, hobby: [ '篮球', '读书' ] }
console.log(person6) // { name: 'maoxiaoxing', age: 25, hobby: [ '篮球', '读书' ] }
const person1 = {
name: 'king',
age: 25,
hobby: ['篮球'],
}
const person7 = {...person1}
person7.name = 'maoxiaoxing'
person7.hobby.push('读书')
console.log(person1) // { name: 'king', age: 25, hobby: [ '篮球', '读书' ] }
console.log(person7) // { name: 'maoxiaoxing', age: 25, hobby: [ '篮球', '读书' ] }
还有很多,我就不一一列举了,有兴趣的大家可以去挖掘一下哪些api是有浅拷贝功能的
const obj1 = {
a: 1,
b: [1, 2]
}
const obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a = 2
obj2.b.push(3)
console.log(obj1) // { a: 1, b: [ 1, 2 ] }
console.log(obj2) // { a: 2, b: [ 1, 2, 3 ] }
JSON.stringify 虽然可以实现深拷贝,但是却不能处理函数和正则,而且当对象中有循环引用会报错
像下面这样
const obj1 = {
a: 1,
b: [1, 2],
c: obj1 // 循环引用
}
const obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a = 2
obj2.b.push(3)
console.log(obj1) // TypeError: Converting circular structure to JSON
console.log(obj2)
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
const obj1 = {
a: 1,
b: [1, 2],
}
obj1.c = obj1
const obj2 = deepClone(obj1)
obj2.a = 2
obj2.b.push(3)
console.log(obj1)
console.log(obj2)
当然这个深拷贝也是比较基础的,不能处理 Map、Set以及更加复杂的数据,如果感兴趣的话,可以自己去增加一个判断去处理