文章已同步至掘金:https://juejin.cn/post/6844903962424934407
欢迎访问😃,有任何问题都可留言评论哦~
数据类型 {#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B}
在进入正题之前,先说个数据类型
在JS中,数据类型分两种:基本数据类型、引用数据类型
基本数据类型 {#%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B}
JS中的基本数据类型有五种: null
、undefined
、boolean
、string
、number
。
数据变量是直接按值存放的,他们的值在内存中占据着固定大小的空间,并被保存在栈内存中,可以直接访问,并且是简单的数据段,其数据类型的值是不可变的。
var str = "xxx";
str[0] = "y";
console.log(str); // xxx
基本类型的比较是值的比较
只要它们的值相等就认为他们是相等的,例如:
var a = 1;
var b = 1;
console.log(a === b); // true
引用数据类型 {#%E5%BC%95%E7%94%A8%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B}
引用数据类型包括对象和数组,其存储在堆当中,而变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。 当我们访问的时候,实际上是访问指针,然后指针去寻找对象或数组,其数据类型的值是可变的。
var str = [1,2,3];
str[0] = 4;
console.log(str); // [4,2,3]
引用类型的比较是引用的比较
我们对 js 中的引用类型进行操作的时候,都是操作其对象的引用(保存在栈内存中的指针),所以比较两个引用类型,是看其的引用是否指向同一个对象。例如:
var a = [1,2,3];
var b = [1,2,3];
console.log(a === b); // false
浅拷贝与深拷贝的理解 {#%E6%B5%85%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B7%B1%E6%8B%B7%E8%B4%9D%E7%9A%84%E7%90%86%E8%A7%A3}
浅拷贝 {#%E6%B5%85%E6%8B%B7%E8%B4%9D}
只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;
简单的说,浅拷贝就是将一个对象的内存地址的""编号""复制给另一个对象。即在真正访问的时候还是会访问到被复制对象。 或者只是深拷贝了第一层的引用类型,而没有拷贝更深层次的应用类型,而是利用复制地址的方式,这也是浅拷贝。
深拷贝 {#%E6%B7%B1%E6%8B%B7%E8%B4%9D}
复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变。
简单的说,深拷贝就是先新建一个空对象,内存中新开辟一块地址,把被复制对象的所有可枚举的(注意可枚举的对象)属性方法一一复制过来,注意要用递归来复制子对象里面的所有属性和方法,直到子子...属性为基本数据类型。
关键点:开辟新内存、递归复制。
浅拷贝与深拷贝的实现方式 {#%E6%B5%85%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B7%B1%E6%8B%B7%E8%B4%9D%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F}
浅拷贝 {#%E6%B5%85%E6%8B%B7%E8%B4%9D-1}
var str1 = {
name:"Fan"
}
var str2 = str1
str2.name = "Jun"
console.log(str1.name); // Jun
这里首先创建了一个 str1
对象,然后将str1
复制给了 str2
, 但是这里仅仅是指针的复制,所以在修改 str2.name
的时候,实际上是修改的同一个堆中的对象,既浅拷贝。
var str = {
a:1,
b:{
d:"Fan"
},
c:[1,2,3]
}
function Test(obj){
var newStr = {};
for (var item in obj){
newStr[item] = obj[item];
}
return newStr;
}
var newStr = Test(str);
console.log(newStr.b.d === str.b.d); // true
这段代码是通过for in
的形式将对象进行复制,这里可以看到复制只是对于指针的复制,得到的新的对象还是指向同一个堆中的对象,所以是浅拷贝。
var str1 = {
name: 'Fan',
age: 22,
other: {
school: 'HuangHuai'
}
}
var str2 = Object.assign({}, str1);
str2.name = 'Jun'
console.log(str1.name) // Fan
`str2.other.school = 'ShiYan'
console.log(str1.other.school) //ShiYan
`
只从表面上来看,似乎Object.assign()
的目标对象是{ }
,是一个新的对象(开辟了一块新的内存空间),是深拷贝。
当我们修改str2.name
的时候,str1.name
没有改变,但是当我们修改 str2.other.school
的时候,str1.other.school
同样也发生了变化。
即Object.assign()
也是浅拷贝,或者说只是深拷贝了第一层,这样我们认为它还是浅拷贝。
var a = [1, [2, 3, 4], {
name: 'Fan'
}];
var b = a.concat(5)
a\[0\] = 6;
console.log(b\[0\]) // 1 看起来像深拷贝
a\[1\]\[0\] = 999;
console.log(b\[1\]\[0\]) // 999 浅拷贝
`a[2].name = 'Fan'
console.log(b[2].name) // Fan 浅拷贝
`
可以看到通过concat
返回的新数组,只有改变其中一个的布尔值、字符串、数值,另一个不会改变,但是改变其中的对象、数组时,可以发现,另一个也在同时改变,即还是引用原来的堆中的内容。
var a = [1, [2, 3, 4], {
name: 'Fan'
}];
var b = a.slice(0)
a\[0\] = 6;
console.log(b\[0\]) // 1 看起来像深拷贝
a\[1\]\[0\] = 999;
console.log(b\[1\]\[0\]) // 999 浅拷贝
a\[2\].name = 'Fan'
console.log(b\[2\].name) // Fan 浅拷贝
这段代码仅仅是将上一段中的concat
修改为了slice
,发现结果也是一样的,即slice
方法得到的也是浅拷贝。
深拷贝 {#%E6%B7%B1%E6%8B%B7%E8%B4%9D-1}
JSON.stringify() 和 JSON.parse()
var str1 = {
name: 'Fan',
age: 22,
other: {
school: 'HuangHuai'
}
}
var str2 = JSON.parse(JSON.stringify(str1));
str2.name = 'Jun'
console.log(str1.name) // Fan
`str2.other.school = 'ShiYan'
console.log(str1.other.school) // HuangHuai
`
可以看出通过JSON.stringify
先将对象转化为字符串,然后再通过JSON.parse()
转化为对象,这个对象就是完全在开辟的新的内存空间中的对象 。
虽然这种方式可以实现深拷贝,但是会存在不足,就是他只能拷贝符合JSON格式的数据,如果不是JSON格式的数据,则不行.
例如:
let obj = {name:"Fan",age:function(){}}
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj) // { name: 'Fan' }
使用递归实现深拷贝
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;
}
var str1 = {
arr: \[1, 2, 3\],
obj: {
key: 'value'
},
fn: function () {
return 1;
}
};
var str3 = deepClone(str1);
`console.log(str3 === str1); // false
console.log(str3.obj === str1.obj); // false
console.log(str3.fn === str1.fn); // true
`