js 如何实现对象值复制?
go 的实现
package main
import "fmt"
type M struct {
Num int64
}
func T1() {
fmt.Printf("----T1----\n")
a := M{1}
b := a
fmt.Printf("%+v\n", a)
fmt.Printf("%+v\n", b)
a.Num = 2
fmt.Printf("%+v\n", a)
fmt.Printf("%+v\n", b)
}
func T2() {
fmt.Printf("----T2----\n")
a := &M{1}
b := a
fmt.Printf("%+v\n", a)
fmt.Printf("%+v\n", b)
a.Num = 2
fmt.Printf("%+v\n", a)
fmt.Printf("%+v\n", b)
}
func main() {
T1()
T2()
}
输出结果
----T1----
{Num:1}
{Num:1}
{Num:2}
{Num:1}
----T2----
&{Num:1}
&{Num:1}
&{Num:2}
&{Num:2}
js 的实现
function T2(){
let a = {num: 1}
let b = a
console.log(a)
console.log(b)
a.num = 2
console.log(a)
console.log(b)
}
输出结果
{num: 1}
{num: 1}
{num: 2}
{num: 2}
如何使用 js 实现 golang 的 T1 方法?除了深拷贝。
就类似于“从源 Object 创建一个新的 Object ,内存地址完全是新的,新 Object 也和源完全一样,不会丢失各种属性”,js 自带的好像没看到类似的方法
最新测试用例
const _ = require('./lodash.min.js')
class M {
get K(){
return this.Num + 1
}
constructor(value) {
this.Num = value
}
}
function T1(){
console.log('----T1----')
const a = new M(1)
const b = a
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
function T1_JSON(){
console.log('----T1_JSON----')
const a = new M(1)
const b = JSON.parse(JSON.stringify(a))
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
function T1_Assign(){
console.log('----T1_Assign----')
const a = new M(1)
const b = Object.assign({}, a)
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
function T1_Spread(){
console.log('----T1_Spread----')
const a = new M(1)
const b = {...a}
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
function T1_CloneDeep(){
console.log('----T1_CloneDeep----')
const a = new M(1)
const b = _.cloneDeep(a)
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
function T1_StructuredClone(){
console.log('----T1_StructuredClone----')
const a = new M(1)
const b = structuredClone(a)
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
function T1_Create(){
console.log('----T1_Create----')
const a = new M(1)
const b = Object.create(a)
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
T1()
T1_JSON()
T1_Assign()
T1_Spread()
T1_CloneDeep()
T1_StructuredClone()
T1_Create()
期望返回结果
M { Num: 1 }
M { Num: 1 }
M { Num: 2 } 3 true
M { Num: 1 } 2 true
实际返回结果
----T1----
M { Num: 1 }
M { Num: 1 }
M { Num: 2 } 3 true
M { Num: 2 } 3 true
----T1_JSON----
M { Num: 1 }
{ Num: 1 }
M { Num: 2 } 3 true
{ Num: 1 } 3 false
----T1_Assign----
M { Num: 1 }
{ Num: 1 }
M { Num: 2 } 3 true
{ Num: 1 } 3 false
----T1_Spread----
M { Num: 1 }
{ Num: 1 }
M { Num: 2 } 3 true
{ Num: 1 } 3 false
----T1_CloneDeep----
M { Num: 1 }
M { Num: 1 }
M { Num: 2 } 3 true
M { Num: 1 } 3 true
----T1_StructuredClone----
M { Num: 1 }
{ Num: 1 }
M { Num: 2 } 3 true
{ Num: 1 } 3 false
----T1_Create----
M { Num: 1 }
M {}
M { Num: 2 } 3 true
M {} 3 true
const b = JSON.parse(JSON.stringify(a));
如果是普通对象,没有 method 之类的东西,且不考虑性能,可以 JSON 化。
const objectA = {
a: {
b: { c: 1 },
d: 2,
},
e: 3
}
const objectB = JSON.parse(JSON.stringify(objectA))
objectA.a.d = 100
objectB.a.d = 200
console.log(objectA.a.d) // 100
console.log(objectB.a.d) // 200
哈哈哈哈 一楼抢答了
- let b = Object.assign({}. a)
- let b = {...a}
- JSON.parse(JSON.stringify(a))
这三种方法都可以
- let b = Object.assign({}, a)
- let b = {...a}
- JSON.parse(JSON.stringify(a))
这三种方法都可以
Object.assign 和{...a}是浅拷贝。
JSON.parse(JSON.stringify(a))是深拷贝但没法处理函数。
自己写递归或者用 lodash 的 cloneDeep 吧。
#2
#3
#4
#5
class M {
get K(){
return this.Num + 1
}
constructor(value) {
this.Num = value
}
}
function T1(){
console.log('----T1----')
const a = new M(1)
const b = a
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
function T2_JSON(){
console.log('----T2_JSON----')
const a = new M(1)
const b = JSON.parse(JSON.stringify(a))
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
function T2_Assign(){
console.log('----T2_Assign----')
const a = new M(1)
const b = Object.assign({}, a)
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
function T2_Spread(){
console.log('----T2_Spread----')
const a = new M(1)
const b = {...a}
console.log(a)
console.log(b)
a.Num = 2
console.log(a, a.K, a instanceof M)
console.log(b, a.K, b instanceof M)
}
T1()
T2_JSON()
T2_Assign()
T2_Spread()
使用 json ,assign ,spread 后原型丢了。不符合需求
----T1----
M { Num: 1 }
M { Num: 1 }
M { Num: 2 } 3 true
M { Num: 2 } 3 true
----T2_JSON----
M { Num: 1 }
{ Num: 1 }
M { Num: 2 } 3 true
{ Num: 1 } 3 false
----T2_Assign----
M { Num: 1 }
{ Num: 1 }
M { Num: 2 } 3 true
{ Num: 1 } 3 false
----T2_Spread----
M { Num: 1 }
{ Num: 1 }
M { Num: 2 } 3 true
{ Num: 1 } 3 false
除了深拷贝?为啥要把他除了啊.jsonstringify 我劝你别用 会丢失数据结构
#10
类似于 b = Object.from(a),这样能够实现 golang 的效果,这个 from 是不存在的,举例而已
github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/.internal/baseClone.js#L157
直接用 lodash 就好
js 没有原生的这种功能,如果想完美的实现一个复杂数据结构对象的值复制,必须使用深拷贝,自己实现一个或者使用第三方库.上面说的 object.assign 和 JSON.stringify 都有各自的局限性,你可以自行百度.
“从源 Object 创建一个新的 Object ,内存地址完全是新的,新 Object 也和源完全一样,不会丢失各种属性”
这个需求不就是深拷贝么,,,除了深拷贝还能咋实现呢
要保留原型,就只能用笨方法了。
#14 原生方法,非第三方
涉及到 class 的东西,是没法做到“复制”的。go 里我记得是没有 class 之类的概念,你才能用系统提供的能力去复制。
只有“普通对象”,可以快捷复制。
#13
#12
lodash 的 cloneDeep 可以实现。但是我想知道有没有原生的方法
所以,要么不要带 class ,要么自己手动深拷贝。
structuredClone()
有自带的方法哈 不过是试验性 api
developer.mozilla.org/en-US/docs/Web/API/structuredClone
structured clone 快来了
虽然浏览器还未开放这个 api
但可以借用浏览器的某些基于此 api 的接口
比如 postmessage 什么的
#20 哈哈哈哈哈哈。还真有。至少可以在 node 开发的时候用,浏览器可以等未来吧
我总觉得 JS 并不是什么正经语言,早年发明之初,就没想过现代前端会那么复杂。
JS 可以快速写逻辑,但是编写复杂的算法,还是比不上传统语言。
学我,JS 语法不足,用 webasm 来补全。把你原生 JS 逼死,也只有用深拷贝。
#21 firefox 永远的神,第一个支持了
正经语言是正经语言,没考虑过会这么复杂是真的.
JS 这种高度抽象的语言的设计初衷之一,就是让使用者不需要去考虑内存细节,所以赋值操作,对于简单类型是赋值,对于复杂类型是引用。
仅针对题主的例子,不考虑其他情况的话可以这样写:
let b = Object.assgin({},a);
这行代码的意思是把 a 里面的所有成员拿出来,一个一个地赋值给 b 的同名成员,这个赋值操作和等号的赋值操作一样,同样是简单类型赋值、复杂类型引用。因为 Num 是简单的数值型,所以执行了赋值而不是引用,导致修改 b.Num 不会让 a.Num 发生改变。
但如果 Num 的值是一个对象,因为 b.Num 和 a.Num 引用的是同一个 Num 对象,所以修改 Num 对象内的成员后,读取 a 和 b 内的 Num 对像会发现发生了变化,此时如果还需要进一步的值复制,就需要深拷贝。
如果对象是简单的、可以用 JSON 描述的对象,比如不含有 getter 、setter ,没用 Symbol 字段名、没用非 JSON 数据类型等,那么常用的方式是 JSON.stringify 序列化再 JSON.parse 反序列化,完成一个深拷贝。这个在本来就在上游使用 JSON 的场景用得很广泛,比如 HTTP 通信。
JS 有个核心特性叫做原型链,对象值复制跟原型链的思想是矛盾的,前者希望尽可能复用代码,后者希望尽可能复制代码。这也就导致在 JS 里做深拷贝不那么方便。
有不少第三方的 deep clone 库,可以拿来直接用。
#26 勘误:
JS 有个核心特性叫做原型链,对象值复制跟原型链的思想是矛盾的,前者希望尽可能复制代码,后者希望尽可能复用代码。这也就导致在 JS 里做深拷贝不那么方便。
基础不牢,地动山摇,都十几层楼了还没人抛出 Object.create()
既然 js 那就上原型啊
const a = {x: 1};
const b = Object.create(a);
b.x = 2;
assert(a.x === 1);
#28 楼主想要的是属性完全相同,内存地址不同的结果,Object.create 只是把源对象作为原型生成了新对象,你对源对象的修改还是会影响到新的对象
这种的原理也是深拷贝吧
#28 这种方式变成 b 往上找值,a 的改动还会影响 b ,而且 b 没有给字段赋值的话,JSON.stringify(b)返回空的"{}"
imgur.com/a/YA68B1j
#28
那自己实现一下吧, 之后维护一下原型链。
const b = Object.assign({}, a);
Object.setPrototypeOf(b, Object.getPrototypeOf(a));
考虑到还有原生对象,精确的复制大概是不现实的(
加上还可以 proxy
正解
使用 lodash
_.clone(value):浅拷贝。浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间。
_.cloneDeep(value):深拷贝。深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
_.defaults(object, [sources]):只对比第一层。给对象添加字段,保持原来字段的值。
_.defaultsDeep(object, [sources]):递归对比到最里层。给对象添加字段,保持原来字段的值。
你的验证代码写错了,console.log(b, a.K, b instanceof M)
本该是 b.K
实际上有个挺严重的问题,所谓值复制可能不一定是可行的。
function factory(value){
let num = value;
return {get Num(){return num}, set Num(val){num=val}}
}
let a=factory();
let b=someValueClone(a);
你不可能在这个情况下通用地分离 a.Num 和 b.Num ,因为涉及到作用域的分支性变化。
所以说实话,不如学 Rust 搞 trait Clone ,你自己写的类你自己实现 Clone 去。
这个问题有那么复杂吗
let a = {num: 1}
let b = {...a}
console.log(a)
console.log(b)
a.num = 2
console.log(a)
console.log(b)
{num: 1}
{num: 1}
{num: 2}
{num: 1}
X-Y Problem 对于X-Y Problem的意思如下: 1)有人想解决问题X 2)他觉得Y可能是解决X问题的方法 3)但是他不知道Y应该怎么做 4)于是他去问别人Y应…
有新的 K40 刷 miui eu 版本的全套小白教程码,eu 的开发版与稳定版有什么区别? 本来刷 K40 开发版的,想想既然已经要刷机了,那还不如直接上 eu 版本。求一套…
前端渣渣,闲时无聊,写了个图片处理在线工具,目前支持:图片压缩,图片大小调整,图片裁剪,图片格式转换。后续还会加些其他功能。 家人们,有什么好的建议吗? getimg.co/…