---
description: https://viblo.asia/p/destructuring-assignment-in-es6-xlbRBNQgRDM
---

# Destructuring Assignment in ES6

![](https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M1E4Gk2ppVKb4olmnun%2F-M1El0IyFdFIvso2OlPk%2F-M1ElRIFSl31m9EZxWCr%2F3f6379e8-7c8b-43ea-bcea-698009f1bfd4.png?alt=media\&token=b9a34c35-f8e3-4a1d-85f0-a7818028b643)

### Introduction <a href="#introduction-0" id="introduction-0"></a>

**ES2015** (hay còn gọi với cái tên quen thuộc hơn là **ES6**) giới thiệu khá nhiều thay đổi về mặt cú pháp giúp cho việc trình bày logic trở nên gọn gàng và dễ hiểu hơn. Một trong những thay đổi đó là việc giới thiệu một cú pháp mới - **Destructuring** hay **Destructuring Assignment** với tên gọi đầy đủ. Chắc hẳn chúng ta đã khá quên thuộc với các phép gán (assignment), vậy việc thêm từ _destructuring_ phía trước có gì thay đổi không? Nếu chỉ dựa vào tên gọi của cú pháp chúng ta sẽ cảm thấy khá khó hiểu và có thể đặt ra một loạt các câu hỏi liên quan đến cái tên đơn giản đó.

Trong bài viết này, chúng ta sẽ tập trung tìm hiểu về **Destructuring Assignment**, từ những định nghĩa tổng quát, đến cách sử dụng cũng như đi sâu tìm hiểu cốt lõi bên trong của cú pháp đó.

### General Definition <a href="#general-definition-1" id="general-definition-1"></a>

> **Destructuring Assignment** là một cú pháp cho phép tách dữ liệu được lưu trữ bên trong (nested) Objects hoặc Arrays (tổng quát hơn là các iterable values) và gán chúng cho các biến riêng biệt.

Từ định nghĩa trên chúng ta có thể chia **Destructuring Assignment** ra thành hai nhóm chính:

* **Object Destructuring**
* **Array Destructuring**

Trước khi tìm hiểu từng nhóm trên, chúng ta hay cùng xem qua một vài ví dụ đơn giản sau:

**Object Destructuring**

```
const person = { first: 'Foo', last: 'Bar' };
const {first, last} = person;

console.log(first); // Foo
console.log(last);  // Bar

//------------------------------------------
// Aliases

let characters = {a: 'a', b: 'b', c: 'c'};
let {a: d, b: e, c: f} = characters;

console.log(d, e, f); // a b c
console.log(a);       // Uncaught ReferenceError: a is not defined

//------------------------------------------
// Vue Resource - Fetching notifications from API endpoint

this.$http.get('/api/notifications')
	.then(({data}) => {
		let {counter, notifications} = data;
		
		this.unreadCounter = counter;
		this.notifications = notifications;
	});
```

**Array Destructuring**

```
let characters = ['a', 'b', 'c'];
let [d, e, f] = characters;

console.log(d, e, f); // a b c
```

Khi nói đến phép gán thông thường, chúng ta thường thấy các cú pháp về mảng hay object xuất hiện ở phía bên phải (right-hand side - RHS) của phép gán và được gọi là _assigning values_. Tuy nhiên khi _destructure_ một mảng hay một đối tượng, các cú pháp trên sẽ xuất hiện phía bên trái (left-hand side - LHS) của phép gán; trong khí đó RHS của phép gán sẽ là giá trị cần _destructuring_.

Như vậy, hiểu một cách đơn giản, **Destructuring Assignment** sẽ là sự đảo ngược của **Normal Assignment**? Hay hiểu đơn giản hơn nữa, chúng ta lấy một phép gán thông thường và _flip_ nó, kết quả thu được sẽ là một **Destructuring Assignment**?

Tuy nhiên trên thưc tế, nó không hoàn toàn giống như chúng ta nghĩ, đó cũng là lí do tại sao chúng ta lại đặt dấu (?) cho hai nhận xét phía trên. Trong phần sau của bài viết, chúng ta sẽ tìm hiểu rõ hơn về **Object Destructuring** và **Array Destructuring**.

```
// Normal assignment
let numbers = [1, 2, 3];
let person = { name: 'Captain America', age: 97 };

// Destructuring assignment
let [a, b, c] = numbers;
let {name, age} = person;
```

### Array Destructuring <a href="#array-destructuring-2" id="array-destructuring-2"></a>

Khi sử dụng **Array Destructuring**, trên thực tế một _iterator_ sẽ được sử dụng để tách các giá trị cần thiết ra khỏi _destructuring source_ (hay giá trị ở RHS của phép gán). Do đó, chúng ta có thể sử dụng **Array Destructuring** cho các _iterable values_ (không phải chỉ riêng các arrays), cụ thể sẽ là _strings, arrays, sets, maps, function arguments, DOM elements_. Chúng ta cũng có thể sử dụng **Array Destructuring** với các toán tử như [**Spread**](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator) nếu cần thiết. Dưới đây là một số ví dụ minh họa:

**Strings**

```
let message = 'Hello';
let [a, b] = message;
let [x, y, ...z] = message;

console.log(a, b);    // H e
console.log(x, y, z); // H e ['l', 'l', 'o']
```

**Arrays**

```
let numbers = [101, 102, 103];
let [x, y, z] = numbers;

console.log(x, y, z); // 101 102 103
```

**Sets**

```
let set = new Set().add('foo').add('bar');
let [a, b] = set;

console.log(a, b); // foo bar
```

**Maps**

```
let map = new Map().set('a', 1).set('b', 2);
let [x, y] = map;

console.log(x, y); // ["a", 1] ["b", 2]
```

Dưới đây sẽ là một số ví dụ mà việc sử dụng **Array Destructuring** là không khả thi:

```
// Empty object
let obj = {};
let [x] = obj; // TypeError

// Undefined values
let und = undefined;
let [x] = und; // TypeError

// Null values
let response = null;
let[x] = response; // TypeError
```

### Object Destructuring <a href="#object-destructuring-3" id="object-destructuring-3"></a>

Chúng ta sẽ bắt đầu bằng một ví dụ đơn giản. Giả sử chúng ta có một function `foo()` trả về một object với các property tương ứng là x, y và z:

```
function foo() {
    return {
        x: 1, 
        y: 2,
        z: 3
    };
}
```

**Object Destructuring** hay còn gọi với một tên khác là **Object Property Assignment Pattern** cho phép chúng ta gán property value của một object cho các biến tương ứng. Chúng ta sẽ có hai cách viết như sau:

```
let {x: x, y: y, z: z} = foo();
let {x, y, z} = foo();
```

Cách viết đầu tiên là dạng đầy đủ của **Object Destructuring** trong đó mỗi property của object sẽ được gán với một biến xác định dưới dạng `<<property>>: <<variable>>`. Tuy nhiên nếu tên của biến trùng với tên của property, chúng ta có thể loại bỏ phần `<<property>>:` trong cú pháp trên và kết quả thu được sẽ là cách viết thứ hai (shortened syntax). Trong trường hợp chúng ta muốn gán property của object cho một biến với tên khác so với tên của property, cách viết đầu tiên sẽ được sử dụng.

```
let {x: a, y: b, z: c} = foo();

console.log(a, b, c); // 1 2 3
console.log(x);       // Uncaught ReferenceError: x is not defined
```

Chúng ta sẽ dành một chút thời gian để trả lời câu hỏi ở phần trước; liệu `{x: a, y: b, z: c}` khi đứng ở LHS và RHS của phép gán có gì khác nhau không?

Nếu để ý kĩ chúng ta sẽ thấy trong ví dụ về **Object Literal**, _x, y, và z_ là các property của object `bar` còn _a, b, và c_ sẽ là các _source value_ được sử dụng để gán cho các property của object `bar`.

Hiểu một cách đơn giản hơn, trong **Object Literal**, các property sẽ được gán giá trị dưới dạng **target <= source**. Tuy nhiên khi xem xét **Object Destructuring** thứ tự sẽ bị đảo ngược thành **source => target** (not **source <= target**).

Khi sử dụng **Object Destructuring** trên một giá trị cụ thể, giá trị đó sẽ được ép kiểu về _Object_. Tuy nhiên việc ép kiểu không sử dụng `Object()` constructor mà sẽ sử dụng `toObject()`. Điểm khác nhau cơ bản giữa hai phương thức này là _object constructor_ sẽ convert các giá trị `null` và `undefined` về empty object còn `toObject` sẽ trả về một exception (TypeError).

```
Object('abc');     // String abc
Object(123);       // Number 123
Object(undefined); // Object {}
Object(null);      // Object {}

let {x} = undefined; // TypeError
let {y} = null;      // TypeError
```

### Default Values <a href="#default-values-4" id="default-values-4"></a>

Khi sử dụng **Destructuring Assignment** chúng ta có thể gán giá trị mặc định cho các _target value_ (LHS) sử dụng `=` syntax, tương tự khi ta gán giá trị mặc định cho _function arguments_. Chúng ta sẽ xét một số ví dụ đơn giản sau đây (chúng ta sẽ sử dụng lại function `foo()` ở phía trên để tiện cho việc minh họa).

```
let {x = 4, y = 5, z = 6, t = 10} = foo();
console.log(x, y, z, t); // 1 2 3 10

let {x = 4, y = 5, z = 6, t} = foo();
console.log(x, y, z, t); // 1 2 3 undefined
```

Chú ý:

```
function foo() {
  return {
  };
}
let { x = 4, y = 5, z = 6, t = 10 } = foo();
```

Như chúng ta thấy, nếu property của object tồn tại, giá trị của property đó sẽ được sử dụng. Nếu property không tồn tại giá trị mặc định sẽ được sử dụng (hoặc `undefined` sẽ được trả về trong trường hợp không có giá trị mặc định nào được cung cấp). Tuy nhiên việc sử dụng _default value_ khi _destructure_ một mảng hay đối tượng nếu không được xem xét kĩ sẽ làm cho logic trở nên khó hiểu cũng như khó debug hơn.

Nếu giá trị được gán là `undefined`, _default value_ sẽ được sử dụng:

```
const [x = 1] = [undefined];
console.log(x); // 1

const [y = 2] = {y: undefined};
console.log(y); // 2
```

_Default value_ không nhất thiết phải là một giá trị cố định, nó có thể là kết quả của _function invocation_,... hay đúng hơn nó có thể là một **Computed Property**:

```
function foo() {
    return 1;
}

let obj1 = {x: 2};
let obj2 = {x: undefined};

let {x=foo()} = obj1;
console.log(x); // 2

let {x=foo()} = obj2;
console.log(x); // 1
```

_Default value_ có thể là giá trị của các biến khác trong LHS của **Destructuring Assignment**, tuy nhiên thứ tự khai báo của các biến là quan trọng trong trường hợp này:

```
const [x=3, y=x] = [];     // x=3; y=3
const [x=3, y=x] = [7];    // x=7; y=7
const [x=3, y=x] = [7, 2]; // x=7; y=2
const [x=y, y=3] = [];     // ReferenceError
```

### Nested Destructuring && Destructuring Parameters <a href="#nested-destructuring--destructuring-parameters-5" id="nested-destructuring--destructuring-parameters-5"></a>

#### Nested Destructuring <a href="#nested-destructuring-6" id="nested-destructuring-6"></a>

Trong các ví dụ từ đầu đến thời điểm này, các giá trị được _destructure_ chỉ là các mảng và đối tượng đơn giản. Trên thực tế **Destructuring Assignment** có thể được sử dụng cho cả _nested arrays_ và _nested objects_. Việc _destructure_ object có thể được sử dụng để làm phẳng (flatten một object). Dưới đây là một số ví dụ đơn giản:

```
let numbers = [1, [2, 3, 4], 5];
let [a, [b, c, d], e] = numbers;
console.log(a, b, c, d, e); // 1 2 3 4 5

let person = { name: 'Foo', age: 15, information: {address: 'Bar', phone: '0199999999'} };
let {name, age, information: {address, phone}} = person;
console.log(name);    // Foo
console.log(age);     // 15
console.log(address); // Bar
console.log(phone);   // 0199999999
```

#### Destructuring Parameters <a href="#destructuring-parameters-7" id="destructuring-parameters-7"></a>

Khi chúng ta thực thi một function với các arguments, trên thực tế các arguments đó sẽ được gán cho các parameters được định nghĩa trong _function definition_. Do đó, chúng ta hoàn toàn có thể sử dụng **Destructuring Assignment** cho function parameters. Dưới đây là một số ví dụ đơn giản:

```
// Array destructuring for parameters
function foo([a, b]) {
    console.log(a + b);
}
foo([1, 2]); // 3

// Object destructuring for parameters
function bar({x, y}) {
    console.log(x, y);
}
foo({x: 1, y: 2}); // 1 2
foo({});           // undefined undefined
```

Chú ý:

```
// Array destructuring for parameters
	function foo([a, b]) {
	    console.log(a + b);
	}
	foo([1, 2]); // 3
	function bar({x, y}) {
	    console.log(x + y);
	}
	bar({x: 1, y: 2}); // 3
```

Default values và các biến thể của **Destructuring Assignment** đề cập trong các ví dụ nói trên đều có thể áp dụng với **Destructuring Parameters**. Tuy nhiên chúng ta cần lưu ý khi làm việc với các default values do chúng có thể gây ra những nhầm lẫn không đáng có, chúng ta hay xem xét ví dụ dưới đây:

```
function foo({ x = 5 } = {}, { y } = { y: 5 }) {
    console.log(x, y);
}

foo({}, {y: 10});          // 5 10
foo({x: 15}, {});          // 15 undefined
foo({}, {});               // 5 undefined
foo({x: 15}, {y: 10});     // 15 10
foo({}, undefined);        // 5 5
foo(undefined, undefined); // 5 5
foo({});                   // 5 5
```

Trong function `foo()` phía trên, chúng ta có hai parameters là `x` và `y` và chúng đều được gán các giá trị mặc đinh sử dụng **Object Destructuring**. Đối với parameter `x`, nó sẽ được gán giá trị mặc định là 5 và nó sẽ nhận giá trị mặc định đó nếu như argument đầu tiên truyền cho function `foo()` không có property với tên là x hoặc argument đó là `undefined`. Trong trường hợp parameter `y`, thoạt nhìn chúng ta sẽ nghĩ cũng tương tự như parameter `x` nếu argument thứ hai truyền cho function `foo()` là một empty object, giá trị mặc định 5 sẽ được sử dụng. Tuy nhiên, điều đó là không đúng. Sự khác biệt ở đây là việc gán giá trị mặc định cho parameter `x` xảy ra bên trong _destructuring pattern_ còn parameter `y` thì không. Do đó, nếu truyền một empty object cho cả hai argument của function `foo()`. Cả hai parameter sẽ đều sử dụng **Object Destructuring** để lấy ra giá trị cần thiết và kết quả sẽ là khác nhau:

```
{x = 5} = {}; // x <= 5
{y} = {};     // y <= undefined
```

### Conclusion <a href="#conclusion-8" id="conclusion-8"></a>

Trong bài viết ngắn này, chúng ta đã đề cập đến một cú pháp khá mới trong **ES2015** - **Destructuring Assginment**, cách sử dụng cú pháp đó cũng nhưng những vấn đề cần lưu ý khi sử dụng nó. Việc sử dụng **Destructuring Assignment** đem lại khá nhiều lợi ích tuy nhiên chúng ta cũng không nên lạm dụng nó hoặc sử dụng nó không đúng cách. Chúng ta đều không muốn mất cả vài phút đồng hồ để _decode_ những logic chúng ta chỉ mới viết một tháng trước đúng không? Hi vọng bài viết sẽ giúp ích được cho các bạn một phần nào đó khi tìm hiều về **ES2015** nói chung và **Destructuring Assignment** nói riêng.
