php深浅拷贝,JavaScript 中的深浅拷贝

工作中经常会遇到需要复制 JavaScript 数据的时候,遇到 bug 时实在令人头疼;面试中也经常会被问到如何实现一个数据的深浅拷贝,但是你对其中的原理清晰吗?一起来看一下吧!

一、为什么会有深浅拷贝

想要更加透彻的理解为什么 JavaScript 会有深浅拷贝,需要先了解下 JavaScript 的数据类型有哪些,一般分为基本类型(Number、String、Null、Undefined、Boolean、Symbol )和引用类型(对象、数组、函数)。

基本类型是不可变的,任何方法都无法改变一个基本类型的值,也不可以给基本类型添加属性或者方法。但是可以为引用类型添加属性和方法,也可以删除其属性和方法。

基本类型和引用类型在内存中的存储方式也大不相同,基本类型保存在栈内存中,而引用类型保存在堆内存中。为什么要分两种保存方式呢? 因为保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是我们可以把它的地址写在栈内存中以供我们访问。

说来这么多,我们来看个示例:

let num1 = 10;

let obj1 = {

name: "hh"

}

let num2 = num1;

let obj2 = obj1;

num2 = 20;

obj2.name = "kk";

console.log(num1); // 10

console.log(obj1.name); // kk

执行完这段代码,内存空间里是这样的:

bVblqXC?w=1112&h=626

可以看到 obj1 和 obj2 都保存了一个指向该对象的指针,所有的操作都是对该引用的操作,所以对 obj2 的修改会影响 obj1。

小结:

之所以会出现深浅拷贝,是由于 JS 对基本类型和引用类型的处理不同。基本类型指的是简单的数据段,而引用类型指的是一个对象保存在堆内存中的地址,JS 不允许我们直接操作内存中的地址,也就是说不能操作对象的内存空间,所以,我们对对象的操作都只是在操作它的引用而已。

在复制时也是一样,如果我们复制一个基本类型的值时,会创建一个新值,并把它保存在新的变量的位置上。而如果我们复制一个引用类型时,同样会把变量中的值复制一份放到新的变量空间里,但此时复制的东西并不是对象本身,而是指向该对象的指针。所以我们复制引用类型后,两个变量其实指向同一个对象,所以改变其中的一个对象,会影响到另外一个。

二、深浅拷贝

1. 浅拷贝

浅拷贝只是复制基本类型的数据或者指向某个对象的指针,而不是复制对象本身,源对象和目标对象共享同一块内存;若对目标对象进行修改,存在源对象被篡改的可能。

我们来看下浅拷贝的实现:

/* sourceObj 表示源对象

* 执行完函数,返回目标对象

*/

function shadowClone (sourceObj = {}) {

let targetObj = Array.isArray(sourceObj) ? [] : {};

let copy;

for (var key in sourceObj) {

copy = sourceObj[key];

targetObj[key] = copy;

}

return targetObj;

}

// 定义 source

let sourceObj = {

number: 1,

string: 'source1',

boolean: true,

null: null,

undefined: undefined,

arr: [{name: 'arr1'}, 1],

func: () => 'sourceFunc1',

obj: {

string: 'obj1',

func: () => 'objFunc1'

}

}

// 拷贝sourceObj

let copyObj = shadowClone(sourceObj);

// 修改 sourceObj

copyObj.number = 2;

copyObj.string = 'source2';

copyObj.boolean = false;

copyObj.arr[0].name = 'arr2';

copyObj.func = () => 'sourceFunc2';

copyObj.obj.string = 'obj2';

copyObj.obj.func = () => 'objFunc2';

// 执行

console.log(sourceObj);

/* {

number: 1,

string: 'source1',

boolean: true,

null: null,

undefined: undefined,

arr: [{name: 'arr2'}],

func: () => 'sourceFunc1',

obj: {

func: () => 'objFunc2',

string: 'obj2'

}

}

*/

2. 深拷贝

深拷贝能够实现真正意义上的对象的拷贝,实现方法就是递归调用“浅拷贝”。深拷贝会创造一个一模一样的对象,其内容地址是自助分配的,拷贝结束之后,内存中的值是完全相同的,但是内存地址是不一样的,目标对象跟源对象不共享内存,修改任何一方的值,不会对另外一方造成影响。

/* sourceObj 表示源对象

* 执行完函数,返回目标对象

*/

function deepClone (sourceObj = {}) {

let targetObj = Array.isArray(sourceObj) ? [] : {};

let copy;

for (var key in sourceObj) {

copy = sourceObj[key];

if (typeof(copy) === 'object') {

if (copy instanceof Object) {

targetObj[key] = deepClone(copy);

} else {

targetObj[key] = copy;

}

} else if (typeof(copy) === 'function') {

targetObj[key] = eval(copy.toString());

} else {

targetObj[key] = copy;

}

}

return targetObj;

}

// 定义 sourceObj

let sourceObj = {

number: 1,

string: 'source1',

boolean: true,

null: null,

undefined: undefined,

arr: [{name: 'arr1'}],

func: () => 'sourceFunc1',

obj: {

string: 'obj1',

func: () => 'objFunc1'

}

}

// 拷贝sourceObj

let copyObj = deepClone(sourceObj);

// 修改 source

copyObj.number = 2;

copyObj.string = 'source2';

copyObj.boolean = false;

copyObj.arr[0].name = 'arr2';

copyObj.func = () => 'sourceFunc2';

copyObj.obj.string = 'obj2';

copyObj.obj.func = () => 'objFunc2';

// 执行

console.log(sourceObj);

/* {

number: 1,

string: 'source1',

boolean: true,

null: null,

undefined: undefined,

arr: [{name: 'arr1'}],

func: () => 'sourceFunc1',

obj: {

func: () => 'objFunc1',

string: 'obj1'

}

}

*/

两个方法可以合并在一起:

/* deep 为 true 表示深复制,为 false 表示浅复制

* sourceObj 表示源对象

* 执行完函数,返回目标对象

*/

function clone (deep = true, sourceObj = {}) {

let targetObj = Array.isArray(sourceObj) ? [] : {};

let copy;

for (var key in sourceObj) {

copy = sourceObj[key];

if (deep && typeof(copy) === 'object') {

if (copy instanceof Object) {

targetObj[key] = clone(deep, copy);

} else {

targetObj[key] = copy;

}

} else if (deep && typeof(copy) === 'function') {

targetObj[key] = eval(copy.toString());

} else {

targetObj[key] = copy;

}

}

return targetObj;

}

三、使用技巧

1. concat()、slice()

(1)若拷贝数组是纯数据(不含对象),可以通过concat() 和 slice() 来实现深拷贝;

let a = [1, 2];

let b = [3, 4];

let copy = a.concat(b);

a[1] = 5;

b[1] = 6;

console.log(copy);

// [1, 2, 3, 4]

let a = [1, 2];

let copy = a.slice();

copy[0] = 3;

console.log(a);

// [1, 2]

(2)若拷贝数组中有对象,可以使用 concat() 和 slice() 方法来实现数组的浅拷贝。

let a = [1, {name: 'hh1'}];

let b = [2, {name: 'kk1'}];

let copy = a.concat(b);

copy[1].name = 'hh2';

copy[3].name = 'kk2';

console.log(copy);

// [1, {name: 'hh2'}, 2, {name: 'kk2'}]

无论 a[1].name 或者 b[1].name 改变,copy[1].name 的值都会改变。

let a = [1, {name: 'hh1'}];

let copy = a.slice();

copy[1].name = 'hh2';

console.log(a);

// [1, {name: 'hh2'}]

改变了 a[1].name 后,copy[1].name 的值也改变了。

2. Object.assign()、Object.create()

Object.assign()、Object.create() 都是一层(根级)深拷贝,之下的级别为浅拷贝。

(1) 若拷贝对象只有一级,可以通过 Object.assign()、Object.create() 来实现对象的深拷贝;

let sourceObj = {

str: 'hh1',

number: 10

}

let targetObj = Object.assign({}, sourceObj)

targetObj.str = 'hh2'

console.log(sourceObj);

// {str: 'hh1', number: 10}

let sourceObj = {

str: 'hh1',

number: 10

}

let targetObj = Object.create(sourceObj)

targetObj.str = 'hh2'

console.log(sourceObj);

// {str: 'hh1', number: 10}

(2) 若拷贝对象有多级, Object.assign()、Object.create() 实现的是对象的浅拷贝。

let sourceObj = {

str: 'hh',

number: 10,

obj: {

str: 'kk1'

}

}

let targetObj = Object.assign({}, sourceObj)

targetObj.obj.str = 'kk2'

console.log(sourceObj);

// {

// str: 'hh',

// number: 10,

// obj: {

// str: 'kk2'

// }

// }

let sourceObj = {

str: 'hh',

number: 10,

obj: {

str: 'kk1'

}

}

let targetObj = Object.create(sourceObj)

targetObj.obj.str = 'kk2'

console.log(sourceObj);

// {

// str: 'hh',

// number: 10,

// obj: {

// str: 'kk2'

// }

// }

修改了 targetObj.obj.str 的值之后,sourceObj.obj.str 的值也改变了。

3. 对象的解构

对象的解构同 Object.assign() 和 Object.create(),都是一层(根级)深拷贝,之下的级别为浅拷贝。

(1)若拷贝对象只有一层,可以通过对象的解构来实现深拷贝;

let sourceObj = {

str: 'hh1',

number: 10

}

let targetObj = {...sourceObj};

targetObj.str = 'hh2'

console.log(sourceObj);

// {str: 'hh1', number: 10}

(2)若拷贝对象有多层,通过对象的解构实现的是对象的浅拷贝。

let sourceObj = {

str: 'hh',

number: 10,

obj: {

str: 'kk1'

}

}

let targetObj = {...sourceObj};

targetObj.obj.str = 'kk2'

console.log(sourceObj);

// {

// str: 'hh',

// number: 10,

// obj: {

// str: 'kk2'

// }

// }

4. JSON.parse()

用 JSON.stringify() 把对象转成字符串,再用 JSON.parse() 把字符串转成新的对象,可以实现对象的深复制。

let source = ['hh', 1, [2, 3], {name: 'kk1'}];

let copy = JSON.parse(JSON.stringify(source));

copy[2][1] = 4;

copy[3].name = 'kk2';

console.log(source);

// ['hh', 1, [2, 3], {name: 'kk1'}]

可以看出,虽然改变了 copy[2].name 的值,但是 source[2].name 的值没有改变。

JSON.parse(JSON.stringify(obj)) 不仅能复制数组还可以复制对象,但是几个弊端:

1)它会抛弃对象的 constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成 Object;

2)这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp 对象是无法通过这种方式深拷贝。

3)只有可以转成 JSON 格式的对象才可以这样用,像 function 没办法转成 JSON。

5. 可以使用的库

以下两种库都能实现深浅拷贝,有各自的使用方法。

jQuery

具体使用可以参考:官方文档

Lodash

具体使用可以参考:官方文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/392289.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Python进行地理编码和反向地理编码

Geocoding is the process of taking input text, such as an address or the name of a place, and returning a latitude/longitude location. To put it simply, Geocoding is converting physical address to latitude and longitude.地理编码是获取输入文本(例如地址或地点…

java开发简历编写_如何通过几个简单的步骤编写出色的初级开发人员简历

java开发简历编写So you’ve seen your dream junior developer role advertised, and are thinking about applying. It’s time to write that Resume! Nothing better than sitting down to a blank piece of paper and not knowing how to start, right?因此,您…

leetcode 628. 三个数的最大乘积(排序)

给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积。 示例 1: 输入: [1,2,3] 输出: 6 解题思路 最大的乘积可能有两种情况 1.两个最小负数和一个最大正数 2.三个最大正数 代码 class Solution {public int maximumProduct(int[…

[Object-C语言随笔之三] 类的创建和实例化以及函数的添加和调用!

上一小节的随笔写了常用的打印以及很基础的数据类型的定义方式,今天就来一起学习下如何创建类与函数的一些随笔; 首先类的创建:在Xcode下,菜单File-New File,然后出现选择class模板,如下图&…

2024-AI人工智能学习-安装了pip install pydot但是还是报错

2024-AI人工智能学习-安装了pip install pydot但是还是报错 出现这样子的错误: /usr/local/bin/python3.11 /Users/wangyang/PycharmProjects/studyPython/tf_model.py 2023-12-24 22:59:02.238366: I tensorflow/core/platform/cpu_feature_guard.cc:182] This …

grafana 创建仪表盘_创建仪表盘前要问的三个问题

grafana 创建仪表盘可视化 (VISUALIZATIONS) It’s easier than ever to dive into dashboarding, but are you doing it right?深入仪表板比以往任何时候都容易,但是您这样做正确吗? Tableau, Power BI, and many other business intelligence tools …

qq群 voiceover_如何在iOS上使用VoiceOver为所有人构建应用程序

qq群 voiceoverby Jayven N由Jayven N 如何在iOS上使用VoiceOver为所有人构建应用程序 (How to build apps for everyone using VoiceOver on iOS) 辅助功能入门 (Getting started with accessibility) There’s always those topics that people don’t talk about enough. S…

IntelliJ IDEA代码常用的快捷键(自查)

IntelliJ IDEA代码常用的快捷键有: Alt回车 导入包,自动修正 CtrlN 查找类 CtrlShiftN 查找文件 CtrlAltL 格式化代码 CtrlAltO 优化导入的类和包 AltInsert 生成代码(如get,set方法,构造函数等) CtrlE或者AltShiftC 最近更改的代码 CtrlR…

leetcode 1489. 找到最小生成树里的关键边和伪关键边(并查集)

给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1 ,同时还有一个数组 edges ,其中 edges[i] [fromi, toi, weighti] 表示在 fromi 和 toi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一个子集,它连接了所有…

带彩色字体的man pages(debian centos)

1234567891011121314151617181920212223242526272829303132333435363738我的博客已迁移到xdoujiang.com请去那边和我交流简介most is a paging program that displays,one windowful at a time,the contents of a file on a terminal. It pauses after each windowful and prin…

提取json对象中的数据,转化为数组

var xx1 ["乐谱中的调号为( )调", "写出a自然小调音阶。", "以G为冠音,构写增四、减五音程。", "调式分析。", "将下列乐谱移为C大调。", "正确组合以下乐谱。", "以下…

java 同步块的锁是什么,java – 同步块 – 锁定多个对象

我添加了另一个答案,因为我还没有添加评论给其他人的帖子。>事实上,同步是用于代码,而不是对象或数据。在同步块中用作参数的对象引用表示锁定。所以如果你有如下代码:class Player {// Same instance shared for all players.…

大数据对社交媒体的影响_数据如何影响媒体,广告和娱乐职业

大数据对社交媒体的影响In advance of our upcoming event — Data Science Salon: Applying AI and ML to Media, Advertising, and Entertainment, we asked our speakers, who are some of nation’s leading data scientists in the media, advertising, and entertainment…

Go-项目结构和代码组织

简介 做大量的输入,通过对比、借鉴,加上自己的经验,产出一个尽可能优的方案。 开源界优秀项目的结构示例 因为最新的 Go 版本已经使用 module 作为版本依赖,所以,所有项目的 vendor 我都忽略,建议直接使用 …

iref streams_如何利用Neo4j Streams并建立即时数据仓库

iref streamsby Andrea Santurbano通过安德里亚桑图尔巴诺(Andrea Santurbano) 如何利用Neo4j Streams并建立即时数据仓库 (How to leverage Neo4j Streams and build a just-in-time data warehouse) In this article, we’ll show how to create a Just-In-Time Data Wareho…

Nodejs正则表达式函数之match、test、exec、search、split、replace使用详解

1. Match函数使用指定的正则表达式函数对字符串惊醒查找,并以数组形式返回符合要求的字符串原型:stringObj.match(regExp)参数:stringObj 必选项,需要去进行匹配的字符串RegExp 必选项,指定的正则表达式返回值&#xf…

Zabbix 3.0 从入门到精通(zabbix使用详解)

第1章 zabbix监控 1.1 为什么要监控 在需要的时刻,提前提醒我们服务器出问题了 当出问题之后,可以找到问题的根源 网站/服务器 的可用性 1.1.1 网站可用性 在软件系统的高可靠性(也称为可用性,英文描述为HA,High Avail…

python 装饰器装饰类_5分钟的Python装饰器指南

python 装饰器装饰类重点 (Top highlight)There’s no doubt that Python decorators are one of the more advanced and tougher-to-understand programming concepts. This doesn’t mean you should avoid learning them — as you encounter them in production code soone…

php中颜色的索引值,计算PHP中两种颜色之间的平均颜色,使用索引号作为参考值...

我们假设为了讨论的目的,每个颜色都有一个“值”.那么,你想要的就足够简单:$index 0.2;$val1 get_value_of_color($color1);$val2 get_value_of_color($color2);$newval $val1 * $index $val2 * (1 - $index);$newcolor get_color_from_value($newval);所以,很…

leetcode 989. 数组形式的整数加法

对于非负整数 X 而言,X 的数组形式是每位数字按从左到右的顺序形成的数组。例如,如果 X 1231,那么其数组形式为 [1,2,3,1]。 给定非负整数 X 的数组形式 A,返回整数 XK 的数组形式。 示例 1: 输入:A […