赋值与深浅拷贝
JavaScript
中存在两大数据类型:
- 基本类型,数据保存在在
栈内存
中- 引用类型,数据保存在
堆内存
中,引用类型的变量是一个指向堆内存中实际对象的引用(指针
),存在栈
中
赋值
赋值是将一个变量赋给另一个变量的操作
因为 JavaScript
中的两大数据类型,所以赋值也分为两类:
1、基本类型:赋值,赋值之后两个变量互不影响
2、引用类型:赋址,实际上是把一个变量的地址赋给另一个变量,两个变量具有相同的引用,指向堆内存中同一个对象,修改其中一个变量的值,相互之间有影响
// 基本类型两个值不会互相影响
let a = "test"
let b = a
b = "change"
console.log(a, b) // test change
// 引用类型的两个值会互相影响
let c = { name: "test" }
let d = c
d.name = "change"
console.log(c, d) // { name: "change" } { name: "change" }
let e = [0, 1, 2]
let f = e
f[0] = 4
console.log(e, f) // [4,1,2]
// 基本类型两个值不会互相影响
let a = "test"
let b = a
b = "change"
console.log(a, b) // test change
// 引用类型的两个值会互相影响
let c = { name: "test" }
let d = c
d.name = "change"
console.log(c, d) // { name: "change" } { name: "change" }
let e = [0, 1, 2]
let f = e
f[0] = 4
console.log(e, f) // [4,1,2]
很多时候,我们想要的结果是两个初始值相同的变量互不影响,由上可知我们其实只需要处理引用类型
的变量
所以就要使用到拷贝(分为深浅两种)来处理引用类型
深浅拷贝定义
综上,让我们整理一下深浅拷贝的定义:
基本类型没有深浅拷贝的概念
,从内存堆栈
的角度来说,深浅拷贝只针对引用类型
浅拷贝
浅拷贝,拷贝一个引用类型
的基本类型
, 该引用类型
中的引用类型
则仍是相同引用
修改浅拷贝后的引用类型
中的基本类型
,不会影响原引用类型
,修改浅拷贝后的引用类型
中的引用类型
,则会影响原引用类型
什么意思?举个例子,下方obj对象是一个引用类型
,其基本类型
为name
,num
,其引用类型
为fisrtObj,testArr
浅拷贝后修改name
,num
基本类型不会影响原变量,而修改fisrtObj
,testArr
引用类型就会影响obj对象
const obj = {
name: "name",
num:0,
fisrtObj: {
name: "name"
},
testArr:[1,2,3]
}
const obj = {
name: "name",
num:0,
fisrtObj: {
name: "name"
},
testArr:[1,2,3]
}
对象浅拷贝
分别使用
- 遍历对象属性与值来新建对象
- Object.assign
- 拓展运算符
遍历对象属性与值来新建对象
实现一个方法
function shallowClone(obj) {
const newObj = {}
for(let prop in obj) {
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop]
}
}
return newObj
}
function shallowClone(obj) {
const newObj = {}
for(let prop in obj) {
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop]
}
}
return newObj
}
由输出结果,可知只是拷贝基本类型
,引用类型则仍是相同引用
,属于浅拷贝
const obj = {
name: "name",
testObj: {
name: "name"
}
}
const cloneObj = shallowClone(obj)
cloneObj.name = "test"
cloneObj.testObj.name = "test"
console.log(obj, cloneObj)
// 打印结果:
// { {
// name: "name", name: "test",
// testObj: { testObj: {
// name: "test" name: "test"
// } }
// } }
const obj = {
name: "name",
testObj: {
name: "name"
}
}
const cloneObj = shallowClone(obj)
cloneObj.name = "test"
cloneObj.testObj.name = "test"
console.log(obj, cloneObj)
// 打印结果:
// { {
// name: "name", name: "test",
// testObj: { testObj: {
// name: "test" name: "test"
// } }
// } }
Object.assign
由输出结果,可知只是拷贝基本类型
,引用类型则仍是相同引用
,属于浅拷贝
const obj = {
num: 0,
arr: ["one", "two"],
}
const cloneObj = Object.assign({}, obj)
cloneObj.num = 10
cloneObj.arr[0] = "test"
console.log(obj, cloneObj)
// 打印结果:
// { {
// num: 0, num: 10,
// arr: ["test", "two"] arr: ["test", "two"]
// } }
const obj = {
num: 0,
arr: ["one", "two"],
}
const cloneObj = Object.assign({}, obj)
cloneObj.num = 10
cloneObj.arr[0] = "test"
console.log(obj, cloneObj)
// 打印结果:
// { {
// num: 0, num: 10,
// arr: ["test", "two"] arr: ["test", "two"]
// } }
解构赋值
解构赋值属于赋值
的概念,不属于深浅拷贝
的概念
由输出结果,可知即使是基本类型
的修改,也会影响原变量,因为没有拷贝原引用类型
,所以不属于深浅拷贝
const obj = {
testObj: {
name: "name"
}
}
const { testObj } = obj
testObj.name = "test"
console.log(obj, testObj)
// 打印结果:
// {
// testObj: { {
// name: "test" name: "test"
// } }
// }
const obj = {
testObj: {
name: "name"
}
}
const { testObj } = obj
testObj.name = "test"
console.log(obj, testObj)
// 打印结果:
// {
// testObj: { {
// name: "test" name: "test"
// } }
// }
拓展运算符
由输出结果,可知只是拷贝基本类型
,引用类型则仍是相同引用
,属于浅拷贝
const obj = {
testObj: {
name: "name",
arr: [1, 2, 3]
}
}
// 属于赋值,不属于深浅拷贝
const { testObj } = obj
testObj.name = "test"
testObj.arr[0] = [4]
// 拓展运算符,属于浅拷贝
const cloneObj = { ...testObj }
cloneObj.name = "last" // 修改基本类型不会影响原引用类型
cloneObj.arr[1] = [5] // 浅拷贝修改引用类型仍是相同引用
console.log(obj, testObj, cloneObj)
// 打印结果:
// {
// testObj: { { {
// name: "test", name: "test", name: "last",
// arr: [4, 5, 3] arr: [4, 5, 3] arr: [4, 5, 3]
// } } }
// }
const obj = {
testObj: {
name: "name",
arr: [1, 2, 3]
}
}
// 属于赋值,不属于深浅拷贝
const { testObj } = obj
testObj.name = "test"
testObj.arr[0] = [4]
// 拓展运算符,属于浅拷贝
const cloneObj = { ...testObj }
cloneObj.name = "last" // 修改基本类型不会影响原引用类型
cloneObj.arr[1] = [5] // 浅拷贝修改引用类型仍是相同引用
console.log(obj, testObj, cloneObj)
// 打印结果:
// {
// testObj: { { {
// name: "test", name: "test", name: "last",
// arr: [4, 5, 3] arr: [4, 5, 3] arr: [4, 5, 3]
// } } }
// }
数组浅拷贝
分别使用
slice
,concat
,拓展运算符
slice
由输出结果,可知只是拷贝基本类型
,引用类型则仍是相同引用
,属于浅拷贝
const arr = [{ name: "name" }, "one", "two"]
const cloneArr = arr.slice(0)
cloneArr[0].name = "test"
cloneArr[1] = "three"
console.log(arr, cloneArr) // [{ name: "test" }, "one", "two"] [{ name: "test" }, "three", "two"]
const arr = [{ name: "name" }, "one", "two"]
const cloneArr = arr.slice(0)
cloneArr[0].name = "test"
cloneArr[1] = "three"
console.log(arr, cloneArr) // [{ name: "test" }, "one", "two"] [{ name: "test" }, "three", "two"]
concat
由输出结果,可知只是拷贝基本类型
,引用类型则仍是相同引用
,属于浅拷贝
const arr = [{ name: "name" }, "one", "two"]
const cloneArr = arr.concat([])
cloneArr[0].name = "test"
cloneArr[1] = "three"
console.log(arr, cloneArr) // [{ name: "test" }, "one", "two"] [{ name: "test" }, "three", "two"]
const arr = [{ name: "name" }, "one", "two"]
const cloneArr = arr.concat([])
cloneArr[0].name = "test"
cloneArr[1] = "three"
console.log(arr, cloneArr) // [{ name: "test" }, "one", "two"] [{ name: "test" }, "three", "two"]
拓展运算符
由输出结果,可知只是拷贝基本类型
,引用类型则仍是相同引用
,属于浅拷贝
const arr = [{ name: "name" }, "one", "two"]
const cloneArr = [...arr]
cloneArr[0].name = "test"
cloneArr[1] = "three"
console.log(arr, cloneArr) // [{ name: "test" }, "one", "two"] [{ name: "test" }, "three", "two"]
const arr = [{ name: "name" }, "one", "two"]
const cloneArr = [...arr]
cloneArr[0].name = "test"
cloneArr[1] = "three"
console.log(arr, cloneArr) // [{ name: "test" }, "one", "two"] [{ name: "test" }, "three", "two"]
深拷贝
深拷贝完全拷贝一个引用类型
,不但拷贝了这个引用类型
中的所有基本类型
,还为这个引用类型
中的所有的引用类型
开辟新栈新堆新指针
所以修改一个深拷贝后的引用类型
的属性或元素,不会影响原引用类型
的属性或元素,无论是修改其属性或元素中的基本类型
还是引用类型
常用的深拷贝方法
- JSON.parse(JSON.stringify())
- 循环递归
JSON.parse(JSON.stringify())
const obj2= JSON.parse(JSON.stringify(obj1))
const obj2= JSON.parse(JSON.stringify(obj1))
JSON.stringify()
:用于将JavaScript值转换为JSON字符串 JSON.parse()
:用于将一个JSON字符串转换为JavaScript对象
但是这种方式存在局限性,会忽略undefined
、symbol
和函数
const obj = {
name: 'A',
name1: undefined,
name3: function() {},
name4: Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2) // {name: "A"}
const obj = {
name: 'A',
name1: undefined,
name3: function() {},
name4: Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2) // {name: "A"}
循环递归
写法不是固定的,自行写得出一种即可
function deepClone(source) {
const targetObj = source.constructor === Array ? [] : {} // 判断复制的目标是数组还是对象
for (let keys in source) {
// 遍历目标
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
// 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {}
targetObj[keys] = deepClone(source[keys])
} else {
// 如果不是,就直接赋值
targetObj[keys] = source[keys]
}
}
}
return targetObj
}
const obj = {
num: 0,
testObj: {
name: "name"
}
}
const shallowobj = obj
const deepobj = deepClone(obj)
// 浅拷贝
shallowobj.num = 1
shallowobj.testObj.name = "test"
// 深拷贝
deepobj.num = 2
deepobj.testObj.name = "last"
console.log(obj)
console.log(shallowobj)
console.log(deepobj)
// 打印结果:
// {
// num:1, num:1, num:2
// testObj: { { {
// name: "test", name: "test", name: "last",
// } } }
// }
function deepClone(source) {
const targetObj = source.constructor === Array ? [] : {} // 判断复制的目标是数组还是对象
for (let keys in source) {
// 遍历目标
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
// 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {}
targetObj[keys] = deepClone(source[keys])
} else {
// 如果不是,就直接赋值
targetObj[keys] = source[keys]
}
}
}
return targetObj
}
const obj = {
num: 0,
testObj: {
name: "name"
}
}
const shallowobj = obj
const deepobj = deepClone(obj)
// 浅拷贝
shallowobj.num = 1
shallowobj.testObj.name = "test"
// 深拷贝
deepobj.num = 2
deepobj.testObj.name = "last"
console.log(obj)
console.log(shallowobj)
console.log(deepobj)
// 打印结果:
// {
// num:1, num:1, num:2
// testObj: { { {
// name: "test", name: "test", name: "last",
// } } }
// }
// 深度克隆
export function deepClone(obj) {
// 对常见的“非”值,直接返回原来值
if ([null, undefined, NaN, false].includes(obj)) return obj;
if (typeof obj !== "object" && typeof obj !== 'function') {
//原始类型直接返回
return obj;
}
var o = isArray(obj) ? [] : {};
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
}
}
return o;
}
// 深度克隆
export function deepClone(obj) {
// 对常见的“非”值,直接返回原来值
if ([null, undefined, NaN, false].includes(obj)) return obj;
if (typeof obj !== "object" && typeof obj !== 'function') {
//原始类型直接返回
return obj;
}
var o = isArray(obj) ? [] : {};
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
}
}
return o;
}
深浅拷贝区别
深浅拷贝都拷贝了引用类型
中基本类型
,深浅拷贝后,修改引用类型
中的基本类型
,都不影响原引用类型
中的基本类型
区别在于:
是否拷贝了引用类型
中的引用类型
也就是修改拷贝后的引用类型
中的引用类型
的值,是否会影响原引用类型
中的引用类型
的值
问题
加深印象,试着回答下问题吧
提问1,什么是深浅拷贝?
都用于拷贝引用类型
浅拷贝是拷贝了一层
, 拷贝了引用类型
的 基本类型
,而引用类型
中的引用类型
仍是相同引用
深拷贝则是完全拷贝,即拷贝了引用类型
的 基本类型
,也拷贝引用类型
中的引用类型
(即开辟新堆新栈新指针
)
提问2,为什么需要深浅拷贝?
因为JavaScript
中引用类型的数据类型,数据保存在堆内存
中,其变量是一个指向堆内存中实际对象的引用
(指针),存在栈
中
所以引用类型的赋值操作,实际上是赋址
,是把一个变量的地址赋给另一个变量,两个变量具有相同的引用,指向堆内存中同一个对象,修改其中一个变量的值,相互之间有影响
而很多时候,我们想要的结果是两个初始值相同的变量互不影响,需要处理引用类型
的变量
所以就要使用到拷贝(分为深浅两种)来处理引用类型
提问3.解构赋值属于深浅拷贝嘛?
不属于,属于赋值概念,既不属于浅拷贝,也不属于深拷贝
提问4.常见的浅拷贝对象有哪些方法?
- 遍历对象属性与值来新建对象
- Object.assign
- 拓展运算符
提问4.常见的浅拷贝数组有哪些方法?
- slice
- concat
- 拓展运算符
提问5.手写一个循环递归深拷贝
是否能手写出来,不行的话再去上方背一背吧