第五节:补充引用类型赋值、浅拷贝、深拷贝、判断中的隐式转化

一. 引用类型赋值

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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2021-09-13 11:30  Yaopengfei  阅读(159)  评论(6编辑  收藏  举报