在JS中复制对象

JavaScript中的对象复制 所有的面向对象的语言中,都存在着对象引用、复制等等问题,对于初学者来说可能难以理解。今天我来总结一下JavaScript中对象复制。 首先我们要知道JavaScript中的数据分为基本类型(单类型)和引用类型。除了Object对象,其余都是基本类型例如我们常见的String、Number、boolean、null、undefined。而数组、时间对象以及我们自定义的对象等等,都是继承自Object的,所以说都是引用类型。 在js中对对象进行赋值时,基本类型会被直接复制,例如下: let a = 1; 阅读详情

原文地址:Copying objects in Javascript, 原文作者:Victor Parmar
渣翻译,有英文阅读能力的可以去原网址阅读,正文部分的括号内是译者的尝试补充说明
自豪地采用谷歌翻译

在这篇文章中,我将会讲到几种在JS中复制对象的方式,我们将会关注到浅复制和深复制。
在开始之前,值得一提的是一些基础知识:JS中的对象只是对内存中某个位置的引用。这些引用是可以更改的。即他们可以被重新分配。从而,简单的复制引用的操作在结果上仅仅是将两个引用指向了内存中的同一位置。

var foo = {
  a: 'abc'
}
console.log(foo.a) // abc

var bar = foo
console.log(bar.a) // abc

foo.a = 'yo foo'
console.log(foo.a) // yo foo
console.log(bar.a) // yo foo

bar.a = 'whatup bar?'
console.log(foo.a) // whatup bar?
console.log(bar.a) // whatup bar?

正如你从上面例子中看到的,foo和bar都反映了同一个对象的变化。从而,在JS中复制对象需要小心,具体取决于您的用例。

浅复制

如果你的对象的属性的类型仅仅只是值类型(译者注:基本类型)的。你可以使用扩展运算符语法或者Object.assign(...)

var obj = { foo: 'foo', bar: 'bar' }
var copy = { ...obj } // Object { foo: 'foo', bar: 'bar' }
var obj = { foo: 'foo', bar: 'bar' }
var copy = Object.assign({}, obj) // Object { foo: 'foo', bar: 'bar' }

请注意,上述两种方法都可用于将属性值从多个源对象复制到目标对象:

var obj1 = { foo: 'foo' }
var obj2 = { bar: 'bar' }

var copySpread = { ...obj1, ...obj2 } // Object { foo: 'foo', bar: 'bar' }
var copyAssign = Object.assign({}, obj1, obj2) // Object { foo: 'foo', bar: 'bar' }

事实上,上述方法的问题在于对象的属性如果是一个对象,则只会复制该属性对象在内存中的引用。即它相当于var bar = foo,如同第一个代码例子:

var foo = { a: 0, b: { c: 0 } }
var copy = { ...foo }

copy.a = 1
copyb.c. = 2

console.dir(foo) // { a: 0, b: { c: 2 } }
console.dir(copy) // { a: 0, b: { c: 2 } }

深复制(有缺陷)

为了对对象进行深复制操作,一个潜在的解决方案是序列化对象为一个字符串,然后反序列化,生成一个新对象:

var obj = { a: 0, b: { c: 0 } }
var copy = JSON.parse(JSON.stringify(obj))

不幸的是,这个方法仅仅适用于当源对象包含可序列化的值类型并且没有循环引用的情况。不能序列化的值的类型,比如Date对象,即使它在字符串化上以ISO格式打印。JSON.parse仅仅会将它解释为一个字符串,而不是Date对象。

深复制(更少的缺陷)

对于更复杂的对象,可以使用更新的HTML5structured clone克隆算法。不幸的是,在撰写本文时,它仍局限于某些内置类型,但它支持的内容类型比JSON.parse更多。比如:DateRegExpMapSetBlobFileListImageData、稀疏和类型化数组。它还在克隆对象中保留了引用关系。允许它支持不适用于上述序列化方法的循环和递归结构。

当前还没有直接调用结构化克隆算法的方法,但一些新的浏览器特性可以被用来间接使用这个方法。从而,我们会得到一些可能用于深度复制对象的变通方法

使用 MessageChannel:这背后的想法是利用MessageChannel通信功能使用的序列化算法。这个功能是基于事件的,因此获取克隆结果是一个异步的操作。

class StructruedCloner {
  constructor() {
    this.pendingClones_ = new Map()
    this.nextKey_ = 0
    
    const channel = new MessageChannel()
    this.inPort_ = channle.port1
    this.outPort_ = channel.port2
    
    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key)
      resolve(value)
      this.pendingClones_.delete(key)
    }
    this.outPort_.start()
  }
  
  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++
      this.pendingClones_.set(key, resolve)
      this.inPort_.postMessage({key, value})
    })
  }
}

const structuredCloneAsync = window.structuredCloneAsync = 
      StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner)

const main = async () => {
  const original = { date: new Date(), number: Math.random() } // 译者注释:添加一些JSON方法不能解释的对象
  original.self = original // 译者注释:添加循环引用
  
  const clone = await structuredCloneAsync(original)
  
  // 不同的对象
  console.assert(original !== clone)
  console.assert(original.date !== clone.date)
  
  // 循环
  console.assert(original.self === original)
  console.assert(clone.self === clone)
  
  // 等价值
  console.assert(original.number === clone.number)
  console.assert(Number(original.date) === Number(clone.date))
  
  console.log('Assertions complete.')
}

main()

使用historyAPIhistory.pushState()history.replaceState()两个方法会创建它们第一个参数的结构化对象。注意这个方法是同步的,操纵浏览器历史记录不是一个快速的操作并且反复调用此方法可能导致浏览器无响应。

const structureClone = obj => {
  const oldState = history.state
  history.replaceState(obj.null)
  const clonedObj = history.state
  history.replaceState(oldState, null)
  return clonedObj
}

使用notificationAPI:当创建一个新的提醒(译者注:notification),构造函数会从它所关联的数据中创建一份结构化的克隆副本。注意,这么做浏览器会尝试将提醒显示给用户。但是这将会静默失败。除非应用已经请求到显示提醒的权限。万一权限存在,提醒会立即关闭。

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true})
  n.onshow = n.close.bind(n)
  return n.data
}

在NodeJS中进行深复制

在NodeJS的8.0.0版本中,它提供了一个序列化的API,它是兼容结构化克隆的。注意:这个API在本文撰写时(译者注:原文发表于2018.11.1)还是标记为实验性的:

const v8 = require('v8')
const buf = v8.serialize({a: 'foo', b: new Date()})
const cloned = v8.deserialize(buf)
cloned.b.getMonth()

对于版本低于8.0.0或者更稳定的实现,一种方法是:可以使用lodashcloneDeep方法。该方法也基于结构化克隆算法

结论

总而言之,在JS中复制对象最佳的算法是严重依赖于你所复制对象的上下文和类型的。而lodash是通用深复制函数最安全的选择。也许你会给出更高效的实现呢。下面是一个对Date对象也起效的深复制的函数:

function deepClone(obj) {
  var copy
  
  // 处理3种基础类型,和null、undefined
  if (obj === null || typeof obj !== 'object') return obj
  
  // 处理日期
  if (obj instanceof Date) {
    copy = new Date()
    copy.setTime(obj.getTime())
    return copy
  }
  
  // 处理数组
  if (Array instanceof Array) {
    copy = []
    for (var i = 0, len = obj.length; i < len; i++) {
      copy[i] = deepClone(obj[i])
    }
    return copy
  }
  
  // 处理函数
  if (obj instanceof Function) {
    copy = function() {
      return obj.apply(this, arguemnts)
    }
    return copy
  }
  
  // 处理对象
  if (obj instance Object) {
    copy = {}
    for (var attr in obj) {
      if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr])
    }
    return copy
  }
  
  throw new Error("Unable to copy obj as type isn't suported" + obj.constructor.name)
}

就个人而言,我期待能够在任何地方使用结构化克隆,最后让这个问题得到休息,快乐的克隆:)

用 SQL 分析不同用户群组留存率 一、前言 用户增长对企业非常重要,主要包括: 新增、留存、召回 。本文介绍一个实用的分析留存率的方法: Cohort Analysis (群组分析),APP的下载量越高并不是说产品就越好,产品若没有很好的留存,花费大量时间和资源来挖掘新客户也将是竹篮打水一场空。群组分析有利于衡量用户在每个漏斗的用户流失节点、用户流失数据,从而帮助你发现产品的用户的行为模式和用户增长点。从本质上讲,群组分析Cohort Analysis为你提供了一种数据驱动的方法,用群组分析Cohort Analysis方法,可以来帮助提 阅读详情

相关推荐

RH850从0搭建Autosar开发环境【7】- Davinci Configurator之CanTp模块配置详解(上)

RH850从0搭建Autosar开发环境【7】- Davinci Configurator之CanTp模块配置详解(上)

weixin_42412049的博客 2250

JS复制一个对象

在JavaScript中,复制一个对象可以使用多种方法。

一步一个脚印,踏实努力,向着目标前进 7193

中国湖泊高分辨率矢量图.shp - (梁子湖)ArcGIS使用

中国湖泊高分辨率矢量图.shp - (梁子湖)ArcGIS使用

js 复制object对象几种常用方法

这种方法是可以实现深拷贝,但是注意(如果值是正则表达式或者函数则不能用)这种方法只能实现对象第一层的深拷贝,对第一层以下的是只能浅拷贝。

nvliya的博客 1639

Js篇】JS复制一个对象

JSON.stringify() 方法用于将一个 JavaScript 对象转换为一个 JSON 字符串,而 JSON.parse() 方法用于将一个 JSON 字符串转换为一个 JavaScript 对象。因此,可以将一个对象先序列化成一个 JSON 字符串,再反序列化成一个新的 JavaScript 对象,从而实现对象复制。在 JavaScript 中,如果直接将一个对象赋值给另一个变量,那么实际上赋值的是该对象的引用(即指向该对象的内存地址),而不是将该对象复制一份赋值给另一个变量。

s398511733的博客 3007

JavaScript 如何拷贝对像(Object)或者数组(Array)

JavaScript 数组,拷贝,复核 Copy ,Clone , deepClone.自定义深拷贝方法,使用lodash cloneDeep方法。Array.prototype.slice.

全栈攻略 495

js拷贝对象的方法及原因

js拷贝对象的方法及原因

qq_36462217的博客 165

在 JavaScript 中复制对象

在 JavaScript 中复制对象

m0_63639281的博客 1785

js对象复制方法

js对象复制 对象js里重要的基本类型,广义上讲js里万物皆对象复制一个数据,是一个常用的需求,但是相对于基本数据类型如数字,字符串等,对象复制起来要复杂的多。 // 数字的复制 var m = 666 var n = m console.log(n) // 666 复制代码对象复制是无法这样的,以为这样的复制只是复制对象的引用 var o1 = { m: 666, n: 'abc...

weixin_33691817的博客 384

JS对象的引用复制和数据复制

JS对象分为两类,一类为基础类型对象,包括字符串(String),数值(Number),布尔值(Boolean),null(空),undefined(未定义)。另一类为合成对象,又叫做引用类型对象,包括数组(Array),对象(Object),函数(Function)。 基础类型对象存储的是对象的实际数据,引用类型对象存储的是对象的引用地址,而把对象的实际内容单独存放,因为引用对象通常比较庞大,...

kangkang_style的博客 1039

JS】归纳总结现代javascript对象的引用和复制(浅拷贝与深拷贝)

在 JavaScript 中有 8 种基本的数据类型(注:7 种基本数据类型也称为原始类型 和 1 种引用类型) 1. `number` 用于任何类型的数字:整数或浮点数,在 `±(2^53-1)` 范围内的整数。 2. `bigint` 用于任意长度的整数。 3. `string` 用于字符串:一个字符串可以包含 0 个或多个字符,所以没有单独的单字符类型。 4. `boolean` 用于 `true` 和 `false`。 5. `null` 用于未知的值 —— 只有一个 `null` 值的独立类型。

余浩的博客 995

JavaScript中对象的浅复制深复制

//把a复制给b let a = 12; let b = a; 这种复制方法只能适用于基本类型,如果a是对象怎么办呢?我们先来看看上面的代码在内存中做了什么事: 声明了变量a = 12,栈内存会分配一块区域来存储,如上图所示。把a赋给b,会在栈中重新开辟一块区域来存储b,并且b的值就是a的值。 假如a是对象,内存做了什么事呢?来看下面的例子: let a = {}; let b = a; ...

weixin_29195615的博客 1051

JS对象复制

在JavaScript很多人复制一个对象的时候都是直接用"=",因为大家都觉得脚本语言是没有指针、引用、地址之类的,所以直接用"="就可以把一个对象复制给另外一个对象,如下代码: 代码1: var i1 = 1; var i2 = i1; i2 = 2; alert("i1:"+i1+",i2:"+i2); 输出结果:i1:1 , i2:2 但可能没有

java的涟漪 857

js对象复制

当复杂数据类型如Object与Array存储的时候,是将数据实例存储在堆中,将引用地址存储在栈中的. 那么在对数据进行复制与拷贝的过程中,就会延伸出两个概念:浅复制;深复制 深复制:以目标对象为原型,在堆中重新开辟一块空间用来存储新的对象实例.那么两个对象就拥有了相同的数据实例和不同的引用地址.新的对象发生改变时,不会对原对象产生影响. 浅复制:是指以目标对象的引用地址赋值给一个新的对象,既两个对象有相同的引用地址,那么自然就有相同的实例.新对象在发生改变的时候,原对象也自然随之改变. 浅复制不会复

weixin_59297032的博客 1728

js json对象复制 对象复制

function util(){ //复制 var clone = function (sObj) { if (typeof (sObj) != "object") { return sObj; } ...

chuwa5121的博客 765

js中数组和对象复制

一、数组 1、浅复制和深度复制的区别 (1) 浅复制:浅复制只会将对象的各个属性进行依次复制,并不会进行递归复制。而 JavaScript 存储对象都是存地址,所以浅复制会导致 两个数组指向同一块内存地址,使得改变第二个数组的同时,导致第一个数组也会改变。 (2) 深复制:而深复制则不同,它会将原对象的各个属性复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制

Iloveyougirls的博客 5158

JavaScript对象复制

近期项目因为怕数据污染所以用到了js对象复制 js里的对象都是继承自object,是引用类型,所以无法通过=号复制 所以整理了一些常用的复制方法,如下 一、通过JSON序列化和反序列化创建新的对象 1 var obj = { a: 1, b: '2' }; 2 var newObj = JSON.parse(JSON.stringify(obj)); 测试一下: obj...

weixin_34365417的博客 115

浅谈JS对象的浅复制深复制

基础知识: JS对象:指向内存中某个位置的指针,指针是可变的,也就是说可以被重新赋值,所以说,复制指针,只是将两个指针指向内存中的同一地址,也可以说是地址复制。 //复制指针 var obj = { a: "123" }; var newObj = obj; 在上面代码中,对象 obj和 newObj都能随着对方的变化而变化,所以要根据实际情况来复制对象。 一、浅复制 如果要操作的对象拥有的属性...

喜鹿的博客 417

js对象深复制和浅复制

在JavaScript中,复制对象分为两种方式,浅复制深复制,浅复制复制一层对象的属性,而深复制则递归复制了所有层级 1、浅复制没有办法去真正的去复制一个对象,而只是保存了对该对象的引用 2、深复制可以实现真正的复制一个对象复制 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对...

weixin_43025823的博客 279

js 深度复制数组与对象

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33751566的博客 229
上一篇: 你应该在你的域名中使用www吗?
下一篇: 使用mkcert为你的localhost添加有效的HTTPS证书
张子虚
博客等级 码龄14年 33粉丝 72原创
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值