JavaScript中的对象复制

简介: JavaScript中的对象复制

所有的面向对象的语言中,都存在着对象引用、复制等等问题,对于初学者来说可能难以理解。今天我来总结一下JavaScript中对象复制。

首先我们要知道JavaScript中的数据分为基本类型(单类型)引用类型

除了Object对象,其余都是基本类型。数组、时间对象以及我们自定义的对象等等,都是继承自Object的,所以说都是引用类型。

在JavaScript中,基本类型有以下几种:

  • undefined 未定义
  • Boolean 布尔值
  • Number 数值
  • String 字符串
  • BigInt 长整型
  • Symbol ES6中新增的符号类型
  • null

我们可以使用typeof看一看各种常用类型:

console.log(typeofundefined);
console.log(typeofnull);
console.log(typeoftrue);
console.log(typeof1);
console.log(typeof'abc');
console.log(typeofSymbol('sym'));
// 函数functiona() {}
letb= () => {}
console.log(typeofa);
console.log(typeofb);
// 对象letobj= {};
console.log(typeofobj);

网络异常,图片无法展示
|

1,基本类型

在js中对对象进行赋值时,基本类型会被直接复制,例如下:

leta=1;
letb=a;
b=2;
console.log(a);
console.log(b);

效果:

网络异常,图片无法展示
|

可见ab都是number基本类型,直接赋值就把a复制给了b,改变b并不改变a

同样可以看这个:

functionch(num) {
num=num+10;
}
leta=1;
console.log(a);
ch(a);
console.log(a);

结果:

网络异常,图片无法展示
|

我们试图通过函数改变a的值,但是没有成功。因为把a作为形参传入后,形参会作为传入实参的一个副本,相当于先做了mun = a的操作,再num自己加10,函数结束,num被销毁,numa的复制品,num变了也不影响a

可见,基本数据类型赋值操作时,在内存中可以简单表示如下:

网络异常,图片无法展示
|

网络异常,图片无法展示
|

2,引用类型

下面建立一个对象,对其进行操作试试看:

// 构造一个角色对象letmiyako= {
name: '宫子',
age: '14',
race: 'ghost',
skills: [{
name: '把你变成布丁',
description: '对敌人造成伤害并回复自身生命值',
injury: 2000  }, {
name: '我~好~恨~啊~',
description: '变身为幽灵,在一段时间内进入无敌状态',
injury: 0  }, {
name: '点心时间到了',
description: '中幅回复自身生命值',
injury: 0  }, {
name: '透明妖怪来咯~',
description: '战斗开始时,中幅提升自身物理防御力',
injury: 0  }],
say: () => {
console.log('布丁布丁布丁布丁布丁~');
  }
}
// 新建一个对象,用原对象赋值letmiyakoHelloween=miyako;
// 修改新的对象miyakoHelloween.name='宫子(万圣节)';
miyakoHelloween.skills= [{
name: '不给布丁就捣蛋的说',
description: '对最远处的一名敌人造成物理伤害(大),并使其陷入诅咒状态',
injury: 10000}, {
name: '狼女孩来了的说',
description: '小幅降低最远处一名敌人的技能值,并使其眩晕',
injury: 0}, {
name: '幽灵木马的说',
description: '中幅降低最远处一名敌人的物理攻击力和魔法攻击力',
injury: 0}, {
name: '嗷呜嗷呜的说',
description: '战斗开始时,中幅提升自身的物理攻击力',
injury: 0}];
miyakoHelloween.say= () => {
console.log('不给布丁就捣蛋!');
}
// 分别输出原对象、新对象,并分别调用两者的方法console.log(miyako);
console.log(miyakoHelloween);
miyako.say();
miyakoHelloween.say();

这里我建立了个自定义对象miyako,然后新建变量miyakoHelloween并使用原对象给其赋值,改变新建变量,原变量会变吗?结果如下:

网络异常,图片无法展示
|

可见虽然只改变了新对象,但是原对象也被改了,和我们上面的试验结果不一样了,这是为什么呢?

因为我们自定义的对象属于Object类型,属于引用类型,直接赋值给别的对象时,只是发生了引用,相当于 miyakomiyakoHelloween两个名字指向了内存中同一空间。如果改变其中一个,另一个也会发生变化,简而言之,这两个名字实质上指向了同一个数据。

对于引用类型数据,在内存中可以简单表示如下:

网络异常,图片无法展示
|

网络异常,图片无法展示
|

这在js中属于浅复制,即我们把已有对象赋值给新对象时并没有给新对象在内存中开辟一个新空间,而是发生引用。

同样把这个对象作为形参传入一个函数并在函数中修改它,你会发现它也发生了改变。

3,再看=====

在JavaScript中我们都使用==或者===判断两个值是否相等。并且我们也知道,前者判断相等时会作数据类型的隐式转换,但是后者不会。

但是当我们把判断相等运算符用在引用类型数据上是什么情况呢?这时就是判断两者指向的地址是否相同了。并且在对引用类型的对象进行比较时,=====的作用是一样的。

leta=1;
letb=2;
letc=1;
lets='1';
leto= {
name: 'oo'}
letoo=o;
letooo= {
name: 'oo'}
console.log(a==b);
console.log(a==c);
console.log(a==s);
console.log(a===s);
console.log(o==oo);
console.log(o===oo);
console.log(o==ooo);
console.log(o===ooo);

结果:

网络异常,图片无法展示
|

可见总结起来如下:

  • 对于基本类型:
  • ==用于判断两者是否值相等,判断时会先进行隐式转换
  • ===用于判断两者是否全等,判断时直接比较不会进行隐式转换
  • 对于引用类型:=====作用相同,用于判断两者指向的内存空间是否是一样的

4,实现深复制

那么我们想要复制一个对象,即实现深复制,怎么做呢?

可以写一个函数,新建一个对象,并通过遍历递归方式对原对象的属性进行获取,把每个属性依次给新对象复制。如果属性是基本类型数据例如数值字符串等等,我们就可以直接赋值给新对象属性了,因为基本类型直接赋值就是相当于真正的复制了,如果是引用类型那就进行递归复制操作:

/** * 对一个对象进行深复制 * @param {*} originObject 原对象 * @returns 复制的对象 */functioncopyObject(originObject, map=newWeakMap()) {
// 如果传入对象是基本类型,则直接返回// null是一种特殊情况,它被判定为Object类型,但是实质上它是个基本类型// 并且,我们不对函数(Function)对象进行复制,因为深复制函数是没有意义的,并且递归复制函数对象只会得到一个空的函数if (originObject===null||typeoforiginObject!='object') {
returnoriginObject;
  }
// 否则,说明是引用类型// 我们使用map记录已经克隆过的变量,先检测map中是否记录了原对象,如果是说明这个传入对象已被复制过,直接返回if (map.has(originObject)) {
returnmap.get(originObject);
  }
// 获取原对象的构造函数,并用这个构造函数创建新的对象letdestObject=neworiginObject.constructor();
// 记录到map里面,表示这个对象已被复制,并传入递归调用,防止循环引用导致内存溢出map.set(originObject, destObject);
// 开始遍历对象的属性并执行递归复制for (letkeyinoriginObject) {
destObject[key] =copyObject(originObject[key], map);
  }
returndestObject;
}

来看一下这个代码,主要是使用了递归的思想。除此之外,我们还使用了一个WeakMap对象,用于记录已经克隆过的属性。否则,有的对象存在循环引用(对象其中有属性引用了自己本身),就会发生无限递归导致内存溢出。

WeakMap对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

然后我们对上面已经构造的对象进行试验:

// 新建一个对象,复制原对象letmiyakoHelloween=copyObject(miyako);
// 修改新的对象miyakoHelloween.name='宫子(万圣节)';
miyakoHelloween.skills= [{
name: '不给布丁就捣蛋的说',
description: '对最远处的一名敌人造成物理伤害(大),并使其陷入诅咒状态',
injury: 10000}, {
name: '狼女孩来了的说',
description: '小幅降低最远处一名敌人的技能值,并使其眩晕',
injury: 0}, {
name: '幽灵木马的说',
description: '中幅降低最远处一名敌人的物理攻击力和魔法攻击力',
injury: 0}, {
name: '嗷呜嗷呜的说',
description: '战斗开始时,中幅提升自身的物理攻击力',
injury: 0}];
miyakoHelloween.say= () => {
console.log('不给布丁就捣蛋!');
}
// 分别输出原对象、新对象,并分别调用两者的方法console.log(miyako);
console.log(miyakoHelloween);
miyako.say();
miyakoHelloween.say();

结果:

网络异常,图片无法展示
|

可见通过该方法实现了深复制,无论是对象本身还是对象里面的对象都实现了深复制。

其实在Java、C#等等面向对象的编程语言中,对象的复制、引用都是和上述js中的情况是一样的。

相关文章
|
1天前
|
XML JavaScript 前端开发
JavaScript简介&引入方式(JavaScript基础语法、JavaScript对象、BOM、DOM、事件监听)
JavaScript简介&引入方式(JavaScript基础语法、JavaScript对象、BOM、DOM、事件监听)
7 2
|
2天前
|
Web App开发 JavaScript 前端开发
JavaScript 中的 Range 和 Selection 对象
JavaScript 中的 `Range` 和 `Selection` 对象用于处理文本选择。`Range` 表示文档中选定的区域,而 `Selection` 表示用户选择的文本或光标位置。`Range` 可以创建并设置于任何元素或文本,具有多个属性(如 `startContainer`, `endContainer`, `collapsed`)和方法(如 `cloneContents`, `deleteContents`)。`Selection` 提供了获取和操作用户选择的方法,如 `anchorNode`, `focusNode` 和 `addRange`。两者在所有现代浏览器中基本兼容。
5 1
JavaScript 中的 Range 和 Selection 对象
|
4天前
|
JSON JavaScript 安全
向js发送含有NSDictionary对象或NSArray对象的消息
向js发送含有NSDictionary对象或NSArray对象的消息
15 0
|
5天前
|
前端开发 JavaScript
前端 JS 经典:判断对象属性是否存在
前端 JS 经典:判断对象属性是否存在
10 0
|
8天前
|
JSON 前端开发 JavaScript
前端 JS 经典:JSON 对象
前端 JS 经典:JSON 对象
14 0
|
8天前
|
前端开发 JavaScript
前端 js 经典:原型对象和原型链
前端 js 经典:原型对象和原型链
19 1
|
8天前
|
JavaScript 前端开发 流计算
使用JavaScript 中的Math对象和勾股定理公式,计算鼠标的位置与页面图片中心点的距离,根据距离对页面上的图片进行放大或缩小处理
使用JavaScript 中的Math对象和勾股定理公式,计算鼠标的位置与页面图片中心点的距离,根据距离对页面上的图片进行放大或缩小处理
|
8天前
|
JSON JavaScript 前端开发
js将json字符串还原为json对象
【5月更文挑战第14天】js将json字符串还原为json对象
40 1
|
9天前
|
设计模式 存储 消息中间件
JavaScript观察者模式:实现对象间的事件通信!
JavaScript观察者模式:实现对象间的事件通信!
|
9天前
|
设计模式 JavaScript 前端开发
JavaScript原型模式:实现对象共享属性和方法!
JavaScript原型模式:实现对象共享属性和方法!