
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 2 NathanDo 2021-12-08 10:38:19 +08:00 const b = JSON.parse(JSON.stringify(a)); |
3 anjianshi 2021-12-08 10:38:43 +08:00 如果是普通对象,没有 method 之类的东西,且不考虑性能,可以 JSON 化。 ```Javascript 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 ``` |
4 anjianshi 2021-12-08 10:39:00 +08:00 哈哈哈哈 一楼抢答了 |
5 viewweiwu 2021-12-08 10:39:00 +08:00 1. let b = Object.assign({}. a) 2. let b = {...a} 3. JSON.parse(JSON.stringify(a)) 这三种方法都可以 |
6 viewweiwu 2021-12-08 10:39:50 +08:00 1. let b = Object.assign({}, a) 2. let b = {...a} 3. JSON.parse(JSON.stringify(a)) 这三种方法都可以 |
7 tsanie 2021-12-08 10:44:30 +08:00 Object.assign 和{...a}是浅拷贝。 JSON.parse(JSON.stringify(a))是深拷贝但没法处理函数。 自己写递归或者用 lodash 的 cloneDeep 吧。 |
8 shakaraka OP PRO @NathanDo #2 @anjianshi #3 @anjianshi #4 @viewweiwu #5 ```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 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 后原型丢了。不符合需求 |
9 shakaraka OP PRO ``` ----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 ``` |
10 yaphets666 2021-12-08 10:50:52 +08:00 除了深拷贝?为啥要把他除了啊.jsonstringify 我劝你别用 会丢失数据结构 |
11 shakaraka OP PRO |
12 gadfly3173 2021-12-08 10:55:08 +08:00 |
13 yaphets666 2021-12-08 10:55:29 +08:00 @wunonglin js 没有原生的这种功能,如果想完美的实现一个复杂数据结构对象的值复制,必须使用深拷贝,自己实现一个或者使用第三方库.上面说的 object.assign 和 JSON.stringify 都有各自的局限性,你可以自行百度. |
14 gadfly3173 2021-12-08 10:58:02 +08:00 “从源 Object 创建一个新的 Object ,内存地址完全是新的,新 Object 也和源完全一样,不会丢失各种属性” 这个需求不就是深拷贝么,,,除了深拷贝还能咋实现呢 |
15 anjianshi 2021-12-08 10:58:32 +08:00 要保留原型,就只能用笨方法了。 |
16 shakaraka OP PRO @gadfly3173 #14 原生方法,非第三方 |
17 anjianshi 2021-12-08 11:05:08 +08:00 涉及到 class 的东西,是没法做到“复制”的。go 里我记得是没有 class 之类的概念,你才能用系统提供的能力去复制。 只有“普通对象”,可以快捷复制。 |
18 shakaraka OP PRO |
19 anjianshi 2021-12-08 11:05:47 +08:00 所以,要么不要带 class ,要么自己手动深拷贝。 |
20 WhiteHu 2021-12-08 11:08:07 +08:00 structuredClone() 有自带的方法哈 不过是试验性 api https://developer.mozilla.org/en-US/docs/Web/API/structuredClone |
21 joshua7v 2021-12-08 11:10:58 +08:00 structured clone 快来了 虽然浏览器还未开放这个 api 但可以借用浏览器的某些基于此 api 的接口 比如 postmessage 什么的 |
23 3dwelcome 2021-12-08 11:11:38 +08:00 我总觉得 JS 并不是什么正经语言,早年发明之初,就没想过现代前端会那么复杂。 JS 可以快速写逻辑,但是编写复杂的算法,还是比不上传统语言。 学我,JS 语法不足,用 webasm 来补全。把你原生 JS 逼死,也只有用深拷贝。 |
25 yaphets666 2021-12-08 11:34:49 +08:00 @3dwelcome 正经语言是正经语言,没考虑过会这么复杂是真的. |
26 libook 2021-12-08 11:43:33 +08:00 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 库,可以拿来直接用。 |
27 libook 2021-12-08 11:46:00 +08:00 @libook #26 勘误: JS 有个核心特性叫做原型链,对象值复制跟原型链的思想是矛盾的,前者希望尽可能复制代码,后者希望尽可能复用代码。这也就导致在 JS 里做深拷贝不那么方便。 |
28 EPr2hh6LADQWqRVH 2021-12-08 11:56:13 +08:00 基础不牢,地动山摇,都十几层楼了还没人抛出 `Object.create()` 既然 js 那就上原型啊 const a = {x: 1}; const b = Object.create(a); b.x = 2; assert(a.x === 1); |
29 gadfly3173 2021-12-08 12:11:35 +08:00 @avastms #28 楼主想要的是属性完全相同,内存地址不同的结果,Object.create 只是把源对象作为原型生成了新对象,你对源对象的修改还是会影响到新的对象 |
31 chenstack 2021-12-08 12:13:25 +08:00 @avastms #28 这种方式变成 b 往上找值,a 的改动还会影响 b ,而且 b 没有给字段赋值的话,JSON.stringify(b)返回空的"{}" |
32 gadfly3173 2021-12-08 12:13:48 +08:00 |
33 EPr2hh6LADQWqRVH 2021-12-08 12:25:31 +08:00 那自己实现一下吧, 之后维护一下原型链。 const b = Object.assign({}, a); Object.setPrototypeOf(b, Object.getPrototypeOf(a)); |
34 codehz 2021-12-08 14:44:19 +08:00 考虑到还有原生对象,精确的复制大概是不现实的( 加上还可以 proxy |
35 SmallTeddy 2021-12-08 14:49:44 +08:00 @NathanDo 正解 |
36 yangzzzzzz 2021-12-08 14:56:05 +08:00 使用 lodash _.clone(value):浅拷贝。浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间。 _.cloneDeep(value):深拷贝。深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。 _.defaults(object, [sources]):只对比第一层。给对象添加字段,保持原来字段的值。 _.defaultsDeep(object, [sources]):递归对比到最里层。给对象添加字段,保持原来字段的值。 |
37 2i2Re2PLMaDnghL 2021-12-08 15:09:45 +08:00 你的验证代码写错了,console.log(b, a.K, b instanceof M) 本该是 b.K |
38 2i2Re2PLMaDnghL 2021-12-08 15:28:46 +08:00 实际上有个挺严重的问题,所谓值复制可能不一定是可行的。 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 去。 |
39 hxse 2021-12-08 16:56:10 +08:00 这个问题有那么复杂吗 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} |