Những câu hỏi hay trong Javascript Phần 5

MVT
Đang cập nhật

Tiếp nối với 3 phần trước trong series Những câu hỏi hay trong Javascript, rất vui khi trở lại đây cùng các bạn trong phần 4, dưới đây sẽ là danh sách các câu hỏi và đáp án theo thứ tự tiếp nối với phần 1, 2 và 3.

101. Lexical Scope là gì?

let name = 'Lydia';

function getName() {
  console.log(name);
  let name = 'Sarah';
}

getName();

A: Lydia

B: Sarah

C: undefined

D: ReferenceError

Mỗi hàm có execution scope(hoặc phạm vi) riêng. Trước tiên, hàm getName xét trong ngữ cảnh (phạm vi) của chính nó để xem nó có chứa tên biến mà chúng ta đang cố truy cập hay không. Trong trường hợp này, hàm getName chứa biến name của nó: chúng ta khai báo tên biến với từ khóa let và với giá trị là 'Sarah'

Các biến có từ khóa let (và const) được đưa lên đầu scope, nhưng không giống như var, chúng không thể truy cập trước khi chúng ta khai báo (khởi tạo) chúng. Khi chúng ta cố gắng truy cập các biến trước khi chúng được khai báo, JavaScript sẽ ném ra một ReferenceError.

Nếu chúng ta không khai báo biến tên trong hàm getName, thì javascript engine sẽ xem xét scope chain (chuỗi phạm vi). Phạm vi bên ngoài có một biến được gọi là tên với giá trị là Lydia. Trong trường hợp đó, nó sẽ ghi lại Lydia.

102. yieldyield* trong Generator function

function* generatorOne() {
  yield ['a', 'b', 'c'];
}

function* generatorTwo() {
  yield* ['a', 'b', 'c'];
}

const one = generatorOne();
const two = generatorTwo();

console.log(one.next().value);
console.log(two.next().value);

A: a and a

B: a and undefined

C: ['a', 'b', 'c'] and a

D: a and ['a', 'b', 'c']

Với từ khóa yield, chúng mang lại giá trị trong hàm. Với từ khóa yield*, chúng có thể mang lại các giá trị từ một hàm generator khác hoặc đối tượng có thể lặp lại (ví dụ một mảng).

Trong generatorOne, chúng ta tạo ra toàn bộ mảng ['a', 'b', 'c'] bằng cách sử dụng từ khóa yield. Giá trị của thuộc tính value trên đối tượng được phương thức next trả về trên one (one.next (). Value) bằng toàn bộ mảng ['a', 'b', 'c'].

console.log(one.next().value); // ['a', 'b', 'c'] console.log(one.next().value); // undefined

Trong generatorTwo chúng ta sử dụng từ khóa yield*. Điều này có nghĩa là giá trị mang lại đầu tiên của two, bằng với giá trị mang lại đầu tiên trong vòng lặp. Trình lặp là mảng ['a', 'b', 'c']. Giá trị được tạo ra đầu tiên là a, vì vậy lần đầu tiên chúng ta gọi giá trị two.next().value., a được trả về.

103. Thay đổi giá trị của một biến khi được gán setInterval

let config = {
  alert: setInterval(() => {
    console.log('Alert!');
  }, 1000),
};

config = null;

A: The setInterval callback won't be invoked

B: The setInterval callback gets invoked once

C: The setInterval callback will still be called every second

D: We never invoked config.alert(), config is null

Thông thường khi chúng ta đặt các đối tượng bằng null, các đối tượng đó sẽ được thu gom rác vì không còn tham chiếu đến đối tượng đó nữa. Tuy nhiên, vì hàm gọi lại trong setInterval là một arrow function (do đó được liên kết với đối tượng cấu hình), nên hàm gọi lại vẫn giữ một tham chiếu đến đối tượng cấu hình, hàm gọi lại vẫn giữ một tham chiếu đến đối tượng cấu hình.

Miễn là có một tham chiếu, đối tượng sẽ không bị thu thập rác. Vì đây là khoảng thời gian, việc đặt cấu hình thành null hoặc xóa config.alert sẽ không thu thập khoảng thời gian này, vì vậy khoảng thời gian sẽ vẫn được gọi. Nó phải được xóa bằng clearInterval (config.alert) để xóa nó khỏi bộ nhớ. Vì nó chưa bị xóa, nên hàm gọi lại setInterval sẽ vẫn được gọi sau mỗi 1000ms (1s).

104. phương thức nào sẽ trả về giá trị 'Hello world!'?

const myMap = new Map();
const myFunc = () => 'greeting';

myMap.set(myFunc, 'Hello world!');

//1
myMap.get('greeting');
//2
myMap.get(myFunc);
//3
myMap.get(() => 'greeting');

A: 1

B: 2

C: 2 and 3

D: All of them

Khi thêm một cặp key hoặc giá trị tương ứng bằng phương thức set, key sẽ là giá trị của đối số đầu tiên được truyền cho hàm set và giá trị sẽ là đối số thứ hai được truyền cho hàm set. Ở VD trên, key là hàm () => 'greeting' và giá trị 'Xin chào thế giới'. myMap bây giờ là {() => 'welcome' => 'Hello world!' }.

Đáp án 1 là sai, vì key không phải là 'welcome' mà là () => 'welcome'. 3 là sai, vì chúng ta đang tạo một hàm mới bằng cách chuyển nó làm tham số cho phương thức get. Đối tượng tương tác bằng tham chiếu. Các hàm là các đối tượng, đó là lý do tại sao hai hàm không bao giờ hoàn toàn bằng nhau, ngay cả khi chúng giống hệt nhau: chúng có tham chiếu đến một vị trí khác trong bộ nhớ.

105. Thay đổi giá trị của object thông qua hàm

const person = {
  name: 'Lydia',
  age: 21,
};

const changeAge = (x = { ...person }) => (x.age += 1);
const changeAgeAndName = (x = { ...person }) => {
  x.age += 1;
  x.name = 'Sarah';
};

changeAge(person);
changeAgeAndName();

console.log(person);

A: {name: "Sarah", age: 22}

B: {name: "Sarah", age: 23}

C: {name: "Lydia", age: 22}

D: {name: "Lydia", age: 23}

Cả hai hàm changeAgechangeAgeAndName đều có một tham số mặc định, cụ thể là một đối tượng mới được tạo {... person}. Đối tượng này có các bản sao của tất cả các key/value trong đối tượng person.

Đầu tiên, chúng ta gọi hàm changeAge và truyền đối tượng person làm tham số của nó. Hàm này tăng giá trị của thuộc tính age lên 1 => person hiện là {name: "Lydia", age: 22}.

Sau đó, chúng ta gọi hàm changeAgeAndName, tuy nhiên chúng ta không truyền một tham số. Thay vào đó, giá trị của x bằng một đối tượng mới: {... person}. Vì là một đối tượng mới nên nó không ảnh hưởng đến giá trị của các thuộc tính trên đối tượng person. Do đó, person vẫn bằng {name: "Lydia", age: 22}.

106. Tách thành các phần tử trong một mảng thông qua spread operator

function sumValues(x, y, z) {
  return x + y + z;
}

A: sumValues([...1, 2, 3])

B: sumValues([...[1, 2, 3]])

C: sumValues(...[1, 2, 3])

D: sumValues([1, 2, 3])

Với toán tử ..., chúng ta có thể tách mảng thành các phần tử. Hàm sumValues nhận 3 tham số x, y, z. ...[1,2,3] sẽ tạo thành 1,2,3 sẽ truyền tương ứng với 3 tham số trong mảng.

107. Toán tử ?. có tác dụng gì?

const person = {
  firstName: 'Lydia',
  lastName: 'Hallie',
  pet: {
    name: 'Mara',
    breed: 'Dutch Tulip Hound',
  },
  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  },
};

console.log(person.pet?.name);
console.log(person.pet?.family?.name);
console.log(person.getFullName?.());
console.log(member.getLastName?.());

A: undefined undefined undefined undefined

B: Mara undefined Lydia Hallie ReferenceError

C: Mara null Lydia Hallie null

D: null ReferenceError null ReferenceError

Với toán tử chuỗi tùy chọn .?, chúng ta không cần phải khai báo tường minh liệu rằng giá trị được lồng sâu bên trong object có hợp lệ hay không. Nếu chúng ta cố gắng truy cập vào thuộc tính của một biến undefined hoặc null biểu thức sẽ trả về undefined

person.pet?.name : trong đối tượng person có thuộc tính là pet, person.pet không null, trong nó có thuộc tính là name, do đó truy vấn thành công và trả về Mara.

Tiếp theo, person.pet?.family.name : person có thuộc tính pet, nên person.pet không null, nhưng trong person.pet lại không có thuộc tính family. Do đó, từ vị trí này trở đí, nó sẽ trả về undefined.

Tương tự với các trường hợp tiếp theo.

108. Lưu ý khi truy vấn mảng với indexOf

const groceries = ['banana', 'apple', 'peanuts'];

if (groceries.indexOf('banana')) {
  console.log('We have to buy bananas!');
} else {
  console.log(`We don't have to buy bananas!`);
}

A: We have to buy bananas!

B: We don't have to buy bananas

C: undefined

D: 1

Chúng tôi đã truyền groceries.indexOf ("banana") vào câu lệnh biểu thức điều kiện if. groceries.indexOf ("banana") trả về 0, là một giá trị sai trong lập trình. Vì điều kiện trong câu lệnh ifsai, nên code trong khối khác sẽ trả về We don't have to buy bananas!.

109. so sánh kiểu dữ liệu với !typeof

const name = 'Lydia Hallie';

console.log(!typeof name === 'object');
console.log(!typeof name === 'string');

A: false true

B: true false

C: false false

D: true true

typeof name trả về kiểu string. Chuỗi string là giá trị "string" đúng nghĩa. vì vậy !typeof name trả về giá trị kiệu boolean là false. false === "object"false === "string" cà 2 đều trả về false.

110. Kết quả dưới đây là gì?

const myFunc = ({ x, y, z }) => {
  console.log(x, y, z);
};

myFunc(1, 2, 3);

A: 1 2 3

B: {1: 1} {2: 2} {3: 3}

C: { 1: undefined } undefined undefined

D: undefined undefined undefined

Hàm myFunc mong đợi một đối tượng có các thuộc tính x, yz là đối số của nó. Vì chúng ta chỉ chuyển ba giá trị số riêng biệt (1, 2, 3) thay vì một đối tượng có thuộc tính x, yz ({x: 1, y: 2, z: 3}), x, yz có giá trị mặc định của chúng là không xác định.

111. Công dụng của Intl.NumberFormat là gì

function getFine(speed, amount) {
  const formattedSpeed = new Intl.NumberFormat('en-US', {
    style: 'unit',
    unit: 'mile-per-hour'
  }).format(speed);

  const formattedAmount = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  }).format(amount);

  return `The driver drove ${formattedSpeed} and has to pay ${formattedAmount}`;
}

console.log(getFine(130, 300))

A: The driver drove 130 and has to pay 300

B: The driver drove 130 mph and has to pay $300.00

C: The driver drove undefined and has to pay undefined

D: The driver drove 130.00 and has to pay 300.00

Với phương thức Intl.NumberFormat, chúng ta có thể định dạng các giá trị số thành bất kỳ ngôn ngữ nào. Chúng ta định dạng giá trị số 130 cho ngôn ngữ en-US dưới dạng đơn vị tính bằng mph, kết quả là 130 miles/h. Giá trị số 300 cho ngôn ngữ en-US dưới dạng đơn vị tiền tệ là USD dẫn đến $300,00.

112. Thêm một phần tử vào mảng bằng destructing function

const spookyItems = ['👻', '🎃', '🕸'];
({ item: spookyItems[3] } = { item: '💀' });

console.log(spookyItems);

A: ["👻", "🎃", "🕸"]

B: ["👻", "🎃", "🕸", "💀"]

C: ["👻", "🎃", "🕸", { item: "💀" }]

D: ["👻", "🎃", "🕸", "[object Object]"]

Bằng cách destructing lại các đối tượng, chúng ta có thể giải nén các giá trị từ đối tượng bên phải và gán giá trị đã giải nén cho giá trị của cùng một tên thuộc tính trên đối tượng bên trái. Trong trường hợp này, chúng ta đang chỉ định giá trị "💀" đến spookyItems[3]. Điều này có nghĩa là chúng ta đang sửa đổi mảng spookyItems, chúng ta đang thêm "💀" vào nó. Khi log spookyItems, kết quả sẽ là ["👻", "🎃", "🕸", "💀"] .

113. Sử dụng flat để duỗi mảng

const emojis = ['🥑', ['✨', '✨', ['🍕', '🍕']]];

console.log(emojis.flat(1));

A: ['🥑', ['✨', '✨', ['🍕', '🍕']]]

B: ['🥑', '✨', '✨', ['🍕', '🍕']]

C: ['🥑', ['✨', '✨', '🍕', '🍕']]

D: ['🥑', '✨', '✨', '🍕', '🍕']

Với phương thức flat, chúng ta có thể tạo một mảng mới, được duỗi bớt các mảng lồng nhau. Độ sâu của mảng phẳng phụ thuộc vào giá trị mà chúng ta truyền vào. Trong trường hợp này, chúng ta đã truyền giá trị 1 (mà chúng tôi không cần phải làm vậy, đó là giá trị mặc định), nghĩa là chỉ các mảng ở độ sâu đầu tiên sẽ được nối với nhau. ['🥑']['✨', '✨', ['🍕', '🍕']] trong trường hợp này. Kết hợp hai mảng này sẽ tạo ra ['🥑', '✨', '✨', ['🍕', '🍕']]

114. Proxy hoạt động như thế nào?

const handler = {
  set: () => console.log('Added a new property!'),
  get: () => console.log('Accessed a property!'),
};

const person = new Proxy({}, handler);

person.name = 'Lydia';
person.name;

A: Added a new property!

B: Accessed a property!

C: Added a new property! Accessed a property!

D: Nothing gets logged

Với một đối tượng Proxy, chúng ta có thể thêm hành vi tùy chỉnh vào một đối tượng mà chúng ta truyền cho nó dưới dạng đối số thứ hai. Trong trường hợp này, chúng ta truyền vào handler có chứa các thuộc tính: setget. setđược gọi khi chúng ta thiết lập giá thuộc tính, get được gọi khi chúng ta nhận hoặc tiếp cận với giá trị thuộc tính đó.

Tham số đầu tiên là một đối tượng rỗng {}, là giá trị của person. Đối với đối tượng này, hành vi tùy chỉnh được chỉ định trong đối tượng trình xử lý sẽ được thêm vào. Nếu chúng ta thêm một thuộc tính vào đối tượng person, set sẽ được gọi. Nếu chúng ta truy cập một thuộc tính trên đối tượng person, thì get sẽ được gọi.

Đầu tiên, chúng ta đã thêm một tên thuộc tính mới vào đối tượng proxy (person.name = "Lydia"). set được gọi và log ra "Added a new property!".

Sau đó, chúng ta truy cập một giá trị thuộc tính trên đối tượng proxy, thuộc tính get trên đối tượng handler được gọi. "Accessed a property!"

115. Phương thức nào sau đây giúp thay đổi thuộc tính trong Object.seal

const person = { name: 'Lydia Hallie' };

Object.seal(person);

A: person.name = "Evan Bacon"

B: person.age = 21

C: delete person.name

D: Object.assign(person, { age: 21 })

Với Object.seal, chúng ta có thể ngăn không cho thêm các câu lệnh mới hoặc loại bỏ các thuộc tính hiện tại. Tuy nhiên, bạn vẫn có thể sửa đổi giá trị của các thuộc tính hiện có.

116. private trong class

class Counter {
  #number = 10

  increment() {
    this.#number++
  }

  getNum() {
    return this.#number
  }
}

const counter = new Counter()
counter.increment()

console.log(counter.#number)

A: 10

B: 11

C: undefined

D: SyntaxError

Trong ES2020, chúng ta có thể thêm các biến private trong các lớp bằng cách sử dụng dấu #. Chúng ta không thể truy cập các biến này bên ngoài lớp. Khi chúng ta cố gắng ghi lại số thứ tự của bộ đếm. #, Một lỗi Cú pháp sẽ xuất hiện: chúng ta không thể truy cập nó bên ngoài lớp Counter!

117. Truyền một mảng trong một object làm tham số của một hàm sẽ như thế nào?

const person = {
  name: 'Lydia Hallie',
  hobbies: ['coding'],
};

function addHobby(hobby, hobbies = person.hobbies) {
  hobbies.push(hobby);
  return hobbies;
}

addHobby('running', []);
addHobby('dancing');
addHobby('baking', person.hobbies);

console.log(person.hobbies);

A: ["coding"]

B: ["coding", "dancing"]

C: ["coding", "dancing", "baking"]

D: ["coding", "running", "dancing", "baking"]

Hàm addHobbies nhận 2 tham số, hoppyhobbies với giá trị mặc định là mảng hobbies trong object person.

Đầu tiên, chúng ta gọi hàm addHobby và truyền "running" làm giá trị cho hobby và một mảng trống làm giá trị cho hobbies. Vì chúng ta chuyển một mảng trống làm giá trị cho y, nên "running" sẽ được thêm vào mảng trống này.

Sau đó, chúng ta gọi hàm addHobby và truyền "dancing" làm giá trị cho hobby. Chúng ta đã không truyền một giá trị cho hobbies, vì vậy nó nhận giá trị mặc định, là thuộc tính hobbies trên đối person. Lúc này, chúng ta đã đưa "dancing" vào trong mảng person.hobbies.

Cuối cùng, chúng ta gọi hàm addHobby và truyền "baking" làm giá trị cho hobby và mảng person.hobbies làm giá trị cho hobbies. Lúc này, chúng ta đã đưa "baking" vào trong mảng person.hobbies

Sau khi đưa "dancing""baking" vào trong person.hobbies, cuối cùng mảng person.hobbies sẽ có giá trị là ["coding", "dancing", "baking"]

118. Toán tử ?.


function getFruit(fruits) {
    console.log(fruits?.[1]?.[1])
}

getFruit([['🍊', '🍌'], ['🍍']])
getFruit()
getFruit([['🍍'], ['🍊', '🍌']])

A: null, undefined, 🍌

B: [], null, 🍌

C: [], [], 🍌

D: undefined, undefined, 🍌

Toán tử ? cho phép chúng ta có thể truy cập các phần tử sâu bên trong các mảng lồng nhau. Chúng ta đang cố gắng truy cập đến phần tử thứ 1 trong mảng con nằm trong phần tử thứ 1 của mảng cha là fruits. Nếu mảng con trên index thứ 1 trong mảng fruits không tồn tại, nó sẽ chỉ trả về undefined. Nếu mảng con trên index thứ 1 trong mảng fruits tồn tại, nhưng mảng con này chỉ có 1 phần tử trong mảng (tức là chỉ có index 0) của nó, nó cũng sẽ trả về undefined

Đầu tiên, chúng ta cố gắng truy cập vào index số 1 trong mảng con ['🍍'] của [['🍊', '🍌'], ['🍍']]. Mảng con này chỉ chứa một index (0), có nghĩa là không có mục nào trên chỉ mục là 1 và do đó nó trả về undefined. Sau đó, chúng ta đang gọi hàm getFruits mà không chuyển một giá trị làm tham số, có nghĩa là các fruits có giá trị undefined theo mặc định. Vì chúng ta xâu chuỗi mục theo điều kiện trên các fruits với index 1, nó trả về undefined vì trên mảng này index 1 không tồn tại.

Cuối cùng, chúng ta log index thứ 1 trong mảng con ['🍊', '🍌'] của ['🍍'], ['🍊', '🍌']. Index thứ 1 trong mảng con này là 🍌, do đó, no được log ra.

119. Hai object dưới đây có bằng nhau không?

const user = {
    email: "e@mail.com",
    password: "12345"
}

const updateUser = ({ email, password }) => {
    if (email) {
        Object.assign(user, { email })
    }

    if (password) {
        user.password = password
    }

    return user
}

const updatedUser = updateUser({ email: "new@email.com" })

console.log(updatedUser === user)

A: false

B: true

C: TypeError

D: ReferenceError

Hàm updateUser cập nhật các giá trị của thuộc tính emailpassword trên user, nếu giá trị của chúng được chuyển cho hàm, sau đó hàm trả về object user. Giá trị trả về của hàm updateUser lúc này là object user, có nghĩa là giá trị của updatedUser là một tham chiếu đến cùng một đối tượng mà user trỏ đến. Do đó, updatedUser === usertrue.

120. slice, slpice, unshift trong mảng

const fruit = ['🍌', '🍊', '🍎']

fruit.slice(0, 1)
fruit.splice(0, 1)
fruit.unshift('🍇')

console.log(fruit)

A: ['🍌', '🍊', '🍎']

B: ['🍊', '🍎']

C: ['🍇', '🍊', '🍎']

D: ['🍇', '🍌', '🍊', '🍎']

Đầu tiên, chúng ta gọi phương thức slice trên mảng fruit. Phương thức slice không sửa đổi mảng ban đầu, nhưng trả về những giá trị nào mà nó cắt ra khỏi mảng: đó chính là "🍌". Sau đó, chúng ta gọi phương splice trên mảng quả. Phương thức splice không sửa đổi mảng ban đầu, có nghĩa là mảng fruit bây giờ bao gồm ['🍊', '🍎']. Cuối cùng, chúng ta gọi phương thức unshift trên mảng fruit, phương thức này sẽ sửa đổi mảng ban đầu bằng cách thêm giá trị đã cho.

"🍇" được đưa vào phần tử đầu tiên trong trường hợp này. Mảng fruit bây giờ bao gồm ['🍇', '🍊', '🍎'].

121. thuộc tính arrow function trong object

const user = {
    email: "my@email.com",
    updateEmail: email => {
        this.email = email
    }
}

user.updateEmail("new@email.com")
console.log(user.email)

A: my@email.com

B: new@email.com

C: undefined

D: ReferenceError

Hàm updateEmail là một hàm arrow function và không bị ràng buộc với đối tượng user. Điều này có nghĩa là từ khóa this không đề cập đến đối tượng user, nhưng đề cập đến phạm vi global scope trong trường hợp này. Giá trị của email trong đối tượng user không được cập nhật. Khi ghi lại giá trị của user.email, giá trị ban đầu của my@email.com sẽ được trả lại.

122. Promise all khi gặp catch

const promise1 = Promise.resolve('First')
const promise2 = Promise.resolve('Second')
const promise3 = Promise.reject('Third')
const promise4 = Promise.resolve('Fourth')

const runPromises = async () => {
    const res1 = await Promise.all([promise1, promise2])
    const res2  = await Promise.all([promise3, promise4])
    return [res1, res2]
}

runPromises()
    .then(res => console.log(res))
    .catch(err => console.log(err))

A: [['First', 'Second'], ['Fourth']]

B: [['First', 'Second'], ['Third', 'Fourth']]

C: [['First', 'Second']]

D: 'Third'

Phương thức Promise.all chạy song song với các Promise đã truyền. Nếu một Promise bị reject, phương thức Promise.all sẽ reject với giá trị của của Promise đã bị reject. Trong trường hợp này, Promise bị từ chối với giá trị "Third". Chúng ta đang bắt giá trị bị từ chối trong phương thức bắt chuỗi trên lệnh gọi runPromises để bắt bất kỳ lỗi nào trong hàm runPromises. Chỉ "Third" mới được log ra, vì 3 Promise.resolve kia bị từ chối bởi giá trị này.

123. Lưu ý !typeof

let randomValue = { name: "Lydia" }
randomValue = 23

if (!typeof randomValue === "string") {
    console.log("It's not a string!")
} else {
    console.log("Yay it's a string!")
}

A: It's not a string!

B: Yay it's a string!

C: TypeError

D: undefined

Điều kiện trong câu lệnh if kiểm tra xem giá trị của !typeof randomValue có bằng "string" hay không. Với toán tử ! nó giúp chuyển đổi giá trị thành giá trị boolean. Nếu giá trị là true, thì với ! nó sẽ giá trị trả về sẽ là false, ngược lại nếu giá trị là false, thì giá trị trả về sẽ là true. Trong trường hợp này, giá trị trả về của typeof randomValue là giá trị true "number", có nghĩa là giá trị của !typeof randomValue là giá trị boolean false.

Tuy nhiên, !typeof randomValue === "string" luôn trả về false, vì chúng ta đang thực sự kiểm tra false === "string". Vì điều kiện trả về false, khối mã của câu lệnh else sẽ được chạy và "Yay it's a string!" sẽ được log ra.

Series Những câu hỏi hay trong Javascript đến đây là hết, thực sự nó cũng khá dài và khó vì có những thứ trong thực tế chúng ta cũng chưa bao giờ gặp nó. Qua đây, hy vọng các bạn sẽ khám phá ra được nhiều điều mới mẻ trong javascript để có thể đào sâu hơn về nó, tận dụng được những điểm mạnh và tránh được những lỗi không đáng có khi lập trình với JS. Cám ơn các bạn đã quan tâm theo dõi, hẹn gặp lại các bạn trong những chủ để tiếp theo.


Bài viết có liên quan