V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
shakaraka
0D
V2EX  ›  程序员

js 如何实现对象值复制?

  •  
  •   shakaraka ·
    PRO
    · Dec 8, 2021 · 3734 views
    This topic created in 1607 days ago, the information mentioned may be changed or developed.

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

    Supplement 1  ·  Dec 8, 2021

    最新测试用例

    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()
    
    Supplement 2  ·  Dec 8, 2021

    期望返回结果

    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 replies    2021-12-08 16:56:10 +08:00
    NathanDo
        2
    NathanDo  
       Dec 8, 2021
    const b = JSON.parse(JSON.stringify(a));
    anjianshi
        3
    anjianshi  
       Dec 8, 2021
    如果是普通对象,没有 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  
       Dec 8, 2021
    哈哈哈哈 一楼抢答了
    viewweiwu
        5
    viewweiwu  
       Dec 8, 2021
    1. let b = Object.assign({}. a)
    2. let b = {...a}
    3. JSON.parse(JSON.stringify(a))

    这三种方法都可以
    viewweiwu
        6
    viewweiwu  
       Dec 8, 2021   ❤️ 1
    1. let b = Object.assign({}, a)
    2. let b = {...a}
    3. JSON.parse(JSON.stringify(a))

    这三种方法都可以
    tsanie
        7
    tsanie  
       Dec 8, 2021   ❤️ 1
    Object.assign 和{...a}是浅拷贝。
    JSON.parse(JSON.stringify(a))是深拷贝但没法处理函数。
    自己写递归或者用 lodash 的 cloneDeep 吧。
    shakaraka
        8
    shakaraka  
    OP
    PRO
       Dec 8, 2021
    @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
       Dec 8, 2021
    ```
    ----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  
       Dec 8, 2021
    除了深拷贝?为啥要把他除了啊.jsonstringify 我劝你别用 会丢失数据结构
    shakaraka
        11
    shakaraka  
    OP
    PRO
       Dec 8, 2021
    @yaphets666 #10

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

    lodash 的 cloneDeep 可以实现。但是我想知道有没有原生的方法
    anjianshi
        19
    anjianshi  
       Dec 8, 2021
    所以,要么不要带 class ,要么自己手动深拷贝。
    WhiteHu
        20
    WhiteHu  
       Dec 8, 2021
    structuredClone()

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

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

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

    学我,JS 语法不足,用 webasm 来补全。把你原生 JS 逼死,也只有用深拷贝。
    shakaraka
        24
    shakaraka  
    OP
    PRO
       Dec 8, 2021
    @joshua7v #21 firefox 永远的神,第一个支持了
    yaphets666
        25
    yaphets666  
       Dec 8, 2021
    @3dwelcome 正经语言是正经语言,没考虑过会这么复杂是真的.
    libook
        26
    libook  
       Dec 8, 2021   ❤️ 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  
       Dec 8, 2021
    @libook #26 勘误:
    JS 有个核心特性叫做原型链,对象值复制跟原型链的思想是矛盾的,前者希望尽可能复制代码,后者希望尽可能复用代码。这也就导致在 JS 里做深拷贝不那么方便。
    EPr2hh6LADQWqRVH
        28
    EPr2hh6LADQWqRVH  
       Dec 8, 2021
    基础不牢,地动山摇,都十几层楼了还没人抛出 `Object.create()`

    既然 js 那就上原型啊

    const a = {x: 1};

    const b = Object.create(a);

    b.x = 2;

    assert(a.x === 1);
    gadfly3173
        29
    gadfly3173  
       Dec 8, 2021
    @avastms #28 楼主想要的是属性完全相同,内存地址不同的结果,Object.create 只是把源对象作为原型生成了新对象,你对源对象的修改还是会影响到新的对象
    bnm965321
        30
    bnm965321  
       Dec 8, 2021
    @wunonglin 这种的原理也是深拷贝吧
    chenstack
        31
    chenstack  
       Dec 8, 2021
    @avastms #28 这种方式变成 b 往上找值,a 的改动还会影响 b ,而且 b 没有给字段赋值的话,JSON.stringify(b)返回空的"{}"
    gadfly3173
        32
    gadfly3173  
       Dec 8, 2021
    EPr2hh6LADQWqRVH
        33
    EPr2hh6LADQWqRVH  
       Dec 8, 2021
    那自己实现一下吧, 之后维护一下原型链。

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

    Object.setPrototypeOf(b, Object.getPrototypeOf(a));
    codehz
        34
    codehz  
       Dec 8, 2021
    考虑到还有原生对象,精确的复制大概是不现实的(
    加上还可以 proxy
    SmallTeddy
        35
    SmallTeddy  
       Dec 8, 2021
    @NathanDo 正解
    yangzzzzzz
        36
    yangzzzzzz  
       Dec 8, 2021
    使用 lodash

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

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

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

    _.defaultsDeep(object, [sources]):递归对比到最里层。给对象添加字段,保持原来字段的值。
    2i2Re2PLMaDnghL
        37
    2i2Re2PLMaDnghL  
       Dec 8, 2021   ❤️ 1
    你的验证代码写错了,console.log(b, a.K, b instanceof M)
    本该是 b.K
    2i2Re2PLMaDnghL
        38
    2i2Re2PLMaDnghL  
       Dec 8, 2021   ❤️ 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  
       Dec 8, 2021
    这个问题有那么复杂吗

    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}
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1088 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 67ms · UTC 17:26 · PVG 01:26 · LAX 10:26 · JFK 13:26
    ♥ Do have faith in what you're doing.