คัดลอกค่าของตัวแปรไปเป็นค่าอื่น


102

ฉันมีตัวแปรที่มีออบเจ็กต์ JSON เป็นค่าของมัน ฉันกำหนดตัวแปรนี้ให้กับตัวแปรอื่นโดยตรงเพื่อให้พวกเขาใช้ค่าเดียวกัน นี่คือวิธีการทำงาน:

var a = $('#some_hidden_var').val(),
    b = a;

สิ่งนี้ใช้ได้ผลและทั้งสองอย่างมีค่าเท่ากัน ฉันใช้mousemoveตัวจัดการเหตุการณ์เพื่ออัปเดตbผ่านแอปของฉัน เกี่ยวกับการคลิกปุ่มผมต้องการที่จะกลับไปเป็นค่าเดิมที่มีความหมายค่าที่เก็บไว้ในba

$('#revert').on('click', function(e){
    b = a;
});

หลังจากนี้หากฉันใช้mousemoveตัวจัดการเหตุการณ์เดียวกันมันจะอัปเดตทั้งสองอย่างaและbเมื่อก่อนหน้านี้จะอัปเดตbตามที่คาดไว้เท่านั้น

ฉันนิ่งงันกับปัญหานี้! มีอะไรผิดปกติที่นี่?


โปรดแสดงตัวจัดการมูสมูฟของคุณ นอกจากนี้เนื่องจากaตั้งค่าจากที่.val()ฉันถือว่าเป็น JSON (สตริง) ไม่ใช่วัตถุ - ใช่ไหม คุณใช้JSON.parse(a)ในบางจุดเพื่อรับวัตถุจริงหรือไม่?
nnnnnn

ใช่มันเป็นสตริง แต่ฉันใช้$.parseJSONเพื่อแปลงเป็นวัตถุ โครงสร้าง: { 'key': {...}, 'key': {...}, ...}. ขออภัยไม่สามารถโพสต์รหัสใด ๆ ที่นี่ไม่อนุญาตในที่ทำงานของฉัน!
Rutwick Gangurde

1
ก็aคือวัตถุ?
Armand

1
ดูเหมือนปัญหาการกำหนดขอบเขต .. เป็นaตัวแปรทั่วโลกหรือไม่?
msturdy

2
รหัสที่คุณแสดงมีเฉพาะสตริง หากคุณกำลังพูดถึงออบเจ็กต์ตัวแปรหลายตัวสามารถอ้างถึงออบเจ็กต์เดียวกันและอ็อบเจ็กต์นั้นสามารถกลายพันธุ์ผ่านตัวแปรใดก็ได้ ดังนั้นหากไม่เห็นโค้ดที่จัดการกับตัวแปรมากขึ้น (จะ$.parseJSON()เข้ามาในโค้ดได้ที่ไหน) จึงยากที่จะบอกว่าปัญหาคืออะไร เกี่ยวกับกฎในสถานที่ทำงานของคุณคุณไม่จำเป็นต้องโพสต์รหัสจริงของคุณทั้งหมดเพียงแค่สร้างตัวอย่างที่สั้นและกว้างขึ้นซึ่งแสดงให้เห็นถึงปัญหา (และควรใส่ลิงก์ไปยังการสาธิตสดที่jsfiddle.net ) .
nnnnnn

คำตอบ:


205

สิ่งสำคัญคือต้องเข้าใจว่าตัว=ดำเนินการใน JavaScript ทำและไม่ทำอะไร

=ผู้ประกอบการไม่ได้ทำให้สำเนาของข้อมูล

ตัว=ดำเนินการสร้างการอ้างอิงใหม่ไปยังข้อมูลเดียวกัน

หลังจากที่คุณเรียกใช้รหัสเดิมของคุณ:

var a = $('#some_hidden_var').val(),
    b = a;

aและbตอนนี้สองชื่อแตกต่างกันสำหรับวัตถุเดียวกัน

การเปลี่ยนแปลงใด ๆ ที่คุณทำกับเนื้อหาของวัตถุนี้จะเห็นเหมือนกันไม่ว่าคุณจะอ้างอิงผ่านaตัวแปรหรือbตัวแปร เป็นวัตถุเดียวกัน

ดังนั้นเมื่อคุณพยายาม "เปลี่ยนกลับ" bเป็นaวัตถุดั้งเดิมในภายหลังด้วยรหัสนี้:

b = a;

รหัสไม่ได้ทำอะไรเลยเพราะaและbเป็นสิ่งเดียวกัน รหัสจะเหมือนกับที่คุณเขียน:

b = b;

ซึ่งเห็นได้ชัดว่าจะไม่ทำอะไรเลย

เหตุใดรหัสใหม่ของคุณจึงใช้งานได้

b = { key1: a.key1, key2: a.key2 };

ที่นี่คุณกำลังสร้างออบเจ็กต์ใหม่เอี่ยมด้วย{...}อ็อบเจกต์ลิเทอรัล วัตถุใหม่นี้ไม่เหมือนกับวัตถุเก่าของคุณ ตอนนี้คุณกำลังตั้งค่าbให้อ้างอิงถึงวัตถุใหม่นี้ซึ่งจะทำสิ่งที่คุณต้องการ

เพื่อจัดการกับวัตถุใด ๆ โดยพลคุณสามารถใช้ฟังก์ชั่นการโคลนวัตถุดังกล่าวเป็นหนึ่งที่ระบุไว้ในคำตอบของอาร์มันด์หรือตั้งแต่คุณใช้ jQuery เพียงแค่ใช้ฟังก์ชั่น$.extend() ฟังก์ชันนี้จะสร้างสำเนาตื้นหรือสำเนาลึกของวัตถุ (อย่าสับสนกับ$().clone()วิธีนี้สำหรับการคัดลอกองค์ประกอบ DOM ไม่ใช่วัตถุ)

สำหรับสำเนาตื้น:

b = $.extend( {}, a );

หรือสำเนาลึก:

b = $.extend( true, {}, a );

สำเนาตื้นกับสำเนาลึกต่างกันอย่างไร สำเนาตื้นจะคล้ายกับรหัสของคุณที่สร้างวัตถุใหม่ด้วยตัวอักษรของวัตถุ สร้างอ็อบเจ็กต์ระดับบนสุดใหม่ที่มีการอ้างอิงถึงคุณสมบัติเดียวกันกับอ็อบเจ็กต์ดั้งเดิม

หากออบเจ็กต์ของคุณมีเฉพาะประเภทดั้งเดิมเช่นตัวเลขและสตริงสำเนาลึกและสำเนาตื้นจะทำสิ่งเดียวกันทุกประการ แต่ถ้าวัตถุของคุณมีวัตถุหรืออาร์เรย์อื่น ๆ ที่ซ้อนอยู่ภายในสำเนาตื้น ๆ จะไม่คัดลอกวัตถุที่ซ้อนกันเหล่านั้นเพียงสร้างการอ้างอิงถึงวัตถุเหล่านั้น ดังนั้นคุณอาจมีปัญหาเดียวกันกับวัตถุซ้อนที่คุณมีกับวัตถุระดับบนสุดของคุณ ตัวอย่างเช่นให้วัตถุนี้:

var obj = {
    w: 123,
    x: {
        y: 456,
        z: 789
    }
};

หากคุณทำสำเนาวัตถุนั้นแบบตื้นxคุณสมบัติของวัตถุใหม่ของคุณจะเป็นxวัตถุเดียวกันจากต้นฉบับ:

var copy = $.extend( {}, obj );
copy.w = 321;
copy.x.y = 654;

ตอนนี้วัตถุของคุณจะมีลักษณะดังนี้:

// copy looks as expected
var copy = {
    w: 321,
    x: {
        y: 654,
        z: 789
    }
};

// But changing copy.x.y also changed obj.x.y!
var obj = {
    w: 123,  // changing copy.w didn't affect obj.w
    x: {
        y: 654,  // changing copy.x.y also changed obj.x.y
        z: 789
    }
};

คุณสามารถหลีกเลี่ยงสิ่งนี้ได้ด้วยสำเนาลึก สำเนาแบบลึกจะวนซ้ำในทุกออบเจ็กต์และอาร์เรย์ที่ซ้อนกัน (และวันที่ในรหัสของ Armand) เพื่อทำสำเนาของวัตถุเหล่านั้นในลักษณะเดียวกับที่ทำสำเนาของวัตถุระดับบนสุด ดังนั้นการเปลี่ยนแปลงcopy.x.yจะไม่ส่งผลกระทบobj.x.yจะไม่ส่งผลกระทบต่อ

คำตอบสั้น ๆ : หากมีข้อสงสัยคุณอาจต้องการสำเนาแบบละเอียด


ขอบคุณนี่คือสิ่งที่ฉันกำลังมองหา วิธีใดที่จะหลีกเลี่ยงมัน? ในสถานการณ์ปัจจุบันของฉันแก้ไขได้ง่ายเนื่องจากฉันมีคุณสมบัติเพียง 2 รายการ แต่อาจมีวัตถุขนาดใหญ่กว่านี้
Rutwick Gangurde

1
คำตอบของ Armand มีฟังก์ชั่นการโคลนวัตถุที่จะทำเคล็ดลับ หรือเนื่องจากคุณใช้ jQuery คุณสามารถใช้$.extend()ฟังก์ชันในตัวได้ รายละเอียดด้านบน. :-)
Michael Geary

คำอธิบายที่ยอดเยี่ยม Michael!
Rutwick Gangurde

8
@MichaelGeary มันอาจจะเป็นการ nitpicking แต่กฎไม่ใช้กับวัตถุเท่านั้นไม่ใช่ตัวแปรดั้งเดิมหรือ? มันอาจคุ้มค่าที่จะสังเกตในคำตอบ
Jonathan dos Santos

วิธีแก้ปัญหา JS สำหรับสิ่งนี้อยู่ในคำตอบของ Armands เหมือนที่ Michael Geary กล่าว =
AlexanderGriffin

57

ฉันพบว่าการใช้ JSON ได้ผล แต่ดูการอ้างอิงแบบวงกลม

var newInstance = JSON.parse(JSON.stringify(firstInstance));

2
ฉันรู้ว่ามันช้าไปหน่อย แต่วิธีนี้มีปัญหาหรือไม่? ดูเหมือนว่าจะทำงานได้อย่างน่าอัศจรรย์เมื่อลองครั้งแรก
Mankind1023

2
สิ่งนี้ยอดเยี่ยมในเกือบทุกสถานการณ์อย่างไรก็ตามหากคุณกำลังทำงานกับข้อมูลที่มีความเป็นไปได้ที่จะคาดเดาได้ก็จะทำให้โปรแกรมของคุณขัดข้อง รหัสบางส่วนเพื่อแสดง: let a = {}; let b = {a:a}; a.b = b; JSON.stringify(a)จะให้ TypeError
schu34

โซลูชันนี้ใช้งานได้ดี อย่างไรก็ตามราคาแพงแค่ไหนในระหว่างการประมวลผล? ดูเหมือนว่าสิ่งนี้ใช้ได้ผลโดยการปฏิเสธสองครั้ง
Abel Callejo

30

คำถามได้รับการแก้ไขแล้วเนื่องจากค่อนข้างนาน แต่สำหรับการอ้างอิงในอนาคตวิธีแก้ปัญหาที่เป็นไปได้คือ

b = a.slice(0);

โปรดระวังสิ่งนี้จะทำงานได้อย่างถูกต้องก็ต่อเมื่อ a เป็นอาร์เรย์ของตัวเลขและสตริงที่ไม่ซ้อนกัน


28

newVariable = originalVariable.valueOf();

สำหรับวัตถุที่คุณสามารถใช้ได้ b = Object.assign({},a);


3
สำหรับวัตถุที่คุณสามารถใช้ได้ b = Object.assign ({}, a);
Kishor Patil

15

เหตุผลนี้เป็นเรื่องง่าย JavaScript ใช้b = aการอ้างอิงดังนั้นเมื่อคุณกำหนดคุณกำลังกำหนดการอ้างอิงbเมื่อทำการอัปเดตaคุณกำลังอัปเดตด้วยb

ฉันพบสิ่งนี้ใน stackoverflow และจะช่วยป้องกันสิ่งนี้ในอนาคตโดยเพียงแค่เรียกใช้เมธอดนี้หากคุณต้องการทำสำเนาลึกของวัตถุ

function clone(obj) {
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

ขอบคุณ! ฉันเห็นโพสต์นั้นก่อนที่ฉันจะโพสต์คำถามใหม่ แต่ฉันไม่แน่ใจว่าจะช่วยในสถานการณ์ของฉันได้หรือไม่ ปรากฎว่านั่นคือสิ่งที่ฉันต้องการ
Rutwick Gangurde

ไม่ควรทั้งหมดยกเว้นคนแรกif (obj instanceof x) { ... }ถ้า?
Zac

@Zac ไม่ใช่ในกรณีนี้ ทุก if-body (ที่สนใจ) ส่งคืนค่า (ออกจากฟังก์ชันก่อน if ถัดไป)
Bitterblue

8

ฉันไม่เข้าใจว่าทำไมคำตอบจึงซับซ้อนนัก ใน Javascript primitives (สตริงตัวเลข ฯลฯ ) จะถูกส่งผ่านด้วยค่าและคัดลอก ออบเจ็กต์รวมถึงอาร์เรย์จะถูกส่งผ่านโดยการอ้างอิง ไม่ว่าในกรณีใดการกำหนดค่าใหม่หรือการอ้างอิงวัตถุเป็น "a" จะไม่เปลี่ยนแปลง "b" แต่การเปลี่ยนเนื้อหาของ 'a' จะทำให้เนื้อหาของ 'b' เปลี่ยนไป

var a = 'a'; var b = a; a = 'c'; // b === 'a'

var a = {a:'a'}; var b = a; a = {c:'c'}; // b === {a:'a'} and a = {c:'c'}

var a = {a:'a'}; var b = a; a.a = 'c'; // b.a === 'c' and a.a === 'c'

วางบรรทัดด้านบน (ทีละบรรทัด) ในโหนดหรือคอนโซลจาวาสคริปต์ของเบราว์เซอร์ จากนั้นพิมพ์ตัวแปรใด ๆ และคอนโซลจะแสดงค่า


4

สำหรับสตริงหรือค่าอินพุตคุณสามารถใช้สิ่งนี้:

var a = $('#some_hidden_var').val(),
b = a.substr(0);

ทำไมคุณถึงตั้งค่าดั้งเดิม? เพียงแค่กำหนดมันก็จะคัดลอกสตริง เฉพาะอ็อบเจ็กต์และอาร์เรย์ (ซึ่งเป็นอ็อบเจ็กต์) เท่านั้นที่ถูกส่งผ่านโดยการอ้างอิง
เทรนตันดีอดัมส์

3

คำตอบส่วนใหญ่ที่นี่ใช้วิธีการในตัวหรือใช้ไลบรารี / เฟรมเวิร์ก วิธีง่ายๆนี้น่าจะใช้ได้ดี:

function copy(x) {
    return JSON.parse( JSON.stringify(x) );
}

// Usage
var a = 'some';
var b = copy(a);
a += 'thing';

console.log(b); // "some"

var c = { x: 1 };
var d = copy(c);
c.x = 2;

console.log(d); // { x: 1 }

อัจฉริยะ! วิธีอื่นเปลี่ยนทุกอย่างให้เป็นพจนานุกรมรวมถึงอาร์เรย์ที่อยู่ภายใน ด้วยสิ่งนี้ฉันจึงโคลนวัตถุและเนื้อหายังคงเหมือนเดิมไม่เหมือนกับสิ่งนี้:Object.assign({},a)
Tiki

0

ฉันแก้ไขมันด้วยตัวเองในขณะนี้ ค่าเดิมมีเพียง 2 คุณสมบัติย่อย ผมปฏิรูปวัตถุใหม่ที่มีคุณสมบัติจากนั้นได้รับมอบหมายให้มันa bตอนนี้ตัวจัดการเหตุการณ์ของฉันอัปเดตเท่านั้นbและต้นฉบับของฉันaยังคงเหมือนเดิม

var a = { key1: 'value1', key2: 'value2' },
    b = a;

$('#revert').on('click', function(e){
    //FAIL!
    b = a;

    //WIN
    b = { key1: a.key1, key2: a.key2 };
});

ใช้งานได้ดี ฉันไม่ได้เปลี่ยนบรรทัดเดียวในโค้ดของฉันยกเว้นด้านบนและมันทำงานตามที่ฉันต้องการ ดังนั้นเชื่อฉันไม่มีอะไรอัปเดตaอีก


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.