JavaScript中深拷贝与浅拷贝详解

目录
  • 1 浅拷贝概念
  • 2 深拷贝概念
  • 3 浅拷贝的实现方式
    • 3.1 Object.assign()
    • 3.2 Array.prototype.concat()
    • 3.3 Array.prototype.slice()
    • 3.4 直接赋值
  • 4 深拷贝的实现方式
    • 4.1 JSON.parse(JSON.stringify())
    • 4.2 函数库lodash
  • 总结

1 浅拷贝概念

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

浅拷贝是创建一个新对象,该对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象

示例代码:

let people = { //定义一个People对象
    name: "张三",
    age: 3,
    address: "中国"
}

console.log("原对象:", people);
let newPeople = people; //进行浅拷贝
console.log("新对象:", newPeople);
//原对象: { name: '张三', age: 3, address: '中国' }
//新对象: { name: '张三', age: 3, address: '中国' }

//为对象修改名字
newPeople.name = "橘猫吃不胖";
console.log("原对象:", people);
console.log("新对象:", newPeople);
//原对象: { name: '橘猫吃不胖', age: 3, address: '中国' }
//新对象: { name: '橘猫吃不胖', age: 3, address: '中国' }

从上面的示例可以看出,当newPeople的name属性修改后,原来的people也发生了变化,这是因为新创建的对象与旧对象具有相同的内存地址

2 深拷贝概念

深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

示例代码:

let people = { //定义一个People对象
    name: "张三",
    age: 3,
    address: "中国"
}

//对people进行深拷贝
let newPeople = JSON.parse(JSON.stringify(people));
console.log("原对象:", people);
console.log("新对象:", newPeople);
// 原对象: { name: '张三', age: 3, address: '中国' }
// 新对象: { name: '张三', age: 3, address: '中国' }

//修改新对象中的adress属性
newPeople.address = "俄罗斯";
console.log("原对象:", people);
console.log("新对象:", newPeople);
// 原对象: { name: '张三', age: 3, address: '中国' }
// 新对象: { name: '张三', age: 3, address: '俄罗斯' }

从上面的例子可以看出,深拷贝后,修改新对象,不会影响原对象。

3 浅拷贝的实现方式

3.1 Object.assign()

Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

语法:

Object.assign(target, ...sources)
//target:目标对象;sources:源对象。

如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。

示例:

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

//进行浅拷贝
const returnedTarget = Object.assign(target, source);

console.log(target);
console.log(returnedTarget);
// { a: 1, b: 4, c: 5 }
// { a: 1, b: 4, c: 5 }

//修改其中的值
target.b = 10;
console.log(target);
console.log(returnedTarget);
// { a: 1, b: 10, c: 5 }
// { a: 1, b: 10, c: 5 }

当对象object只有一层的时候,是深拷贝,示例代码如下:

const obj = { name: "橘猫吃不胖" };

//进行浅拷贝
let newObj = Object.assign({}, obj);

//修改新对象中的name属性为张三
newObj.name = "张三";
console.log("原对象:", obj);
console.log("新对象:", newObj);
// 原对象: { name: '橘猫吃不胖' }
// 新对象: { name: '张三' }

3.2 Array.prototype.concat()

concat()方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

语法:

var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])
//valueN可选,数组和/或值,将被合并到一个新的数组中。
//如果省略了所有 valueN 参数,则 concat 会返回调用此方法的现存数组的一个浅拷贝。

示例代码:

let arr1 = [1, 2, { name: "橘猫吃不胖" }];

//进行浅拷贝
let arr2 = arr1.concat();

console.log("原数组:", arr1);
console.log("新数组:", arr2);
// 原数组: [ 1, 2, { name: '橘猫吃不胖' } ]
// 新数组: [ 1, 2, { name: '橘猫吃不胖' } ]

//修改原数组
arr1[1] = "hhhhh";
console.log("原数组:", arr1);
console.log("新数组:", arr2);
// 原数组: [ 1, 'hhhhh', { name: '橘猫吃不胖' } ]
// 新数组: [ 1, 2, { name: '橘猫吃不胖' } ]

3.3 Array.prototype.slice()

slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。

语法:

arr.slice([begin[, end]])
//begin:可选,提取起始处的索引(从 0 开始),从该索引开始提取原数组元素。
//如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。
//如果省略 begin,则 slice 从索引 0 开始。
//如果 begin 超出原数组的索引范围,则会返回空数组。

//end:可选,提取终止处的索引(从 0 开始),在该索引处结束提取原数组元素。slice 会提取原数组中索引从 begin 到 end 的所有元素(包含 begin,但不包含 end)。
//slice(1,4) 会提取原数组中从第二个元素开始一直到第四个元素的所有元素 (索引为 1, 2, 3的元素)。
//如果该参数为负数, 则它表示在原数组中的倒数第几个元素结束抽取。 slice(-2,-1) 表示抽取了原数组中的倒数第二个元素到最后一个元素(不包含最后一个元素,也就是只有倒数第二个元素)。
//如果 end 被省略,则 slice 会一直提取到原数组末尾。
//如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。

示例代码:

let arr1 = [1, 2, { name: "橘猫吃不胖" }];

//进行浅拷贝
let arr2 = arr1.slice();

console.log("原数组:", arr1);
console.log("新数组:", arr2);
// 原数组: [ 1, 2, { name: '橘猫吃不胖' } ]
// 新数组: [ 1, 2, { name: '橘猫吃不胖' } ]

//修改原数组
arr1[1] = "hhhhh";
console.log("原数组:", arr1);
console.log("新数组:", arr2);
// 原数组: [ 1, 'hhhhh', { name: '橘猫吃不胖' } ]
// 新数组: [ 1, 2, { name: '橘猫吃不胖' } ]

3.4 直接赋值

直接使用“=”赋值可以实现浅拷贝,示例代码如下:

let obj1 = { //定义一个对象obj1
    name: "张三",
    age: 34
}
let obj2 = obj1; //进行浅拷贝

console.log("obj1:", obj1);
console.log("obj2:", obj2);
// obj1: { name: '张三', age: 34 }
// obj2: { name: '张三', age: 34 }

//修改obj2中的name属性
obj2.name = "橘猫吃不胖";

console.log("obj1:", obj1);
console.log("obj2:", obj2);
// obj1: { name: '橘猫吃不胖', age: 34 }
// obj2: { name: '橘猫吃不胖', age: 34 }

4 深拷贝的实现方式

4.1 JSON.parse(JSON.stringify())

JSON是一种语法,用来序列化对象、数组、数值、字符串、布尔值和 null 。它基于JavaScript语法,但与之不同:JavaScript不是JSON,JSON也不是JavaScript。

JSON对象包含两个方法:用于解析JSON的parse()方法,以及将对象/值转换为JSON字符串的stringify()方法,下面对这两种方法进行一些介绍。

JSON.parse()方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换(操作)。

语法:

JSON.parse(text[, reviver])
//text:要被解析成 JavaScript 值的字符串
//reviver,可选,转换器, 如果传入该参数(函数),可以用来修改解析生成的原始值,调用时机在 parse 函数返回之前。

示例:

JSON.parse('{}');              // {}
JSON.parse('true');            // true
JSON.parse('"foo"');           // "foo"
JSON.parse('[1, 5, "false"]'); // [1, 5, "false"]
JSON.parse('null');            // null

JSON.stringify()方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。

语法:

JSON.stringify(value[, replacer [, space]])
//value:将要序列化成 一个 JSON 字符串的值。
//replacer,可选,如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
//space,可选,指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null),将没有空格。

示例:

JSON.stringify({});                        // '{}'
JSON.stringify(true);                      // 'true'
JSON.stringify("foo");                     // '"foo"'
JSON.stringify([1, "false", false]);       // '[1,"false",false]'
JSON.stringify({ x: 5 });                  // '{"x":5}'

深拷贝示例代码:

let people = { //定义一个People对象
    name: "张三",
    age: 3,
    address: "中国"
}

//对people进行深拷贝
let newPeople = JSON.parse(JSON.stringify(people));
console.log("原对象:", people);
console.log("新对象:", newPeople);
// 原对象: { name: '张三', age: 3, address: '中国' }
// 新对象: { name: '张三', age: 3, address: '中国' }

//修改新对象中的adress属性
newPeople.address = "俄罗斯";
console.log("原对象:", people);
console.log("新对象:", newPeople);
// 原对象: { name: '张三', age: 3, address: '中国' }
// 新对象: { name: '张三', age: 3, address: '俄罗斯' }

用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,这样新的对象产生了,实现深拷贝。这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。

let arr1 = [1, 2, { name: "橘猫吃不胖" }, function () { }];

//进行深拷贝
let arr2 = JSON.parse(JSON.stringify(arr1));

console.log("原数组:", arr1);
console.log("新数组:", arr2);
// 原数组: [ 1, 2, { name: '橘猫吃不胖' }, [Function (anonymous)] ]
// 新数组: [ 1, 2, { name: '橘猫吃不胖' }, null ]

由上面例子可以看出,函数并没有被拷贝在arr2中。这是因为 JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。

4.2 函数库lodash

Lodash是一个JavaScript库,提供了多个实用程序功能,而Lodash库中最常用的功能之一是cloneDeep()方法。此方法有助于深度克隆对象,还可以克隆JSON.stringify()方法的局限性,即不可序列化的属性。

示例代码:

const lodash = require("lodash");
let people = {
    name: "张三",
    age: 3,
    address: "中国"
}

//对people进行深拷贝
let newPeople = lodash.cloneDeep(people);
console.log("原对象:", people);
console.log("新对象:", newPeople);
// 原对象: { name: '张三', age: 3, address: '中国' }
// 新对象: { name: '张三', age: 3, address: '中国' }

//修改新对象中的adress属性
newPeople.address = "俄罗斯";
newPeople.name = "橘猫吃不胖";
console.log("原对象:", people);
console.log("新对象:", newPeople);
// 原对象: { name: '张三', age: 3, address: '中国' }
// 新对象: { name: '橘猫吃不胖', age: 3, address: '俄罗斯' }

总结

到此这篇关于JavaScript中深拷贝与浅拷贝详解的文章就介绍到这了,更多相关JavaScript深拷贝与浅拷贝内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • js中的赋值 浅拷贝和深拷贝详细

    目录 1.js内存 2.赋值 3.浅拷贝 4.深拷贝 前言: 在学习下面文章前我们简单了解一下的内存的知识,以下先简要提一下 1.js内存 js内存,或者说大部分语言的内存都分为栈和堆.基本数据类型的变量值分配在栈上,引用数据类型的变量值分配在堆上,栈中只是存储具体堆中对象的地址. 2.赋值 对于基本数据类型,赋值操作是拷贝,即新旧变量不会相互影响. var a = 1; var b = a; b = 2; console.log(b); // 2 对于引用数据类型,赋值操作只是在栈中新增一个指

  • 详解JS变量存储深拷贝和浅拷贝

    变量类型与存储空间 栈内存和堆内存 基本数据类型 string.number.null.undefined.boolean.symbol(ES6新增) 变量值存放在栈内存中,可直接访问和修改变量的值 基本数据类型不存在拷贝,好比如说你无法修改数值1的值 引用类型 Object Function RegExp Math Date 值为对象,存放在堆内存中 在栈内存中变量保存的是一个指针,指向对应在堆内存中的地址. 当访问引用类型的时候,要先从栈中取出该对象的地址指针,然后再从堆内存中取得所需的数据

  • js中区分深拷贝与浅拷贝的实战过程

    目录 一.自我理解 二.数据存储形式 (1)基本数据类型存储于栈中 (2)引用数据类型存储与堆中 三.怎样实现深拷贝? (1)借助JSON对象的parse和stringify (2)手写递归 (3)JQuery中extend方法 总结/注意 总结 一.自我理解 简单来讲就是:深拷贝层层拷贝,浅拷贝只拷贝第一层. 在深拷贝中,新对象中的更改不会影响原对象,而在浅拷贝中,新对象中的更改,原对象中也会跟着改. 在深拷贝中,原对象与新对象不共享相同的属性,而在浅拷贝中,它们具有相同的属性. 举个栗子:存

  • 浅谈JavaScript浅拷贝和深拷贝

    目录 一.直接赋值 二.浅拷贝 三.深拷贝 1. JSON对象的方式 2. 递归复制 网上关于这个话题,讨论有很多了,根据各路情况我自己整理了一下,最后还是能接近完美的实现深拷贝,欢迎大家讨论. javascript中的对象是引用类型,在复制对象的时候就要考虑是用浅拷贝还是用深拷贝. 一.直接赋值 对象是引用类型,如果直接赋值给另外一个对象,那么只是赋值一个引用,实际上两个变量指向的同一个数据对象,如果其中一个对象的属性变更,那么另外一个也会变更. 示例1,简单示例: let human1 =

  • 详解JS深拷贝与浅拷贝

    一.预备知识 1.1.JS数据类型 基本数据类型:Boolean.String.Number.null.undefined 引用数据类型:Object.Array.Function.RegExp.Date等 1.2.数据类型的复制 基本数据类型的复制,是按值传递的 var a = 1; var b = a; b = 2; console.log(a); // 1 console.lob(b); // 2 引用数据类型的复制,是按引用传值 var obj1 = { a: 1; b: 2; }; v

  • JavaScript中深拷贝与浅拷贝详解

    目录 1 浅拷贝概念 2 深拷贝概念 3 浅拷贝的实现方式 3.1 Object.assign() 3.2 Array.prototype.concat() 3.3 Array.prototype.slice() 3.4 直接赋值 4 深拷贝的实现方式 4.1 JSON.parse(JSON.stringify()) 4.2 函数库lodash 总结 1 浅拷贝概念 深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的. 浅拷贝是创建一个新对象,该对象有着原始对象属性值的一份精确拷

  • JavaScript中的函数式编程详解

    函数式编程 函数式编程是一种编程范式,是一种构建计算机程序结构和元素的风格,它把计算看作是对数学函数的评估,避免了状态的变化和数据的可变,与函数式编程相对的是命令式编程.我们有这样一个需求,给数组的每个数字加一: // 数组每个数字加一, 命令式编程 let arr = [1, 2, 3, 4]; let newArr = []; for(let i = 0; i < arr.length; i++){ newArr.push(arr[i] + 1); } console.log(newArr)

  • C语言结构体成员赋值的深拷贝与浅拷贝详解

    目录 浅拷贝 结构体中不存在指针成员变量时 结构体中存在指针成员变量时 深拷贝 结论 浅拷贝 C语言中的浅拷贝是指在拷贝过程中,对于指针型成员变量只拷贝指针本身,而不拷贝指针所指向的目标,它按字节复制的.我们分几种情况举例子来看一下. 结构体中不存在指针成员变量时 代码如下: #include <stdio.h> typedef struct { char name[64]; int age; }Member; int main(){ Member stu1 = { "LiMing&

  • javascript中Array()数组函数详解

    在程序语言中数组的重要性不言而喻,JavaScript中数组也是最常使用的对象之一,数组是值的有序集合,由于弱类型的原因,JavaScript中数组十分灵活.强大,不像是Java等强类型高级语言数组只能存放同一类型或其子类型元素,JavaScript在同一个数组中可以存放多种类型的元素,而且是长度也是可以动态调整的,可以随着数据增加或减少自动对数组长度做更改. Array()是一个用来构建数组的内建构造器函数.数组主要由如下三种创建方式: array = new Array() array =

  • JavaScript中eval()函数用法详解

    eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行. 如果参数是一个表达式,eval() 函数将执行表达式.如果参数是Javascript语句,eval()将执行 Javascript 语句. 语法 复制代码 代码如下: eval(string) 参数 描述 string 必需.要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句. eval()函数用法详解: 此函数可能使用的频率并不是太高,但是在某些情况下具有很大的作用,下面就介绍一下eva

  • JavaScript中 ES6 generator数据类型详解

    1. generator简介 generator 是ES6引入的新的数据类型, 看上去像一个函数,除了使用return返回, yield可以返回多次. generator 由function* 定义, (注意*号), 2. 示例 函数无法保存状态, 有时需要全局变量来保存数字: 2.1 'use strict'; function next_id(){ var id = 1; while(id<100){ yield id; id++; } return id; } // 测试: var x,

  • javascript 中的继承实例详解

    javascript 中的继承实例详解 阅读目录 原型链继承 借用构造函数 组合继承 寄生组合式继承 后记 继承有两种方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法. 由于函数没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且实现继承主要依靠原型链来实现. 下面介绍几种js的继承: 原型链继承 原型链继承实现的本质是重写原型对象,代之以一个新类型的实例.代码如下: function SuperType() { this.pr

  • Javascript中window.name属性详解

    关于window下自带name的属性 不知道大家有没有发现这样一种情况 在控制台里直接输出未声明变量,正常情况应该是会报错的,而且声明未赋值的变量输出应该是undefined var a; //undefined b; //报错 但是偏偏就个别特例,就是name属性 其实window自身就带有name这个属性,在控制台输入window可以可以看到 打开 往下翻就可以找到 window.name直译过来是窗口名字,主要用于为超链接和表单设置目标(targets),什么意思呢,我们做个案例 建立两个

  • JavaScript中BOM和DOM详解

    目录 BOM(浏览器对象模型) 1. window 获取浏览器c窗口尺寸 2. screen 获取电脑屏幕大小 3. window 开启关闭窗口 4. 浏览器事件 5. location 6. history 7. navigator 获取浏览器相关信息 8. 弹窗 DOM (文档对象模型) DOM 分类 DOM对象 Document文档对象 element文档对象 DOM事件操作 鼠标事件 键盘事件 触屏事件 特殊事件 表单事件 浏览器兼容处理 兼容性写法,封装工具 BOM(浏览器对象模型)

  • javascript中的深复制详解及实例分析

     javascript中的深复制 JavaScript深拷贝是初学者甚至有经验的开发着,都会经常遇到问题,并不能很好的理解javascript的深拷贝.        深拷贝(deepClone)是神马,与深拷贝相对应的就是浅拷贝,刚开始我也没弄懂. 在很多情况下,我们都需要给变量赋值,给内存地址赋予一个值,但是在赋值引用值类型的时候,只是共享一个内存区域,导致赋值的时候,还跟之前的值保持一直性. 看一个具体的例子 // 给test赋值了一个对象 var test = { a: 'a', b:

随机推荐