js 如何实现对象值复制? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
shakaraka
0D
V2EX    程序员

js 如何实现对象值复制?

  •  
  •   shakaraka
    PRO
    2021-12-08 10:32:10 +08:00 3419 次点击
    这是一个创建于 1449 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 自带的好像没看到类似的方法

    第 1 条附言    2021-12-08 12:50:26 +08:00

    最新测试用例

    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() 
    第 2 条附言    2021-12-08 12:50:48 +08:00

    期望返回结果

    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 
    39 条回复    2021-12-08 16:56:10 +08:00
    NathanDo
        2
    NathanDo  
       2021-12-08 10:38:19 +08:00
    const b = JSON.parse(JSON.stringify(a));
    anjianshi
        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
    ```
    anjianshi
        4
    anjianshi  
       2021-12-08 10:39:00 +08:00
    哈哈哈哈 一楼抢答了
    viewweiwu
        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))

    这三种方法都可以
    viewweiwu
        6
    viewweiwu  
       2021-12-08 10:39:50 +08:00   1
    1. let b = Object.assign({}, a)
    2. let b = {...a}
    3. JSON.parse(JSON.stringify(a))

    这三种方法都可以
    tsanie
        7
    tsanie  
       2021-12-08 10:44:30 +08:00   1
    Object.assign 和{...a}是浅拷贝。
    JSON.parse(JSON.stringify(a))是深拷贝但没法处理函数。
    自己写递归或者用 lodash 的 cloneDeep 吧。
    shakaraka
        8
    shakaraka  
    OP
    PRO
       2021-12-08 10:48:14 +08:00
    @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 后原型丢了。不符合需求
    shakaraka
        9
    shakaraka  
    OP
    PRO
       2021-12-08 10:48:46 +08:00
    ```
    ----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
    ```
    yaphets666
        10
    yaphets666  
       2021-12-08 10:50:52 +08:00
    除了深拷贝?为啥要把他除了啊.jsonstringify 我劝你别用 会丢失数据结构
    shakaraka
        11
    shakaraka  
    OP
    PRO
       2021-12-08 10:52:37 +08:00
    @yaphets666 #10

    类似于 b = Object.from(a),这样能够实现 golang 的效果,这个 from 是不存在的,举例而已
    yaphets666
        13
    yaphets666  
       2021-12-08 10:55:29 +08:00
    @wunonglin js 没有原生的这种功能,如果想完美的实现一个复杂数据结构对象的值复制,必须使用深拷贝,自己实现一个或者使用第三方库.上面说的 object.assign 和 JSON.stringify 都有各自的局限性,你可以自行百度.
    gadfly3173
        14
    gadfly3173  
       2021-12-08 10:58:02 +08:00
    “从源 Object 创建一个新的 Object ,内存地址完全是新的,新 Object 也和源完全一样,不会丢失各种属性”
    这个需求不就是深拷贝么,,,除了深拷贝还能咋实现呢
    anjianshi
        15
    anjianshi  
       2021-12-08 10:58:32 +08:00
    要保留原型,就只能用笨方法了。
    shakaraka
        16
    shakaraka  
    OP
    PRO
       2021-12-08 11:03:00 +08:00
    @gadfly3173 #14 原生方法,非第三方
    anjianshi
        17
    anjianshi  
       2021-12-08 11:05:08 +08:00
    涉及到 class 的东西,是没法做到“复制”的。go 里我记得是没有 class 之类的概念,你才能用系统提供的能力去复制。
    只有“普通对象”,可以快捷复制。
    shakaraka
        18
    shakaraka  
    OP
    PRO
       2021-12-08 11:05:22 +08:00
    @yaphets666 #13
    @gadfly3173 #12

    lodash 的 cloneDeep 可以实现。但是我想知道有没有原生的方法
    anjianshi
        19
    anjianshi  
       2021-12-08 11:05:47 +08:00
    所以,要么不要带 class ,要么自己手动深拷贝。
    WhiteHu
        20
    WhiteHu  
       2021-12-08 11:08:07 +08:00
    structuredClone()

    有自带的方法哈 不过是试验性 api

    https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
    joshua7v
        21
    joshua7v  
       2021-12-08 11:10:58 +08:00
    structured clone 快来了
    虽然浏览器还未开放这个 api
    但可以借用浏览器的某些基于此 api 的接口
    比如 postmessage 什么的
    shakaraka
        22
    shakaraka  
    OP
    PRO
       2021-12-08 11:11:34 +08:00
    @WhiteHu #20 哈哈哈哈哈哈。还真有。至少可以在 node 开发的时候用,浏览器可以等未来吧
    3dwelcome
        23
    3dwelcome  
       2021-12-08 11:11:38 +08:00
    我总觉得 JS 并不是什么正经语言,早年发明之初,就没想过现代前端会那么复杂。

    JS 可以快速写逻辑,但是编写复杂的算法,还是比不上传统语言。

    学我,JS 语法不足,用 webasm 来补全。把你原生 JS 逼死,也只有用深拷贝。
    shakaraka
        24
    shakaraka  
    OP
    PRO
       2021-12-08 11:12:23 +08:00
    @joshua7v #21 firefox 永远的神,第一个支持了
    yaphets666
        25
    yaphets666  
       2021-12-08 11:34:49 +08:00
    @3dwelcome 正经语言是正经语言,没考虑过会这么复杂是真的.
    libook
        26
    libook  
       2021-12-08 11:43:33 +08:00   2
    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 库,可以拿来直接用。
    libook
        27
    libook  
       2021-12-08 11:46:00 +08:00
    @libook #26 勘误:
    JS 有个核心特性叫做原型链,对象值复制跟原型链的思想是矛盾的,前者希望尽可能复制代码,后者希望尽可能复用代码。这也就导致在 JS 里做深拷贝不那么方便。
    EPr2hh6LADQWqRVH
        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);
    gadfly3173
        29
    gadfly3173  
       2021-12-08 12:11:35 +08:00
    @avastms #28 楼主想要的是属性完全相同,内存地址不同的结果,Object.create 只是把源对象作为原型生成了新对象,你对源对象的修改还是会影响到新的对象
    bnm965321
        30
    bnm965321  
       2021-12-08 12:12:26 +08:00
    @wunonglin 这种的原理也是深拷贝吧
    chenstack
        31
    chenstack  
       2021-12-08 12:13:25 +08:00
    @avastms #28 这种方式变成 b 往上找值,a 的改动还会影响 b ,而且 b 没有给字段赋值的话,JSON.stringify(b)返回空的"{}"
    gadfly3173
        32
    gadfly3173  
       2021-12-08 12:13:48 +08:00
    EPr2hh6LADQWqRVH
        33
    EPr2hh6LADQWqRVH  
       2021-12-08 12:25:31 +08:00
    那自己实现一下吧, 之后维护一下原型链。

    const b = Object.assign({}, a);

    Object.setPrototypeOf(b, Object.getPrototypeOf(a));
    codehz
        34
    codehz  
       2021-12-08 14:44:19 +08:00
    考虑到还有原生对象,精确的复制大概是不现实的(
    加上还可以 proxy
    SmallTeddy
        35
    SmallTeddy  
       2021-12-08 14:49:44 +08:00
    @NathanDo 正解
    yangzzzzzz
        36
    yangzzzzzz  
       2021-12-08 14:56:05 +08:00
    使用 lodash

    _.clone(value):浅拷贝。浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间。

    _.cloneDeep(value):深拷贝。深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

    _.defaults(object, [sources]):只对比第一层。给对象添加字段,保持原来字段的值。

    _.defaultsDeep(object, [sources]):递归对比到最里层。给对象添加字段,保持原来字段的值。
    2i2Re2PLMaDnghL
        37
    2i2Re2PLMaDnghL  
       2021-12-08 15:09:45 +08:00   1
    你的验证代码写错了,console.log(b, a.K, b instanceof M)
    本该是 b.K
    2i2Re2PLMaDnghL
        38
    2i2Re2PLMaDnghL  
       2021-12-08 15:28:46 +08:00   1
    实际上有个挺严重的问题,所谓值复制可能不一定是可行的。

    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 去。
    hxse
        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}
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5648 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 01:50 PVG 09:50 LAX 17:50 JFK 20:50
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86