การเปรียบเทียบเชิงลึกระหว่าง Object.assign ของ JavaScript และ spread operator สำหรับการจัดการอ็อบเจกต์ รวมถึงเกณฑ์มาตรฐานประสิทธิภาพและตัวอย่างกรณีการใช้งานจริง
JavaScript Object.assign vs Spread: การเปรียบเทียบประสิทธิภาพและกรณีการใช้งาน
JavaScript มีหลายวิธีในการจัดการอ็อบเจกต์ สองวิธีที่นิยมใช้กันคือ Object.assign()
และ spread operator (...
) ทั้งสองวิธีช่วยให้คุณสามารถคัดลอก properties จากอ็อบเจกต์ต้นทางหนึ่งหรือหลายตัวไปยังอ็อบเจกต์เป้าหมายได้ อย่างไรก็ตาม ทั้งสองวิธีมีความแตกต่างกันในด้าน синтаксис (syntax), ประสิทธิภาพ และกลไกการทำงานเบื้องหลัง บทความนี้จะเปรียบเทียบอย่างละเอียดเพื่อช่วยให้คุณเลือกเครื่องมือที่เหมาะสมกับกรณีการใช้งานของคุณ
ทำความเข้าใจ Object.assign()
Object.assign()
คือเมธอดที่คัดลอก enumerable own properties ทั้งหมดจากอ็อบเจกต์ต้นทางหนึ่งหรือหลายตัวไปยังอ็อบเจกต์เป้าหมาย โดยจะแก้ไขอ็อบเจกต์เป้าหมายโดยตรงและส่งคืนค่านั้นกลับมา синтаксис (syntax) พื้นฐานคือ:
Object.assign(target, ...sources)
target
: อ็อบเจกต์เป้าหมายที่จะคัดลอก properties ไปใส่ อ็อบเจกต์นี้จะถูกแก้ไขsources
: อ็อบเจกต์ต้นทางหนึ่งหรือหลายตัวที่จะคัดลอก properties ออกมา
ตัวอย่าง:
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target); // Output: { a: 1, b: 4, c: 5 }
console.log(returnedTarget === target); // Output: true
ในตัวอย่างนี้ properties ของ source
ถูกคัดลอกไปยัง target
สังเกตว่า property b
ถูกเขียนทับ และ returnedTarget
คืออ็อบเจกต์เดียวกันกับ target
ทำความเข้าใจ Spread Operator
Spread operator (...
) ช่วยให้คุณสามารถกระจาย iterable (เช่น array หรือ object) ออกเป็นองค์ประกอบแต่ละตัวได้ เมื่อใช้กับอ็อบเจกต์ มันจะสร้าง shallow copy ของ properties ของอ็อบเจกต์ไปยังอ็อบเจกต์ใหม่ синтаксис (syntax) นั้นเรียบง่าย:
const newObject = { ...sourceObject };
ตัวอย่าง:
const source = { a: 1, b: 2 };
const newObject = { ...source };
console.log(newObject); // Output: { a: 1, b: 2 }
console.log(newObject === source); // Output: false
ในที่นี้ newObject
จะมีสำเนาของ properties ของ source
อยู่ สิ่งสำคัญคือ newObject
เป็นอ็อบเจกต์ *ใหม่* ซึ่งแยกจาก source
ความแตกต่างที่สำคัญ
แม้ว่าทั้ง Object.assign()
และ spread operator จะให้ผลลัพธ์ที่คล้ายกัน (การคัดลอก properties ของอ็อบเจกต์) แต่ก็มีความแตกต่างที่สำคัญ:
- Immutability: Spread operator จะสร้างอ็อบเจกต์ใหม่โดยไม่เปลี่ยนแปลงอ็อบเจกต์เดิม (immutable) ส่วน
Object.assign()
จะแก้ไขอ็อบเจกต์เป้าหมายโดยตรง (mutable) - การจัดการอ็อบเจกต์เป้าหมาย:
Object.assign()
อนุญาตให้คุณระบุอ็อบเจกต์เป้าหมายได้ ในขณะที่ spread operator จะสร้างอ็อบเจกต์ใหม่เสมอ - Property Enumerability: ทั้งสองวิธีจะคัดลอกเฉพาะ enumerable properties เท่านั้น ส่วน non-enumerable properties จะไม่ถูกคัดลอก
- Inherited Properties: ไม่มีวิธีใดคัดลอก inherited properties จาก prototype chain
- Setters:
Object.assign()
จะเรียกใช้ setters บนอ็อบเจกต์เป้าหมาย แต่ spread operator จะไม่ทำเช่นนั้น มันจะกำหนดค่าโดยตรง - แหล่งข้อมูลที่เป็น Undefined หรือ Null:
Object.assign()
จะข้ามอ็อบเจกต์ต้นทางที่เป็นnull
และundefined
ไป แต่การใช้ spread กับnull
หรือundefined
จะทำให้เกิด error
การเปรียบเทียบประสิทธิภาพ
ประสิทธิภาพเป็นข้อพิจารณาที่สำคัญ โดยเฉพาะเมื่อต้องจัดการกับอ็อบเจกต์ขนาดใหญ่หรือการดำเนินการบ่อยครั้ง การทดสอบ microbenchmark แสดงให้เห็นอย่างสม่ำเสมอว่า spread operator โดยทั่วไปเร็วกว่า Object.assign()
ความแตกต่างนี้เกิดจากการทำงานภายในของทั้งสองวิธี
ทำไม Spread Operator ถึงเร็วกว่า?
Spread operator มักได้รับประโยชน์จากการปรับปรุงประสิทธิภาพภายในของ JavaScript engines มันถูกออกแบบมาโดยเฉพาะสำหรับการสร้างอ็อบเจกต์และอาร์เรย์ ซึ่งช่วยให้ engines สามารถทำการปรับปรุงประสิทธิภาพที่ไม่สามารถทำได้กับ Object.assign()
ที่มีวัตถุประสงค์การใช้งานทั่วไปกว่า Object.assign()
ต้องจัดการกับกรณีต่างๆ มากมาย รวมถึง setters และ property descriptors ที่แตกต่างกัน ทำให้มีความซับซ้อนโดยเนื้อแท้
ตัวอย่าง Benchmark (เพื่อการอธิบาย):
// Simplified example (actual benchmarks require more iterations and robust testing)
const object1 = { a: 1, b: 2, c: 3, d: 4, e: 5 };
const object2 = { f: 6, g: 7, h: 8, i: 9, j: 10 };
// Using Object.assign()
console.time('Object.assign');
for (let i = 0; i < 1000000; i++) {
Object.assign({}, object1, object2);
}
console.timeEnd('Object.assign');
// Using Spread Operator
console.time('Spread Operator');
for (let i = 0; i < 1000000; i++) {
({...object1, ...object2 });
}
console.timeEnd('Spread Operator');
หมายเหตุ: นี่เป็นเพียงตัวอย่างพื้นฐานเพื่อการอธิบายเท่านั้น การทดสอบ benchmark ในโลกแห่งความเป็นจริงควรใช้ไลบรารีเฉพาะทาง (เช่น Benchmark.js) เพื่อผลลัพธ์ที่แม่นยำและเชื่อถือได้ ขนาดของความแตกต่างด้านประสิทธิภาพอาจแตกต่างกันไปขึ้นอยู่กับ JavaScript engine, ขนาดของอ็อบเจกต์ และการดำเนินการที่เฉพาะเจาะจง
กรณีการใช้งาน: Object.assign()
แม้ว่า spread operator จะมีข้อได้เปรียบด้านประสิทธิภาพ แต่ Object.assign()
ก็ยังคงมีประโยชน์ในสถานการณ์เฉพาะ:
- การแก้ไขอ็อบเจกต์ที่มีอยู่: เมื่อคุณต้องการอัปเดตอ็อบเจกต์เดิม (mutation) แทนที่จะสร้างอ็อบเจกต์ใหม่ ซึ่งเป็นเรื่องปกติเมื่อทำงานกับไลบรารีการจัดการ state แบบ mutable หรือเมื่อประสิทธิภาพเป็นสิ่งสำคัญอย่างยิ่ง และคุณได้ทำการ profiled โค้ดของคุณเพื่อยืนยันว่าการหลีกเลี่ยงการจัดสรรอ็อบเจกต์ใหม่นั้นคุ้มค่า
- การรวมหลายอ็อบเจกต์เข้ากับเป้าหมายเดียว:
Object.assign()
สามารถรวม properties จากอ็อบเจกต์ต้นทางหลายตัวเข้ากับเป้าหมายเดียวได้อย่างมีประสิทธิภาพ - การทำงานกับสภาพแวดล้อม JavaScript ที่เก่ากว่า: Spread operator เป็นฟีเจอร์ของ ES6 หากคุณต้องการสนับสนุนเบราว์เซอร์หรือสภาพแวดล้อมที่เก่ากว่าซึ่งไม่รองรับ ES6
Object.assign()
จะเป็นทางเลือกที่เข้ากันได้ (แม้ว่าคุณอาจต้องใช้ polyfill) - การเรียกใช้ Setters: หากคุณต้องการเรียกใช้ setters ที่กำหนดไว้ในอ็อบเจกต์เป้าหมายระหว่างการกำหนด property
Object.assign()
คือตัวเลือกที่ถูกต้อง
ตัวอย่าง: การอัปเดต State แบบ Mutable
let state = { name: 'Alice', age: 30 };
function updateName(newName) {
Object.assign(state, { name: newName }); // Mutates the 'state' object
}
updateName('Bob');
console.log(state); // Output: { name: 'Bob', age: 30 }
กรณีการใช้งาน: Spread Operator
Spread operator โดยทั่วไปเป็นที่นิยมมากกว่าเนื่องจากข้อดีด้าน immutability และประสิทธิภาพในการพัฒนา JavaScript สมัยใหม่ส่วนใหญ่:
- การสร้างอ็อบเจกต์ใหม่พร้อมกับ properties ที่มีอยู่: เมื่อคุณต้องการสร้างอ็อบเจกต์ใหม่พร้อมสำเนาของ properties จากอ็อบเจกต์อื่น ซึ่งมักจะมีการแก้ไขบางอย่าง
- Functional Programming: Spread operator สอดคล้องกับหลักการของ functional programming ซึ่งเน้นเรื่อง immutability และการหลีกเลี่ยง side effects
- การอัปเดต State ใน React: ใน React, spread operator ถูกใช้อย่างแพร่หลายเพื่อสร้างอ็อบเจกต์ state ใหม่เมื่ออัปเดต component state แบบ immutably
- Redux Reducers: Redux reducers มักใช้ spread operator เพื่อคืนค่าอ็อบเจกต์ state ใหม่ตาม actions ที่ได้รับ
ตัวอย่าง: การอัปเดต React State แบบ Immutably
import React, { useState } from 'react';
function MyComponent() {
const [state, setState] = useState({ name: 'Charlie', age: 35 });
const updateAge = (newAge) => {
setState({ ...state, age: newAge }); // Creates a new state object
};
return (
<div>
<p>Name: {state.name}</p>
<p>Age: {state.age}</p>
<button onClick={() => updateAge(36)}>Increment Age</button>
</div>
);
}
export default MyComponent;
Shallow Copy vs. Deep Copy
สิ่งสำคัญที่ต้องเข้าใจคือทั้ง Object.assign()
และ spread operator ทำการคัดลอกแบบ *shallow* copy ซึ่งหมายความว่ามีเพียง properties ระดับบนสุดเท่านั้นที่ถูกคัดลอก หากอ็อบเจกต์มีอ็อบเจกต์หรืออาร์เรย์ซ้อนอยู่ภายใน เฉพาะการอ้างอิง (references) ไปยังโครงสร้างที่ซ้อนกันเหล่านั้นเท่านั้นที่จะถูกคัดลอก ไม่ใช่ตัวโครงสร้างที่ซ้อนกันเอง
ตัวอย่างของ Shallow Copy:
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
copy.a = 3; // Modifies 'copy.a', but 'original.a' remains unchanged
copy.b.c = 4; // Modifies 'original.b.c' because 'copy.b' and 'original.b' point to the same object
console.log(original); // Output: { a: 1, b: { c: 4 } }
console.log(copy); // Output: { a: 3, b: { c: 4 } }
ในการสร้าง *deep* copy (ที่ซึ่งอ็อบเจกต์ที่ซ้อนกันอยู่ก็ถูกคัดลอกด้วย) คุณต้องใช้เทคนิคต่างๆ เช่น:
JSON.parse(JSON.stringify(object))
: นี่เป็นวิธีที่ง่ายแต่อาจช้า ไม่สามารถใช้กับฟังก์ชัน, วันที่, หรือ circular references ได้_.cloneDeep()
ของ Lodash: ฟังก์ชันยูทิลิตี้จากไลบรารี Lodash- ฟังก์ชัน recursive ที่สร้างขึ้นเอง: มีความซับซ้อนกว่าแต่ให้การควบคุมกระบวนการ deep copy ได้มากที่สุด
- Structured Clone: ใช้
window.structuredClone()
สำหรับเบราว์เซอร์หรือแพ็คเกจstructuredClone
สำหรับ Node.js เพื่อ deep copy อ็อบเจกต์
แนวทางปฏิบัติที่ดีที่สุด
- เลือกใช้ Spread Operator เพื่อ Immutability: ในแอปพลิเคชัน JavaScript สมัยใหม่ส่วนใหญ่ ควรเลือกใช้ spread operator เพื่อสร้างอ็อบเจกต์ใหม่แบบ immutably โดยเฉพาะเมื่อทำงานกับการจัดการ state หรือ functional programming
- ใช้ Object.assign() สำหรับการแก้ไขอ็อบเจกต์ที่มีอยู่: เลือกใช้
Object.assign()
เมื่อคุณต้องการแก้ไขอ็อบเจกต์ที่มีอยู่ ณ ที่เดิมโดยเฉพาะ - ทำความเข้าใจ Shallow vs. Deep Copy: โปรดทราบว่าทั้งสองวิธีทำการคัดลอกแบบ shallow copy ใช้เทคนิคที่เหมาะสมสำหรับการทำ deep copy เมื่อจำเป็น
- ทำการ Benchmark เมื่อประสิทธิภาพเป็นสิ่งสำคัญ: หากประสิทธิภาพมีความสำคัญสูงสุด ให้ทำการทดสอบ benchmark อย่างละเอียดเพื่อเปรียบเทียบประสิทธิภาพของ
Object.assign()
และ spread operator ในกรณีการใช้งานเฉพาะของคุณ - พิจารณาความสามารถในการอ่านโค้ด: เลือกวิธีที่ทำให้โค้ดอ่านง่ายและบำรุงรักษาได้ดีที่สุดสำหรับทีมของคุณ
ข้อควรพิจารณาในระดับนานาชาติ
พฤติกรรมของ Object.assign()
และ spread operator โดยทั่วไปจะสอดคล้องกันในสภาพแวดล้อม JavaScript ต่างๆ ทั่วโลก อย่างไรก็ตาม มีข้อควรพิจารณาที่เป็นไปได้ดังต่อไปนี้:
- การเข้ารหัสตัวอักษร (Character Encoding): ตรวจสอบให้แน่ใจว่าโค้ดของคุณจัดการกับการเข้ารหัสตัวอักษรอย่างถูกต้อง โดยเฉพาะเมื่อต้องจัดการกับสตริงที่มีตัวอักษรจากภาษาต่างๆ ทั้งสองวิธีคัดลอก property ที่เป็นสตริงได้อย่างถูกต้อง แต่ปัญหาการเข้ารหัสอาจเกิดขึ้นเมื่อประมวลผลหรือแสดงผลสตริงเหล่านี้
- รูปแบบวันที่และเวลา (Date and Time Formats): เมื่อคัดลอกอ็อบเจกต์ที่มีวันที่ โปรดระวังเรื่องโซนเวลาและรูปแบบวันที่ หากคุณต้องการ serialize หรือ deserialize วันที่ ให้ใช้วิธีที่เหมาะสมเพื่อให้แน่ใจว่ามีความสอดคล้องกันในภูมิภาคต่างๆ
- รูปแบบตัวเลข (Number Formatting): ภูมิภาคต่างๆ ใช้รูปแบบการจัดรูปแบบตัวเลขที่แตกต่างกัน (เช่น ตัวคั่นทศนิยม, ตัวคั่นหลักพัน) โปรดระวังความแตกต่างเหล่านี้เมื่อคัดลอกหรือจัดการอ็อบเจกต์ที่มีข้อมูลตัวเลขที่อาจแสดงให้ผู้ใช้ในภาษาต่างๆ
สรุป
Object.assign()
และ spread operator เป็นเครื่องมือที่มีค่าสำหรับการจัดการอ็อบเจกต์ใน JavaScript โดยทั่วไปแล้ว spread operator ให้ประสิทธิภาพที่ดีกว่าและส่งเสริม immutability ทำให้เป็นตัวเลือกที่นิยมในแอปพลิเคชัน JavaScript สมัยใหม่จำนวนมาก อย่างไรก็ตาม Object.assign()
ยังคงมีประโยชน์สำหรับการแก้ไขอ็อบเจกต์ที่มีอยู่และสนับสนุนสภาพแวดล้อมที่เก่ากว่า การทำความเข้าใจความแตกต่างและกรณีการใช้งานของทั้งสองจะช่วยให้คุณเขียนโค้ด JavaScript ที่มีประสิทธิภาพ บำรุงรักษาง่าย และแข็งแกร่งยิ่งขึ้น