第五节:补充引用类型赋值、浅拷贝、深拷贝、判断中的隐式转化
一. 引用类型赋值
1. 原理
(1). js中对象和数组都是引用类型。
(2). 内存分为两个区域,栈区域 和 堆区域。
(3). 引用类型,在栈区域中存放的堆的二进制地址,这个地址指向 堆区域中的实际值。
(4). 将一个对象A赋值给另一个对象B,实际上是将A的地址赋值给了B,A和B共同指向同一个对象区域,所以改了B中的属性,A中的也改了。
2. 代码实战
<script type="text/javascript"> //js中数组和对象都是引用类型 var user1 = { name: 'ypf1', age: 10, child: { cname: 'ep1' } }; var user2 = user1; user2.name='ypf2'; user2.child.cname='ep2'; console.log(user1.name); //ypf2 console.log(user1.child.cname); //ep2 </script>
剖析:
二. 浅拷贝和深拷贝
(相关文档参考:https://juejin.cn/post/6981272992923795486)
1. 浅拷贝
(1). 定义
对于基本数据类型,拷贝的是基本类型的值(即原值和新值不会相互影响);对于引用数据类型而言,拷贝是栈中的地址(即拷贝后的内容和原始内容指向同一个地址,修改值会相互影响)。
例如:user1中的child属性是个对象,即引用类型。 所以拷贝给user2中的child属性,拷贝的还是一个地址,所以改了cname,全改。
(2). 方案1--Object.assign
var user1 = { name: 'ypf1', age: 10, child: { cname: 'ep1' } }; //浅拷贝-方案1 { var user2 = Object.assign({}, user1); user2.name = 'ypf2'; // 对象中的对象,指向的还是地址,浅拷贝,拷贝过去的也是地址,改1个,就都改了 user2.child.cname = 'ep2'; console.log(user1.name); //ypf1 console.log(user1.child.cname); //ep2 }
(2). 方案2-展开运算符
var user1 = { name: 'ypf1', age: 10, child: { cname: 'ep1' } }; //浅拷贝-方案2 { var user2 = {...user1}; user2.name = 'ypf2'; // 对象中的对象,指向的还是地址,浅拷贝,拷贝过去的也是地址,改1个,就都改了 user2.child.cname = 'ep2'; console.log(user1.name); //ypf1 console.log(user1.child.cname); //ep2 }
(3). 方案3-lodash
引用: <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
var user1 = { name: 'ypf1', age: 10, child: { cname: 'ep1' } }; // 浅拷贝-方案3 { var user2 = _.clone(user1); user2.name = 'ypf2'; // 对象中的对象,指向的还是地址,浅拷贝,拷贝过去的也是地址,改1个,就都改了 user2.child.cname = 'ep2'; console.log(user1.name); //ypf1 console.log(user1.child.cname); //ep2 }
(4). 手写浅拷贝代码
核心:Object.keys获取的事对象的实例属性。 【Object.getOwnPropertyNames for-in 详解 https://www.cnblogs.com/yaopengfei/p/16471767.html 】
{
console.log("4. 手写浅拷贝代码");
function shallowCopy(obj) {
let newObj = {};
// 核心点:Object.keys获取对象的实例属性,不能获取原型属性或不可枚举的属性
Object.keys(obj).forEach(item => {
newObj[item] = obj[item];
});
return newObj;
}
// 测试
let obj1 = { name: "ypf", age: 18, children: { cName: 2 } };
let obj2 = shallowCopy(obj1);
obj1.name = "ypf2";
console.log(obj2.name); //ypf 不受影响
obj1.children.cName = 3;
console.log(obj2.children.cName); //3 受影响了
}
2. 深拷贝
(1). 说明
对于基本数据类型,拷贝的是基本类型的值(即原值和新值不会相互影响);对于引用数据类型而言,深拷贝是从内存中完整的拷贝出来一份,并且会在堆内存中开辟一个新的空间进行存储(即原值和新值不会相互影响)。
例如:user1中的child属性,拷贝给user2的时候,完整拷贝,开辟了新空间进行存储,所以user1和user2中修改任何内容,相互不影响。
(2). 方案1
var user1 = { name: 'ypf1', age: 10, child: { cname: 'ep1' } }; // 深拷贝-方案1 { var user2 = JSON.parse(JSON.stringify(user1)); user2.name = 'ypf2'; // 深拷贝,对象中的对象也全部copy了 user2.child.cname = 'ep2'; console.log(user1.name); //ypf1 console.log(user1.child.cname); //ep2 }
剖析弊端:
A. 无法对函数进行拷贝。
B. 破坏了原有的原型链。
C. 如果对象中存在循环引用,会直接报错。
{
console.log("3.JSON.parse(JSON.stringify())的弊端");
function Person(age) {
this.age = age;
}
let person = new Person(20);
let obj1 = {
name: "ypf",
getMsg() {
console.log(this.name);
},
pItem: person,
};
// 深拷贝
let obj2 = JSON.parse(JSON.stringify(obj1));
// 问题1:
console.log(obj2); //里面没有getMsg方法,没有拷贝成功
// 问题2
console.log(obj2.pItem.constructor); //[Function: Object] 指向了Object,导致原型链破坏, 应该指向 Person
console.log(obj1.pItem.constructor); //[Function: Person]
}
{
let obj1 = {
name: "ypf",
item: obj1, //循环引用
};
// 深拷贝
let obj2 = JSON.parse(JSON.stringify(obj1)); //直接报错
// 问题3
console.log(obj2.item);
}
(3). 方案2-lodash
引用 <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
var user1 = { name: 'ypf1', age: 10, child: { cname: 'ep1' } }; // 深拷贝-方案2 { var user2 = _.cloneDeep(user1); user2.name = 'ypf2'; // 对象中的对象,指向的还是地址,浅拷贝,拷贝过去的也是地址,改1个,就都改了 user2.child.cname = 'ep2'; console.log(user1.name); //ypf1 console.log(user1.child.cname); //ep2 }
补充:
上述两种深拷贝的方案,其中JSON.parse(JSON.stringify(xxx))方案无法深拷贝对象里的方法,深拷贝后的对象里将不存在方法。 借助lodash包里的cloneDeep方法进行的深拷贝可以拷贝对象里的方法,进行使用。
// 测试上述两种深拷贝方案对于-方法的处理 { var obj1 = { name: 'ypf1', age: 10, child: { cname: 'ep1' }, test() { console.log('我是test方法') } }; // 1. 使用cloneDeep方法可以拷贝对象里的方法 let obj2 = _.cloneDeep(obj1); obj2.test(); //2. 使用JSON.parse和JSON.stringify的方案拷贝不了对象里的方法 let obj3 = JSON.parse(JSON.stringify(obj1)); obj3.test(); //深拷贝后的obj3里没有test方法【obj3.test is not a function】 }
(4). 手写深拷贝代码
核心:采用递归的方式进行赋值,如果检测到对应的属性有嵌套,则递归调用,直到属性为基本数据类型为止。
{
console.log("5.手写深拷贝代码");
function deepCopy(obj) {
if (typeof obj === "object") {
let newObj = {};
// Object.keys获取对象的实例属性
Object.keys(obj).forEach(item => {
newObj[item] = deepCopy(obj[item]);
});
return newObj;
} else {
return obj;
}
}
// 测试
let obj1 = { name: "ypf", age: 18, children: { cName: 2 } };
let obj2 = deepCopy(obj1);
obj1.name = "ypf2";
console.log(obj2.name); //ypf 不受影响
obj1.children.cName = 3;
console.log(obj2.children.cName); //2 不受影响
}
三. 判断中的隐式转化
逻辑判断时, 可以转化的情况下, 会隐式的自动将一个string类型成一个number类型再来进行判断(隐式转化)
<script type="text/javascript"> const score = "100"; // 逻辑判断时, 可以转化的情况下, 会隐式的自动将一个string类型成一个number类型再来进行判断(隐式转化) if (score > 90) { console.log(typeof score); //转换完,后续的过程中,又变成string类型了 console.log("优秀"); } </script>
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】