Interviews - javascript
All the javascript interview question.
C1:
Trong javascript có bao nhiêu phạm vi của biến, hãy giải thích nó?
Xem đáp án
Ban đầu lúc được thiết kế thì Javascript chỉ có 2 phạm vi duy nhất là phạm vị toàn cục (global scope) và phạm vi địa phương (local scope).
Global scope
Các biến có phạm vi toàn cục sẽ có thể truy xuất ở bất kì đâu. Ví dụ đơn giản:
var a = 10;
function doSomething(){
console.log(a); // có thể truy cập a
}
function doAnotherThing(){
console.log(a); // có thể truy cập a
}
doSomething();
doAnotherThing();
Local scope
Các biến chỉ tồn tại bên trong các hàm (function), local scope còn được gọi là function scope vì lẽ này. Mỗi hàm có phạm vi cục bộ riêng của nó, do đó một hàm không thể truy cập các biến cục bộ của một hàm khác. Ví dụ:
function doSomething(){
var a = 10;
}
function doAnotherThing(){
console.log(a); // không thể có thể truy cập biến A. Lỗi: ReferenceError: a is not defined
}
doAnotherThing();
Từ năm 2015 trở đi với ES6
ES6 bổ sung thêm từ khóa let
cho javascript. Các biến khai báo với từ khóa let sẽ có phạm vị khối (block scope).
Phạm vị khối (block scope) là một biến được khai báo trong 1 khối lệnh nào đó, ví dụ: câu lệnh điều kiện (if) hoặc vòng lặp (for). Thực sự thì phạm vi khối không có gì lạ đối với các ngôn ngữ hướng đối tượng như Java, C#, tuy nhiên đối với Javascript thì hơi “dị”?
Ta xem ví dụ bên dưới:
if(true){
let a = 10;
var b = 20;
}
//Có thể truy cập b nhưng không thể truy cập a
console.log(b); // in ra 10
console.log(a); // Lỗi: a is not defined
Trong ví dụ trên, biến a
khai báo với let
sẽ có phạm vị khối lệnh. Do đó, bên ngoài khối lệnh if
thì không thể truy xuất được. Trong khi đó, biến b
khai báo bằng từ khóa var
sẽ có phạm vị hàm, đo đó, vẫn có thể truy xuất bên ngoài khối lệnh if
.
C2:
So sánh ==
và ===
trong Javascript
Xem đáp án
JavaScript có 2 loại so sánh:
- Strict comparison (
===
) hay còn gọi là so sanh nghiêm ngặt, nó sẽ kiểm tra giá trị bằng nhau mà không tự động ép kiểu. - Abstract comparison (
==
) hay còn gọi là so sanh không nghiêm ngặt, nó sẽ kiểm tra giá trị bằng nhau có tự động ép kiểu.
Ví dụ:
var a = "42";
var b = 42;
a == b; // true
a === b; // false
Bạn có thể xem chi tiết hơn về so sánh trong javascript ở đây, vì nó sẽ có rất nhiều trường hợp phi logic so sánh trong javascript
Một số quy tắc so sánh đơn giản:
-
Nếu giá trị của một trong hai bên của phép so sánh là
true
hoặcfalse
, hãy tránh==
và sử dụng===
. -
Nếu giá trị của một trong hai bên của phép so sánh là các giá trị cụ thể này (0, "" hoặc [] — mảng trống), hãy tránh
==
và sử dụng===
. -
Trong tất cả các trường hợp khác, bạn có thể yên tâm sử dụng
==
. Nó không chỉ an toàn mà trong nhiều trường hợp, nó đơn giản hóa mã của bạn giúp dễ đọc hơn.
C3:
Sự khác biệt của biến dùng var
, let
và const
Xem đáp án
Từ phiên bản ES6, từ khoá let và const được thêm vào cho khai báo biến.
keyword | const | let | var |
---|---|---|---|
global scope | no | no | yes |
function scope | yes | yes | yes |
block scope | yes | yes | no |
can be reassigned | no | yes | yes |
Ta thử xem các ví dụ:
var variable1 = 23;
let variable2 = 89;
function catchValues(){
console.log(variable1);
console.log(variable2);
// Both the variables can be accessed anywhere since they are declared in the global scope
}
window.variable1; // Returns the value 23
window.variable2; // Returns undefined
Các biến được khai báo với từ khóa let
trong global scope sẽ hoạt động giống như biến được khai báo với từ khóa var
trong global scope.
Các biến được khai báo trong global scope với từ khóa var và let có thể được truy cập từ bất kỳ đâu trong code.
Nhưng, có một sự khác biệt!
Các biến được khai báo với từ khóa var trong global scope được thêm vào đối tượng window/global. Do đó, chúng có thể được truy cập bằng window.variableName
.
Trong khi đó, các biến được khai báo với từ khóa let không được thêm vào đối tượng toàn cục, do đó, việc cố gắng truy cập các biến đó bằng cách sử dụng window.variableName
sẽ dẫn đến lỗi.
var và let ở function scope
function varVsLetFunction(){
let awesomeCar1 = "Audi";
var awesomeCar2 = "Mercedes";
}
console.log(awesomeCar1); // Throws an error
console.log(awesomeCar2); // Throws an error
Các biến được khai báo trong function scope bằng cách sử dụng var
và let
hoạt động hoàn toàn giống nhau, nghĩa là chúng không thể được truy cập từ bên ngoài phạm vi.
{
var variable3 = [1, 2, 3, 4];
}
console.log(variable3); // Outputs [1,2,3,4]
{
let variable4 = [6, 55, -1, 2];
}
console.log(variable4); // Throws error
for(let i = 0; i < 2; i++){
//Do something
}
console.log(i); // Throws error
for(var j = 0; j < 2; i++){
// Do something
}
console.log(j) // Outputs 2
Trong javascript, một khối có nghĩa là code được viết bên trong dấu ngoặc nhọn {}
.
Các biến được khai báo với từ khóa var không có block scope. Nó có nghĩa là một biến được khai báo trong block scope {}
với từ khóa var cũng giống như khai báo biến trong global scope.
Các biến được khai báo với từ khóa let bên trong block scope không thể được truy cập từ bên ngoài khối.
const
Các biến với từ khóa const hoạt động chính xác như một biến được khai báo với từ khóa let chỉ có một điểm khác biệt, bất kỳ biến nào được khai báo với từ khóa const đều là hằng số, tức là không thể được gán lại.
const x = {name:"Vivek"};
x = {address: "India"}; // Throws an error
x.name = "Nikhil"; // No error is thrown
const y = 23;
y = 44; // Throws an error
Trong đoạn code trên, mặc dù chúng ta có thể thay đổi giá trị của một thuộc tính bên trong biến được khai báo với từ khóa const, nhưng chúng ta không thể gán lại hoàn toàn chính biến đó.
C4:
Cho biết kết quả của đoạn code dưới đây?
console.log(typeof typeof 1);
Xem đáp án
Kết quả: string
Giải thích:
typeof 1 là Number và typeof Number là string
C5:
Object trong Javascript là gì?
Xem đáp án
Trong JavaScript, đối tượng là một thực thể độc lập, có thể được xem như một bản ghi có chứa thông tin về một đối tượng cụ thể. Tương tự như cách một cái cốc có thể có những đặc điểm riêng của nó như màu sắc, thiết kế, trọng lượng, chất liệu, trong JavaScript, đối tượng cũng có thể có những thuộc tính định nghĩa sự đặc trưng của chúng.
Một đối tượng trong JavaScript được tạo ra bằng cách tạo ra một tập hợp các cặp tên - giá trị, trong đó tên được coi là một chuỗi duy nhất và giá trị có thể là bất kỳ giá trị nào, bao gồm cả các đối tượng khác và các hàm.
Để tạo một đối tượng trong JavaScript, có ba cách thông dụng như sau:
Cách đầu tiên là sử dụng cú pháp literal để tạo một đối tượng mới bằng cách khai báo biến và gán giá trị là một đối tượng trống {}:
const dog = {};
Cách thứ hai là sử dụng hàm tạo Object() để tạo một đối tượng mới:
const cat = new Object();
Cách thứ ba là sử dụng phương thức tĩnh Object.create() để tạo một đối tượng mới, dựa trên một đối tượng đã có sẵn (gọi là đối tượng prototype):
const horse = Object.create({});
C6:
Sự khác biệt giữa các kiểu dữ liệu trong JavaScript?
Xem đáp án
Trong JavaScript các kiểu dữ liệu được chia làm hai loại là kiểu nguyên thuỷ và đối tượng. Để biết kiểu dữ liệu của các biến JavaScript, ta có thể sử dụng typeof.
String
- biểu diễn một mảng ký tự hay một chuỗi. Kiểu chuỗi trong javascript có thể sử dụng một cặp dấu ngoặc kép hoặc dấu ngoặc kép đơn.
var str = "Vivek Singh Bisht"; //sử dụng dấu ngoặc kép
var str2 = "John Doe"; // sử dụng dấu ngoặc đơn
Number
- biểu diễn cả số nguyên và số thực.
var x = 3; // số nguyên
var y = 3.6; // số thực
BigInt
- kiểu dữ liệu này được sử dụng để lưu trữ các số vượt quá giới hạn của kiểu dữ liệu Number. Nó có thể lưu trữ các số nguyên lớn và được biểu diễn bằng cách thêm “n” vào một chữ số nguyên.
var bigInteger = 234567890123456789012345678901234567890;
Boolean
- kiểu luận lý, có hai giá trị là true và false. Thường được dùng với điều kiện.
var a = 2;
var b = 3;
var c = 2;
(a == b)(
// trả về false
a == c,
); // trả về true
undefined
- khi giá trị của một biến là không xác định.
var x; // giá trị của x là undefined
var y = undefined; // ta cũng có thể gán một biến là undefined
null
- biểu diễn giá trị null. Vì JavaScript là case-sensitive, null sẽ không giống với Null
, NULL
, hoặc bất kỳ biến thể khác.
var z = null;
Symbol
- mới được giới thiệu trong ES6. Nó lưu trữ các giá trị duy nhất và ẩn danh.
var symbol1 = Symbol("symbol");
Sử dụng typeof
để tìm kiểu nguyên thuỷ:
typeof "John Doe"; // Returns "string"
typeof 3.14; // Returns "number"
typeof true; // Returns "boolean"
typeof 234567890123456789012345678901234567890n; // Returns bigint
typeof undefined; // Returns "undefined"
typeof null; // Returns "object" (đặc trưng của JavaScript)
typeof Symbol("symbol"); // Returns Symbol
Trong JavaScript nếu dữ liệu không phải là kiểu nguyên thuỷ thì tất cả đều là object.
Object
- dùng để lưu trữ tập hợp dữ liệu
// Tập hợp dữ liệu dạng key-value
var obj1 = {
x: 43,
y: "Hello world!",
z: function () {
return this.x;
},
};
// Tập hợp dữ liệu dạng danh sách
var array1 = [5, "Hello", true, 4.1];
C7:
Kể các cách khai báo 1 biến trong Javascript?
Xem đáp án
Trong JavaScript, có ba cách khai báo biến:
var
: là cách khai báo biến truyền thống trong JavaScript. Biến được khai báo bằng từ khóa var có thể được truy cập ở bất kỳ đâu trong phạm vi của hàm hoặc khối được khai báo. Ví dụ:
var myVariable = "Hello World!";
let
: là một cách khai báo biến mới được giới thiệu trong phiên bản ECMAScript 6. Biến được khai báo bằng từ khóa let
chỉ có thể được truy cập trong phạm vi của khối được khai báo (phạm vi cục bộ). Khác với var, biến được khai báo bằng let không được hoisted (nâng biến lên đầu phạm vi). Ví dụ:
let myVariable = "Hello World!";
const
: là một cách khai báo biến mới khác được giới thiệu trong phiên bản ECMAScript 6. Biến được khai báo bằng từ khóa const
cũng có phạm vi cục bộ và giống như let
, không được hoisted. Tuy nhiên, biến được khai báo bằng const là biến không thể gán lại giá trị mới. Ví dụ:
const myVariable = "Hello World!";
Lưu ý rằng, khi sử dụng const
, chúng ta không thể gán lại giá trị mới cho biến đó, nhưng chúng ta vẫn có thể thay đổi giá trị của thuộc tính trong trường hợp đối tượng hay mảng được gán vào biến đó.
C8:
Kể tên các kiểu dữ liệu cơ bản trong Javascript
Xem đáp án
Kiểu dữ liệu nguyên thủy:
Number
: Các số nguyên hoặc số thực. Ví dụ: 5 hoặc 5.05String
: là các text như “Các kiểu dữ liệu trong JavaScript”, text có thể có một hoặc nhiều ký tự.Boolean
: chỉ có 2 giá trị là true hoặc false.Undefine
: là các giá trị không xác định.Null
: đơn giản là không có giá trị nào cả.Symbol
: mới được giới thiệu trong ES6. Nó lưu trữ các giá trị duy nhất và ẩn danh
Kiểu dữ liệu không nguyên thủy (tham chiếu):
-
Object
: Thể hiện một đối tượng và các thuộc tính có thể truy cập đến. -
Array
: Nhóm các giá trị giống nhau. -
RegExp
: Biểu thức chính quy.
C9:
Strict mode trong JavaScript là gì?
Xem đáp án
Strict hiểu đơn giản theo nghĩa tiếng Việt là “nghiêm ngặt, nghiêm khắc”. Strict Mode là một quy mẫu nghiêm khắc của Javascript. Nếu như coi việc viết code bình thường là Normal Mode, thì Strict Mode
sẽ có thêm nhiều quy định khác so với Normal Mode. Việc đó khiến cho một thao tác vốn bình thường có thể chạy ngon lành trở nên lỗi, và throw ra errors.
Nhìn chung, Strict được tạo ra nhằm:
- Ngăn chặn sử dụng, và throw errors khi người lập trình thực hiện những xử lý được coi là unsafe, những xử lý mà có thể là ngoài ý muốn.
- Vô hiệu hoá các tính năng có thể gây nhầm lẫn, hoặc không nên được sử dụng.
- Ngăn chặn sử dụng một số từ mà có thể sẽ được sử dụng làm keywork trong tương lai. Dưới đây là một số ví dụ
Gán giá trị cho biến chưa được khai báo
"use strict";
variable = "vien.huynh";
console.log(variable);
//Uncaught ReferenceError: variable is not defined
Báo lỗi khi sử dụng delete
"use strict";
function getName(name) {
alert(name);
}
delete getName;
//Uncaught SyntaxError: Delete of an unqualified
//identifier in strict mode.
Các tham số của hàm không được trùng nhau
"use strict";
function getName(name, name, age) {
//code
}
//Uncaught SyntaxError: Duplicate parameter name not allowed in this context
Không cho phép khai báo biến dưới dạng hệ nhị phân
var num = 01010;
//Uncaught SyntaxError: Octal literals are not allowed in strict mode.
Không được phép ghi đè lên thuộc tính chỉ được phép đọc
"use strict";
var obj = {};
Object.defineProperty(obj, "ver", { value: 1, writable: false });
obj.ver = 10;
Không sử dụng được with
"use strict";
var bar = 1;
var foo = 2;
with (bar) {
console.log(foo);
}
//Uncaught SyntaxError: Strict mode code may not include a with statement
Không cho phép khai báo biến trong eval
"use strict";
eval("var x = 4");
alert(x);
//Uncaught ReferenceError: x is not defined
Không chấp nhận khai báo các keyword. Ở chế độ strict mode thì các bạn sẽ không sử dụng được các từ khóa sau để khai báo làm tên biến, hằng,…
-
implements
-
interface
-
let
-
package
-
private
-
protected
-
public
-
static
-
yield
-
arguments
C10:
JavaScript là ngôn ngữ kiểu tĩnh hay kiểu động?
Xem đáp án
JavaScript là một ngôn ngữ kiểu động (dynamic typing language). Điều này có nghĩa là các biến trong JavaScript không được khai báo với một kiểu dữ liệu cụ thể và có thể thay đổi kiểu dữ liệu trong quá trình thực thi chương trình. Ví dụ, một biến có thể được khai báo với kiểu dữ liệu số nguyên (integer) và sau đó được gán giá trị là một chuỗi (string). Các ngôn ngữ kiểu tĩnh (static typing language) như C++ hoặc Java, đòi hỏi các biến phải được khai báo với kiểu dữ liệu cụ thể và giữ nguyên kiểu dữ liệu đó trong suốt quá trình thực thi chương trình.
Ví dụ, trong JavaScript, bạn có thể khai báo một biến mà không cần chỉ định kiểu dữ liệu của nó, như sau:
var myVariable;
Trong ví dụ trên, myVariable là một biến kiểu động, và kiểu dữ liệu của nó sẽ được xác định tại thời điểm thực thi, dựa trên giá trị được gán cho biến. Ví dụ:
myVariable = "Hello World"; // kiểu dữ liệu của myVariable là string
myVariable = 42; // kiểu dữ liệu của myVariable là number
Như vậy, kiểu dữ liệu của biến myVariable đã được xác định tại thời điểm thực thi, và nó có thể thay đổi trong quá trình chạy chương trình.
C11:
Array trong Javascript là gì?
Xem đáp án
Trong Javascript, Array
là một kiểu dữ liệu được sử dụng để lưu trữ một tập hợp các giá trị trong một biến. Các giá trị này có thể là bất kỳ kiểu dữ liệu nào, bao gồm cả số, chuỗi, đối tượng và các mảng khác.
Để khởi tạo một mảng mới, ta sử dụng cú pháp:
let myArray = [1, 2, 3, 4, 5];
Ở đây, myArray
là tên biến, và danh sách giá trị nằm trong cặp dấu ngoặc vuông []
.
Các phần tử của mảng được truy cập thông qua chỉ số (index), bắt đầu từ 0. Ví dụ, để truy cập phần tử thứ hai của mảng myArray
, ta sử dụng cú pháp:
Một số phương thức thông dụng của Array trong Javascript bao gồm:
push()
: Thêm một hoặc nhiều phần tử vào cuối mảng.pop()
: Xóa phần tử cuối cùng của mảng.shift()
: Xóa phần tử đầu tiên của mảng.unshift()
: Thêm một hoặc nhiều phần tử vào đầu mảng.slice()
: Trích xuất một phần của mảng.concat()
: Kết hợp hai hoặc nhiều mảng lại với nhau để tạo ra một mảng mới.
Ví dụ, để thêm một phần tử mới vào cuối mảng myArray
, ta sử dụng phương thức push()
như sau:
myArray.push(6);
console.log(myArray); // Output: [1, 2, 3, 4, 5, 6]
C12:
Sự khác nhau giữa null
và undefined
trong Javascript?
Xem đáp án
Trong JavaScript, null
và undefined
là hai giá trị đặc biệt đại diện cho sự vắng mặt của giá trị.
Undefined có nghĩa là không xác định. Trong javascript, khi bạn khai báo một biến nhưng chưa gán giá trị cho nó, giá trị của biến đó sẽ là undefined
.
Ví dụ:
let x;
console.log(x); // undefined
let obj = {a: 1};
console.log(obj.b); // undefined
Bất cứ biến nào cũng có thể bị làm rỗng bằng cách thiết lập giá trị về không xác định (undefined
).
var test = undefined;
alert(test); //undefined
Null có nghĩa là giá trị rỗng hoặc giá trị không tồn tại, nó có thể được sử dụng để gán cho một biến như là một đại diện không có giá trị.
let y = null;
console.log(y); // null
Ngoài ra thì còn một chú ý nữa đó là undefine
có kiểu giá trị là undefined
nhưng null
lại là 1 object
typeof undefined; // undefined
typeof null; // object
Vì vậy, khi muốn kiểm tra xem một biến đã được khởi tạo hay chưa, bạn nên sử dụng undefined
, trong khi khi muốn chỉ định rõ ràng rằng một giá trị không có ý nghĩa thì nên sử dụng null
.
C13:
Giải thích về ép kiểu ngầm trong JavaScript?
Xem đáp án
Ép kiểu ngầm trong javascript là sự chuyển đổi tự động của giá trị từ kiểu dữ liệu này sang kiểu khác. Nó xảy ra khi thực hiện một biểu thức với các kiểu dữ liệu khác nhau.
Ép kiểu String
Ép kiểu string xảy ra khi dùng toán tử +
. Một số cộng với một chuỗi, kiểu số sẽ bị ép thành kiểu chuỗi.
Ví dụ:
var x = 3;
var y = "3";
x + y; // Returns "33"
var x = 24;
var y = "Hello";
x + y; // Returns "24Hello";
Để hiểu về hai ví dụ khi ta cộng một số vào chuỗi, thì khi JavaScript thấy biểu thức x+y
với hai kiểu khác nhau (một số và một chuỗi), nó chuyển đổi kiểu số thành chuỗi để thực hiện hành động. Sau khi chuyển đổi, cả hai biến đều là kiểu chuỗi, thao tác +
lúc này sẽ thành phép nối chuỗi kết quả là ra chuỗi “33” và “24Hello”.
Ngược lại, khi thực hiện phép toán -
, thì chuỗi lại bị ép kiểu ngầm thành số. Ví dụ:
var x = 3;
Var y = "3";
x - y //Returns 0 since the variable y (string type) is converted to a number type
Ép kiểu Boolean
- Ép kiểu
boolean
xảy ra khi sử dụng các toán tử logic, lệnh if hay kiểm tra vòng lặp. Để hiểu về ép kiểu logic, ta cần hiểu về giá trị truthy và falsy. - Giá trị truthy là cái sẽ được ép kiểu thành true. Còn falsy sẽ được ép kiểu thành false.
- Tất cả các giá trị ngoại trừ
0
,0n
,-0
,""
,null
,undefined
, vàNaN
thì đều là truthy.
Câu lệnh If:
var x = 0;
var y = 23;
if (x) {
console.log(x);
} // The code inside this block will not run since the value of x is 0(Falsy)
if (y) {
console.log(y);
} // The code inside this block will run since the value of y is 23 (Truthy)
Toán tử Logic:
- Toán tử logic trong javascript không giống các ngôn ngữ lập trình khác, nó không trả về true hay false, mà nó trả về một toán hạng.
- OR ( || ) - Nếu giá trị đầu tiên là truthy, giá trị đầu tiên sẽ được trả về, ngược lại thì nó trả về giá trị thứ hai.
- AND ( && ) - Nếu hai giá trị đều là truthy, giá trị thứ hai sẽ được trả về. Nếu giá trị đầu là falsy sẽ trả về giá trị đầu hoặc giá trị hai là falsy sẽ trả về giá trị hai.
Ví dụ:
var x = 220;
var y = "Hello";
var z = undefined;
x | | y // Returns 220 since the first value is truthy
x | | z // Returns 220 since the first value is truthy
x && y // Returns "Hello" since both the values are truthy
y && z // Returns undefined since the second value is falsy
if( x && y ){
console.log("Code runs" ); // This block runs because x && y returns "Hello" (Truthy)
}
if( x || z ){
console.log("Code runs"); // This block runs because x || y returns 220(Truthy)
}
Ép kiểu dấu bằng
Xảy ra khi thực hiện phép ”==“. Nhớ lại thì phép ”==” được dùng để so sánh hai giá trị khác kiểu.
Thực tế khi sử dụng ”==” một ép kiểu ngầm đã xảy ra, chuyển đổi tất cả toán hạng về cùng kiểu và so sánh chúng.
Ví dụ:
var a = 12;
var b = "12";
a == b; // Returns true because both 'a' and 'b' are converted to the same type and then compared. Hence the operands are equal.
Ép kiểu ngầm không xảy ra khi dùng ”===“.
var a = 226;
var b = "226";
a === b // Returns false because coercion does not take place and the operands are of different types. Hence they are not equal.
C14:
Toán tử typeof
trong Javascript là gì?
Xem đáp án
Toán tử typeof trong Javascript được sử dụng để trả về kiểu dữ liệu của một biến hoặc một giá trị. Toán tử này trả về một chuỗi biểu thị kiểu dữ liệu của biến hoặc giá trị được xác định.
Ví dụ:
var a;
typeof a; // "undefined"
a = "hello world";
typeof a; // "string"
a = 42;
typeof a; // "number"
a = true;
typeof a; // "boolean"
a = null;
typeof a; // "object" -- weird, bug
a = undefined;
typeof a; // "undefined"
a = { b: "c" };
typeof a; // "object"
C15:
Lập trình bất đồng bộ trong Javascript là gì?
Xem đáp án
Lập trình bất đồng bộ (async) là 1 phần quan trọng trong javascript. Cách tiếp cận phổ biến là sử dụng các callback
.
Ví dụ như 1 lệnh ajax gửi request lên server và sau khi nhận được thành công data trả về từ server thì callback sẽ được thực hiện. Thời điểm mà callback được thực hiện không phải là ngay lập tức sau khi có request ajax mà có thể là 1 vài giây sau đó tùy thuộc vào tốc độ xử lí của server.
Ví dụ khác:
console.log("A");
console.log("B");
console.log("C");
thực hiện đoạn code trên cho ra kết quả:
A;
B;
C;
Nhìn vào đoạn code này ta thấy nó hoạt động giống như cơ chế đồng bộ nghĩa là sẽ thực thi từng dòng lệnh một. Ta thay đổi đoạn code trên 1 chút như sau:
console.log("A");
setTimeout(function () {
console.log("B");
}, 2000);
console.log("C");
Đoạn code trên cho ra kết quả:
A;
C;
B;
Ta thấy rằng thay vì chờ đợi phần lệnh console.log('B')
trong setTimeout()
chạy xong thì lệnh console.log('C')
mới thực hiện giống như cơ chế chạy đồng bộ thì lệnh console.log('C')
lại trả về kết quả trước. Đây chính là điểm khác nhau giữa cơ chế bất đồng bộ và đồng bộ trong việc lập trình javascript.
C16:
Kết quả đoạn code sau là gì?
const person = { name: "Lydia" };
Object.defineProperty(person, "age", { value: 21 });
console.log(person);
console.log(Object.keys(person));
- A:
{ name: "Lydia", age: 21 }
,["name", "age"]
- B:
{ name: "Lydia", age: 21 }
,["name"]
- C:
{ name: "Lydia"}
,["name", "age"]
- D:
{ name: "Lydia"}
,["age"]
Xem đáp án
Đáp án: B
Với phương thức defineProperty
, chúng ta có thể thêm các thuộc tính mới, cũng như sửa các thuộc tính sẵn có của object. Khi chúng ta thêm thuộc tính vào object bằng defineProperty
, chúng sẽ mặc định là thuộc tính not enumerable. Phương thức Object.keys
sẽ trả về tất cả các thuộc tính enumerable của object, trong trường hợp này thì chỉ có "name"
mà thôi.
Thêm nữa, các thuộc tính được thêm bởi defineProperty
là mặc định không thể thay đổi được. Tất nhiên ta có thể override các điều đó bằng các thuộc tính như writable
, configurable
và enumerable
. Tức là defineProperty
là một cách rất mềm dẻo để tạo ra và điều chỉnh thuộc tính của object.
C17:
Javascript là ngôn ngữ pass-by-reference hay pass-by-value không?
Xem đáp án
Nó luôn pass-by-value, nhưng đối với các đối tượng, giá trị của biến đối tượng đó là một tham chiếu (reference). Do đó, khi bạn truyền một đối tượng và thay đổi các thành viên của nó, những thay đổi đó vẫn tồn tại bên ngoài hàm. Điều này làm cho nó giống như đi qua tham chiếu. Nhưng nếu bạn thực sự thay đổi giá trị của biến đối tượng, bạn sẽ thấy rằng thay đổi không tồn tại, chứng tỏ rằng nó thực sự chuyển qua giá trị. Ví dụ:
function changeStuff(a, b, c) {
a = a * 10;
b.item = "changed";
c = { item: "changed" };
}
var num = 10;
var obj1 = { item: "unchanged" };
var obj2 = { item: "unchanged" };
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);
console.log(obj2.item);
Kết quả:
10;
changed;
unchanged;
C18:
Hàm anonymous là gì và khi nào nên sử dụng?
Xem đáp án
Anonymous Function là gì?
Một Anonymous Function là một hàm không có tên (hay còn gọi là hàm ẩn danh), là một hàm được sinh ra đúng vào thời điểm chạy của chương trình.
Thông thường khi bạn khai báo một hàm thì trình biên dịch sẽ lưu lại trong bộ nhớ nên bạn có thể gọi ở trên hay dưới vị trí khai báo hàm đều được, nhưng với anonymous functions thì nó sẽ được sinh ra khi trình biên dịch xử lý tới vị trí của nó. Ví dụ:
// gọi trước hàm
showDomain(); // hoạt động
function showDomain() {
alert("Học Javascript tại kungfutech.edu.vn");
}
// gọi sau hàm
showDomain(); // hoạt động
Trong ví dụ này cho dù bạn gọi hàm ở phía trên hay dưới đều hoạt động tốt là vì chương trình đã lưu hàm đó vào bộ nhớ. Nhưng nếu ta sử dụng anonymous function như ví dụ dưới đây sẽ bị lỗi ngay.
// gọi trước hàm
showDomain(); // Lỗi vì hàm này chưa tồn tại
var showDomain = function () {
alert("Học Javascript tại kungfutech.edu.vn");
};
// gọi sau hàm
showDomain(); // hoạt động vì hàm đã tồn tại
Khi nào thì cần dùng Anonymous Function?
Nếu hàm cần được truyền ở nhiều nơi:
- Định nghĩa 1 hàm thông thường.
- Truyền hàm đó vào 1 hàm.
Nếu hàm chỉ truyền 1 nơi?
-
Bất tiện khi tạo ra 1 hàm mới (các chi phí như đặt tên hàm).
-
Giúp tăng tính ràng buộc cho việc chỉ được phép truyền 1 lần.
-
Bên cạnh đó hỗ trợ được thêm khả năng chỉ gọi 1 lần.
C19:
Kết quả đoạn code sau là gì?
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius,
};
shape.diameter();
shape.perimeter();
- A:
20
and62.83185307179586
- B:
20
andNaN
- C:
20
and63
- D:
NaN
and63
Xem đáp án
Đáp án: B
Chú ý rằng giá trị diameter
là một hàm thông thường, còn perimeter
là một arrow function.
Không giống như hàm thông thường, với arrow function, biếnthis
sẽ trỏ tới surrounding scope! Có nghĩa là khi chúng ta gọi perimeter
, nó sẽ không được gọi bởi shape object, mà nó được gọi bởi object nào đó tại surrounding scope (ví dụ window
chẳng hạn).
Khi không có giá trị radius
tại object đó, nó sẽ trả về undefined
.
C20:
Kết quả đoạn code sau là gì?
const { name: myName } = { name: "Lydia" };
console.log(name);
- A:
"Lydia"
- B:
"myName"
- C:
undefined
- D:
ReferenceError
Xem đáp án
Đáp án: D
Khi ta tiến hành unpack giá trị name
từ object ở phía bên phải, ta đã gán giá trị "Lydia"
của nó cho biến có tên là myName
.
Với cú pháp { name: myName }
, chúng ta muốn khai báo một biến myName
với giá trị là giá trị của thuộc tính name
trong object phía bên phải.
Do name
chưa được định nghĩa, nên ghi log ra, nó sẽ throw ra một ReferenceError.
C21:
Kết quả đoạn code sau là gì?
let greeting;
greetign = {}; // Lỗi đánh máy!
console.log(greetign);
- A:
{}
- B:
ReferenceError: greetign is not defined
- C:
undefined
Xem đáp án
Đáp án: A
Nó sẽ log ra object greetign
, bởi vì chúng ta vừa khởi tạo một global object! Khi chúng ta đánh máy nhầm greeting
thành greetign
, trình thông dịch của JS sẽ coi nó như là global.greetign = {}
(hay window.greetign = {}
nếu chạy trên browser).
Để tránh điều này chúng ta có thể sử dụng "use strict"
. Nó sẽ đảm bảo rẳng các biến đều phải được khai báo trước khi sử dụng.
C22:
Viết một hàm có tên lucky_sevens
nhận một mảng các số nguyên và trả về giá trị true
nếu ba phần tử liên tiếp bất kỳ có tổng bằng 7.
Xem đáp án
Giải pháp:
function lucky_sevens(arr) {
// nếu mảng có ít hơn 3 phần tử thì trả về false
if (arr.length < 3) {
return "not possible";
}
// chạy một vòng lặp từ phần tử vị trí 3 đến cuối và kiểm tra xem 3 số liên tiếp có tổng bằng 7 hay không
for (var i = 2; i < arr.length; i++) {
if (arr[i] + arr[i - 1] + arr[i - 2] === 7) {
return true;
}
}
return false;
}
lucky_sevens([2, 1, 5, 1, 0]);
C23:
Đây có phải là một pure function không?
function sum(a, b) {
return a + b;
}
- A: Yes
- B: No
Xem đáp án
Đáp án: A
Một hàm được gọi là pure function khi nó luôn luôn trả về một giá trị giống nhau, nếu đối số đưa vào là giống nhau.
Hàm sum
luôn trả về giá trị giống nhau. Nếu ta đưa vào 1
và 2
, nó sẽ luôn trả về 3
. Nếu ta đưa vào 5
và 10
, nó luôn trả về 15
. Cứ như vậy, đây là một pure function.
C24:
Điểm khác nhau giữa method GET
và method POST
?
Xem đáp án
Điểm khác nhau giữa method GET
và method POST
GET:
- Có thể bị cache.
- Nằm trong history của browser.
- Có thể bookmarked
- Dữ liệu truyền đi sẽ hiện trên url và chúng bị giới hạn (độ dài tối đa của URL là 2048).
- Hạn chế về loại dữ liệu: chỉ có ký tự ASCII được cho phép
POST:
-
Dùng để submit data tới servers.
-
Không bị cache
-
Không nằm trong history của browser.
-
Không thể bookmarked
-
Dữ liệu truyền đi nằm trong body của request.
-
Không bị giới hạn dữ liệu truyền đi.
-
Không giới hạn loại dữ liệu truyền đi.
C25:
Sự khác biệt giữa throw Error('msg')
so với throw new Error('msg')
là gì?
Xem đáp án
Trong JavaScript, throw Error('msg')
và throw new Error('msg')
là hai cách khác nhau để ném một lỗi (throw an error).
Cú pháp throw Error('msg')
sẽ tạo ra một đối tượng lỗi (error object) với thuộc tính message được thiết lập là ‘msg’ và sau đó ném đối tượng lỗi đó. Tuy nhiên, việc này sẽ không cung cấp đầy đủ thông tin về lỗi bởi vì nó không phải là một đối tượng lỗi chuẩn được định nghĩa trong JavaScript.
Còn cú pháp throw new Error('msg')
sẽ tạo ra một đối tượng lỗi mới được tạo bởi từ khóa new
và thuộc tính message được thiết lập là ‘msg’. Đối tượng lỗi này sẽ được định nghĩa là một đối tượng lỗi chuẩn trong JavaScript, vì vậy nó cung cấp đầy đủ các thuộc tính và phương thức để giúp xác định và xử lý lỗi.
Vì vậy, khi ném lỗi trong JavaScript, thường nên sử dụng cú pháp throw new Error('msg')
để đảm bảo rằng đối tượng lỗi được tạo ra là đối tượng lỗi chuẩn và cung cấp đầy đủ các thông tin về lỗi cần thiết để xác định và xử lý lỗi.
C26:
Kết quả đoạn code sau là gì?
function sayHi() {
console.log(name);
console.log(age);
var name = "Lydia";
let age = 21;
}
sayHi();
- A:
Lydia
vàundefined
- B:
Lydia
vàReferenceError
- C:
ReferenceError
và21
- D:
undefined
vàReferenceError
Xem đáp án
Đáp án: D
Trong hàm chúng ta đã khai báo biến name
với var
. Điều đó có nghĩa là biến này sẽ được hoisted (một vùng nhớ sẽ được set up khi biến được khởi tạo) với giá trị mặc định là undefined
, cho tới khi chúng ta thực sự định nghĩa biến đó. Trong hàm này, chúng ta chưa hề định nghĩa biến name
tại dòng mà ta log ra, vậy nên giá trị mặc định của nó vẫn là undefined
.
Các biến được khai báo với keyword let
(và const
) cũng được hoisted nhưng không giống như var
, chúng không được khởi tạo. Chúng ta sẽ không thể truy cập chúng cho tới khi chúng ta khai báo (khởi tạo) chúng. Người ta gọi đó là “temporal dead zone”. Khi ta truy cập đến một giá trị trước khi chúng được khai báo, JavaScript sẽ throws một ReferenceError
.
C27:
Kết quả đoạn code sau là gì?
console.log(String.raw`Hello
world`);
- A:
Hello world!
- B:
Hello
world
- C:
Hello world
- D: `Hello
Xem đáp án
Đáp án: C
String.raw
trả về chuỗi nguyên bản, các ký tự (
, ,
etc.) sẽ vẫn là nguyên bản và không biến thành xuống dòng hay khoảng trắng! Nếu ta không để là chuỗi nguyên bản, sẽ có trường hợp xảy ra lỗi không mong muốn, ví dụ với đường dẫn:
const path = `C:DocumentsProjects able.html`
Sẽ cho ta chuỗi là:
"C:DocumentsProjects able.html"
Với String.raw
, nó sẽ trả về là:
C:DocumentsProjects able.html
Do đó, trong trường hợp này Hello world
sẽ được ghi ra.
C28:
Kết quả đoạn code sau là gì?
const box = { x: 10, y: 20 };
Object.freeze(box);
const shape = box;
shape.x = 100;
console.log(shape);
- A:
{ x: 100, y: 20 }
- B:
{ x: 10, y: 20 }
- C:
{ x: 100 }
- D:
ReferenceError
Xem đáp án
Đáp án: B
Object.freeze
khiến cho chúng ta không thể thêm vào, xóa đi hay thay đổi bất kì thuộc tính nào của object (trừ phi giá trị của thuộc tính lại chính là một object khác).
Khi chúng ta tạo ra biến shape
và set cho nó giá trị bằng với một object đã được đóng băng là box
, thì shape
cũng sẽ trỏ tới một object đã được đóng băng. Ta có thể check một object có đang bị đóng băng hay không bằng Object.isFrozen
. Trong trường hợp này, Object.isFrozen(shape)
trả về true, vì shape
đang trỏ tới một object bị đóng băng.
Do đó, cộng với việc x
không phải là object, ta sẽ không thể thay đổi giá trị của x
. x
sẽ vẫn là 10
, và { x: 10, y: 20 }
được ghi ra.
C29:
Kết quả đoạn code sau là gì?
const myPromise = () => Promise.resolve("I have resolved!");
function firstFunction() {
myPromise().then((res) => console.log(res));
console.log("second");
}
async function secondFunction() {
console.log(await myPromise());
console.log("second");
}
firstFunction();
secondFunction();
- A:
I have resolved!
,second
vàI have resolved!
,second
- B:
second
,I have resolved!
vàsecond
,I have resolved!
- C:
I have resolved!
,second
vàsecond
,I have resolved!
- D:
second
,I have resolved!
vàI have resolved!
,second
Xem đáp án
Đáp án: D
Có thể tưởng tượng đơn giản cách promise thực thi như sau: bây giờ tôi sẽ để tạm nó sang một bên vì nó tính toán mất thời gian. Chỉ khi nào nó được hoàn thành (resolved) hay bị hủy bỏ (rejected) hay khi call stack trở nên rỗng thì tôi sẽ lấy giá trị trả về ra.
Dù chúng ta có thể sử dụng giá trị thu được bằng cú pháp .then
, hoặc sử dụng cặp cú pháp await/async
, nhưng, cách chúng hoạt động là khác nhau.
Trong firstFunction
, chúng ta đưa promise qua một bên chờ cho nó tính toán xong, và vẫn tiếp tục chạy những code tiếp sau đó, theo đó console.log('second')
sẽ được chạy. Sau đó promise được hoàn thành trả về giá trị I have resolved
, giá trị này sẽ được log ra khi call stack trở nên rỗng.
Với từ khóa await
trong secondFunction
, ta đã tạm dừng một hàm bất đồng bộ cho tới khi chúng trả về giá trị, sau đó ta mới đi tiếp đến các câu lệnh tiếp theo.
Do đó nó sẽ chờ cho tới khi myPromise
được hoàn thành và trả về giá trị I have resolved
, sau đó chúng ta sẽ chạy tiếp câu lệnh tiếp theo in ra second
.
C30:
Coercion
trong JavaScript là gì?
Xem đáp án
Coercion trong JavaScript là quá trình chuyển đổi tự động của một kiểu dữ liệu sang kiểu dữ liệu khác. Trong JavaScript, có hai loại coercion là implicit coercion (chuyển đổi ngầm định) và explicit coercion (chuyển đổi rõ ràng). Hiểu đơn giản là một cái chuyển kiểu dữ liệu một cách tường minh, mình có thế nhìn thấy được qua mã, trong khi đó kiểu kia thì coercion ngầm định.
Đây là một ví dụ về explicit coercion:
var a = "42";
var b = Number(a);
a; // "42"
b; // 42 -- the number!
Và đây là một ví dụ về implicit coercion:
var a = "42";
var b = a * 1; // "42" implicitly coerced to 42 here
a; // "42"
b; // 42 -- the number!
1. Equality operator (==)
console.log(69 == "69"); // true
Sỡ dĩ điều này xảy ra là gì trước khi phép so sánh thực sự xảy ra, javascript sẽ thực hiện coercion. Nói cách khác, nếu 2 value có cùng type, thì chỉ việc so sánh, nhưng nếu chúng khác type, javascript sẽ cố gắng để convert chúng về cùng 1 type rồi mới so sánh. Ở đây 69 và ‘69’ đã được convert về cùng 1 type là number. 69 vẫn giữ nguyên, nhưng ‘69’ sẽ được convert thành 69. 69 == '69' => 69 == 69 => true
coercion không tuần theo 1 logic nào cả, mà nó tuân theo các rules mà ta phải nhớ, áp dụng cho vô số các trường hợp (khi so sánh các value, type với nhau). Tôi sẽ liệt kê ra đây 1 số trường hợp mà các bạn hay gặp
1.1 So sánh number và string
string sẽ được convert thành number, sau đó so sánh. Trường hợp này dẫn tới 2 trường hợp, 1 là string convert được thành number (ví dụ các string như ‘10’, ‘1252’, …), việc so sánh là bình thường. Trường hợp 2 là string không thể convert được thành số (ví dụ các string như ‘abc’, ‘s1fe13324’, …) các gía trị này sẽ convert thành NaN, và như tôi đã nói ở trên, NaN không bằng giá trị nào cả, nên kết quả trả về luôn là false
1.2 So sánh boolean với các type values khác
Đầu tiền boolean value (true => 1, false => 0) thành number, rồi mới so sánh, ví dụ “1” == true sẽ được đổi thành 1 == 1 do true được convert thành number 1 và string “1” convert thành number 1, dẫn tới result là true.
2. Strict equality operator (===)
Operator này sẽ chỉ compare 2 value, just it, không coercion, không convert type.
3. Other operator (+, -, *, /)
Trường hợp mà ta hay gặp nhất với các operator này thao tác với number, number và string. Với toàn number thì không nói làm gì. Nhưng giữa number với string, thì ngoài trừ toán tử + sẽ tiến hành chuyển đổi number thành string rồi tiến hành phép nối string, các toán tử khác (-, *, /) đều sẽ convert string thành number và tiến hành phép toán như thông thường, trong trường hợp không convert được thì các bạn biết điều gì xảy ra rồi đấy (NaN)
4 + "3"; // "43"
4 - "3"; // 1
"4" * "3"; //12
"4" / 3; //1.33333
4 + "a";
("4a");
4 - "a"; // NaN
4 * "a"; // NaN
4 / "a"; //NaN
C31:
Kết quả đoạn code sau là gì?
async function getData() {
return await Promise.resolve("I made it!");
}
const data = getData();
console.log(data);
- A:
"I made it!"
- B:
Promise {<resolved>: "I made it!"}
- C:
Promise {<pending>}
- D:
undefined
Xem đáp án
Đáp án: C
Một hàm async
luôn luôn trả về một promise
. await
sẽ chờ cho tới khi promise đó được hoàn thành: một pending promise sẽ được trả về khi ta gọi getData()
bằng cách gán nó cho biến data
.
Nếu ta muốn truy cập giá trị đã hoàn thành của promise, trong trường hợp này là "I made it"
, ta có thể sử dụng hàm .then()
ngay sau data
như sau:
data.then(res => console.log(res))
Khi này nó sẽ ghi ra "I made it!"
C32:
Kết quả đoạn code sau là gì?
function Car() {
this.make = "Lamborghini";
return { make: "Maserati" };
}
const myCar = new Car();
console.log(myCar.make);
- A:
"Lamborghini"
- B:
"Maserati"
- C:
ReferenceError
- D:
TypeError
Xem đáp án
Đáp án: B
Khi chúng ta trả về một thuộc tính, giá trị của thuộc tính bằng với giá trị đã được trả về bởi lệnh return, chứ không phải giá trị được set trong constructor. Chúng ta trả về giá trị là "Maserati"
, do đó myCar.make
sẽ là "Maserati"
.
C33:
Kết quả đoạn code sau là gì?
Promise.resolve(5);
- A:
5
- B:
Promise {<pending>: 5}
- C:
Promise {<fulfilled>: 5}
- D:
Error
Xem đáp án
Đáp án: C
Ta có thể truyền vào giá trị bất kì cho Promise.resolve
, dù có là promise hay không promise. Bản thân nó sẽ là một hàm trả về một promise với giá trị đã được resolved.
Trong trường hợp này ta đưa vào giá trị 5
. Nó sẽ trả về một resolved promise với giá trị 5
.
C34:
Giải thích về phương thức call()
, aplly()
và bind()
?
Xem đáp án
call()
Đó là một phương thức được xác định trước trong javascript.
Phương thức này gọi một phương thức (hàm) bằng cách chỉ định đối tượng sở hữu.
Ví dụ 1:
function sayHello() {
return "Hello " + this.name;
}
var obj = { name: "Sandy" };
sayHello.call(obj);
// Returns "Hello Sandy"
Phương thức call()
cho phép một đối tượng sử dụng phương thức của đối tượng khác
Ví dụ 2:
var person = {
age: 23,
getAge: function () {
return this.age;
},
};
var person2 = { age: 54 };
person.getAge.call(person2);
// Returns 54
call() chấp nhận tham số:
function saySomething(message) {
return this.name + " is " + message;
}
var person4 = { name: "John" };
saySomething.call(person4, "awesome");
// Returns "John is awesome"
apply()
Tương tự như phương thức call()
. Nhưng khác ở điểm phương thức call()
nhận các tham số riêng biệt, trong khi apply()
nhận tham số là một mảng.
function saySomething(message) {
return this.name + " is " + message;
}
var person4 = { name: "John" };
saySomething.apply(person4, ["awesome"]);
bind()
Phương thức này trả về một hàm mới, trong đó giá trị của this
sẽ được liên kết với đối tượng sở hữu, được cung cấp dưới dạng một tham số.
Ví dụ:
var bikeDetails = {
displayDetails: function (registrationNumber, brandName) {
return (
this.name +
" , " +
"bike details: " +
registrationNumber +
" , " +
brandName
);
},
};
var person1 = { name: "Vivek" };
var detailsOfPerson1 = bikeDetails.displayDetails.bind(
person1,
"TS0122",
"Bullet",
);
// Binds the displayDetails function to the person1 object
detailsOfPerson1();
// Returns Vivek, bike details: TS0452, Thunderbird
C35:
Giá trị nào của ‘method’ sẽ được trả về với log '{ name: "Lydia", age: 22 }'
?
const keys = ["name", "age"];
const values = ["Lydia", 22];
const method =
/* ?? */
Object[method](
keys.map((_, i) => {
return [keys[i], values[i]];
}),
); // { name: "Lydia", age: 22 }
- A:
entries
- B:
values
- C:
fromEntries
- D:
forEach
Xem đáp án
Đáp án: C
Hàm fromEntries
trả về một mảng 2d trong một object. Phần tử đầu tiên trong từng mảng con sẽ là từ khoá và phần tử thứ hai trong từng mảng con sẽ là giá trị. Trong trường hợp này, ta tiến hành map qua mảng keys
, nó sẽ trả về một mảng mà phần tử đầu tiên của mảng đó là phần tử trên thứ tự hiện tại của mảng key, và phần tử thứ hai của mảng đó là phần tử trên thứ tự hiện tại của mảng values.
Theo như trên thì ta tạo ra một mảng gồm những mảng con chứa đựng những từ khoá và giá trị đúng, và nó trả về { name: "Lydia", age: 22 }
.
C36:
Kết quả đoạn code sau là gì?
async function getData() {
return await Promise.resolve("I made it!");
}
const data = getData();
console.log(data);
- A:
"I made it!"
- B:
Promise {<resolved>: "I made it!"}
- C:
Promise {<pending>}
- D:
undefined
Xem đáp án
Đáp án: C
Một hàm async
luôn luôn trả về một promise
. await
sẽ chờ cho tới khi promise đó được hoàn thành: một pending promise sẽ được trả về khi ta gọi getData()
bằng cách gán nó cho biến data
.
Nếu ta muốn truy cập giá trị đã hoàn thành của promise, trong trường hợp này là "I made it"
, ta có thể sử dụng hàm .then()
ngay sau data
như sau:
data.then(res => console.log(res))
Khi này nó sẽ ghi ra "I made it!"
C37:
Hàm setInterval trả về cái gì?
setInterval(() => console.log("Hi"), 1000);
- A: một id duy nhất
- B: số lượng milliseconds
- C: function truyền vào
- D:
undefined
Xem đáp án
Đáp án: A
Nó trả về một id duy nhất. Id này dùng để clear interval sau này với hàm clearInterval()
.
C38:
Kết quả đoạn code sau là gì?
let c = { greeting: "Hey!" };
let d;
d = c;
c.greeting = "Hello";
console.log(d.greeting);
- A:
Hello
- B:
Hey
- C:
undefined
- D:
ReferenceError
- E:
TypeError
Xem đáp án
Đáp án: A
Trong JavaScript, tất cả các object sẽ được tham chiếu khi chúng được gán _bằng_wwwww một giá trị khác.
Đầu tiên, giá trị c
có giá trị là một object. Sau đó, chúng ta gán d
tham chiếu tới object mà c
trỏ tới.
Khi ta thay đổi giá trị của object, tất cả các biến tham chiếu cũng đều thay đổi giá trị theo.
C39:
Kết quả đoạn code sau là gì?
let a = 3;
let b = new Number(3);
let c = 3;
console.log(a == b);
console.log(a === b);
console.log(b === c);
- A:
true
false
true
- B:
false
false
true
- C:
true
false
false
- D:
false
true
true
Xem đáp án
Đáp án: C
new Number()
là một hàm built-in constructor. Mặc dù nó trông có vẻ giống như là một số, nhưng không phải: nó thực sự là một object với hàng tá những thông số khác nữa.
Khi ta sử dụng phép so sánh ==
, nó đơn thuần chỉ kiểm tra xem 2 biến có giá trị giống nhau. Chúng đều có giá trị là 3
, vậy nên phép toán đầu trả về true
.
Tuy nhiên khi sử dụng phép so sánh ===
, cả giá trị và kiểu đều phải giống nhau. Rõ ràng: new Number()
không phải là một số, nó là một object. Cả 2 phép toán sau đều trả về false.
C40:
Kết quả đoạn code sau là gì?
const add = () => {
const cache = {};
return (num) => {
if (num in cache) {
return `From cache! ${cache[num]}`;
} else {
const result = num + 10;
cache[num] = result;
return `Calculated! ${result}`;
}
};
};
const addFunction = add();
console.log(addFunction(10));
console.log(addFunction(10));
console.log(addFunction(5 * 2));
- A:
Calculated! 20
Calculated! 20
Calculated! 20
- B:
Calculated! 20
From cache! 20
Calculated! 20
- C:
Calculated! 20
From cache! 20
From cache! 20
- D:
Calculated! 20
From cache! 20
Error
Xem đáp án
Đáp án: C
Hàm add
chính là một hàm memoized (hàm có nhớ). Với việc có nhớ, chúng ta có thể cache lại kết quả của function để tăng tốc độ tính toán lên. Trong trường hợp này, chúng ta tạo ra một cache
object để lưu trữ những kết quả tính toán trước đó.
Mỗi lần chúng ta gọi hàm addFunction
với đối số giống nhau, đầu tiên nó sẽ check xem đối số đó có tồn tại trong cache hay không. Nếu có, giá trị trong cache sẽ được trả về luôn, tiết kiệm thời gian tính toán. Còn nếu không thì nó sẽ tiến hành tính toán kết quả và tiếp tục lưu vào cache.
Chúng ta gọi hàm addFunction
ba lần với cùng một đối số: trong lần gọi đầu tiên, giá trị của num
là 10
và chưa có mặt trong cache. Do đó num in cache
trả về false
, và sẽ chạy vào else block: Calculated! 20
sẽ được ghi ra, và 10 sẽ được đưa vào cạche. cache
khi này sẽ là { 10: 20 }
.
Tại lần gọi thứ hai, cache
object đã có giá trị 10
. num in cache
trả về true
, và 'From cache! 20'
được ghi ra.
Tại lần gọi thứ ba, ta đưa vào 5 * 2
, tức 10
vào hàm. Tiếp tục giống như trên, 'From cache! 20'
sẽ được ghi ra.
C41:
Kết quả đoạn code sau là gì?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
- A:
0 1 2
and0 1 2
- B:
0 1 2
and3 3 3
- C:
3 3 3
and0 1 2
Xem đáp án
Đáp án: C
Bởi vì event queue trong JavaScript, hàm setTimeout
callback sẽ được gọi sau khi vòng lặp được thực hiện. Bời vì biến i
trong vòng lặp đầu tiên được khai báo với từ khóa var
, nên nó sẽ là một biến global. Trong suốt vòng lặp, mỗi lần chúng ta tăng giá trị của i
lên 1
, sử dụng phép toán ++
. Cho tới khi callback setTimeout
được gọi, giá trị của i
đã trở thành 3
rồi.
Trong vòng lặp thứ 2, biến i
được khai báo với từ khóa let
, có nghĩa nó là một biến block-scoped (block là những gì được viết bên trong cặp ngoặc { }
). Tại mỗi vòng lặp, i
sẽ là một biến mới có một giá trị mới, và giá trị đó có scope là bên trong vòng lặp mà thôi.
C42:
Kết quả đoạn code sau là gì?
+true;
!"Lydia";
- A:
1
andfalse
- B:
false
andNaN
- C:
false
andfalse
Xem đáp án
Đáp án: A
Phép toán cộng +
sẽ convert một toán hạng sang dạng number. true
là 1
, và false
is 0
.
Chuỗi 'Lydia'
là một truthy value. Điều chúng ta thật sự đang hỏi chính là “có phải một giá trị truthy là falsy?“. Rõ ràng câu trả lời là false
rồi.
C43:
Cái nào đúng?
const bird = {
size: "small",
};
const mouse = {
name: "Mickey",
small: true,
};
- A:
mouse.bird.size
không hợp lệ - B:
mouse[bird.size]
không hợp lệ - C:
mouse[bird["size"]]
không hợp lệ - D: Tất cả đều hợp lệ
Xem đáp án
Đáp án: A
Trong JavaScript thì tất cả keys của các object đều là string (ngoại trừ khi nó là một Symbol). Dù chúng ta không viết chúng như một string, về cơ bản chúng sẽ luôn được chuyển sang dạng string.
JavaScript thông dịch (hay unboxes) từng câu lệnh. Khi chúng ta sử dụng cặp dấu ngoặc []
, nó sẽ tìm kiếm dấu mở ngoặc đầu tiên [
, và sẽ tiếp tục tìm kiếm cho tới khi gặp dấu đóng ngoặc ]
. Chỉ khi đó thì câu lệnh mới được thực thi.
mouse[bird.size]
: Giá trị đầu tiên bird.size
là "small"
. mouse["small"]
sẽ trả về true
Tuy nhiên, khi chúng ta sử dụng dấu chấm .
, điều trên không còn đúng nữa. mouse
không hề có key nào tên là bird
, có nghĩa mouse.bird
sẽ là undefined
. Sau đó chúng ta gọi size
sử dụng chấm .
: mouse.bird.size
. Vì mouse.bird
là undefined
, lời gọi sẽ trở thành undefined.size
. Đây là một lời gọi không hợp lệ, nó sẽ throw ra một lỗi kiểu như Cannot read property "size" of undefined
.
C44:
Thuộc tính NaN
trong JavaScript là gì?
Xem đáp án
Thuộc tính NaN biểu diễn một giá trị Not-a-Number. Nó biểu thị một giá trị không phải là số.
typeof
của NaN trả về Number
. Muốn kiểm tra một giá trị có phải NaN không, có thể dùng hàm isNaN()
.
Ví dụ:
isNaN("Hello"); // Returns true
isNaN(345); // Returns false
isNaN("1"); // Returns false, since '1' is converted to Number type which results in 0 ( a number)
isNaN(true); // Returns false, since true converted to Number type results in 1 ( a number)
isNaN(false); // Returns false
isNaN(undefined); // Returns true
C45:
Cách lặp các phần tử của mảng trong Javascript?
Xem đáp án
Cách thứ nhất: sử dụng for
, cạm bẫy phổ biến ở đây là var
nằm trong phạm vi hàm chứ không phải phạm vi khối và hầu hết bạn muốn biến i thuộc phạm vi khối. ES2015 giới thiệu let
có phạm vi khối và bạn nên sử dụng nó để thay thế. Vì - vậy, điều này trở thành
for (let i = 0; i <arr.length; i ++) {
...for (let i = 0; i <arr.length; i ++)
}
Cách thứ 2: sử dụng forEach
, cấu trúc này đôi khi có thể thuận tiện hơn vì bạn không phải sử dụng index nếu tất cả những gì bạn cần là các phần tử mảng. Ngoài ra còn có các phương thức every
và some
sẽ cho phép bạn kết thúc - sớm quá trình lặp.
arr.forEach(function (el, index) {
...
}).
Tuy vậy, vòng lặp for
cho phép linh hoạt hơn, chẳng hạn như kết thúc sớm vòng lặp bằng cách sử dụng break hoặc tăng vòng lặp nhiều hơn.
C46:
Kết quả đoạn code sau là gì?
function addToList(item, list) {
return list.push(item);
}
const result = addToList("apple", ["banana"]);
console.log(result);
- A:
['apple', 'banana']
- B:
2
- C:
true
- D:
undefined
Xem đáp án
Đáp án: B
Hàm .push()
trả về độ dài của mảng mới! Trước đó, mảng chỉ hồm một phần tử là "banana"
và có độ dài là 1
. Sau khi thêm chuỗi "apple"
vào mảng, mảng lúc này có hai chuỗi và có độ dài là 2
. Do đó hàm addToList
sẽ trả về 2.
Hàm push
sẽ thay đổi chính bản thân mảng truyền vào. Do đó nếu chúng ta muốn trả về mảng thay vì chỉ trả về độ dài, chúng ta nên trả về trực tiếp mảng list
sau khi đã thêm item
vào đó.
C47:
Làm sao để clone một mảng?
Xem đáp án
Để clone một mảng, chúng ta có thể sử dụng một trong các cách dưới đây:
1. Sử dụng hàm slice
- Hàm
slice()
là một phương thức của mảng trong Javascript và được sử dụng để sao chép một phần hoặc toàn bộ các phần tử của mảng. - Khi sử dụng slice() để clone một mảng, chúng ta sẽ tạo ra một bản sao của mảng gốc. Các thay đổi trên mảng sao chép sẽ không ảnh hưởng đến mảng gốc, và ngược lại.
Ví dụ:
const originalArray = [1, 2, 3, 4];
const clonedArray = originalArray.slice();
2. Sử dụng hàm JSON.stringify()
và JSON.parse()
(deep copy)
- Hàm
JSON.stringify()
được sử dụng để chuyển đổi một đối tượng Javascript thành một chuỗi JSON. - Hàm
JSON.parse()
được sử dụng để chuyển đổi một chuỗi JSON thành một đối tượng Javascript. - Khi sử dụng
JSON.stringify()
vàJSON.parse()
để clone một mảng, chúng ta sẽ tạo ra một bản sao sâu của mảng gốc. Các thay đổi trên mảng sao chép sẽ không ảnh hưởng đến mảng gốc, và ngược lại.
Ví dụ:
const originalArray = [1, 2, 3, 4];
const clonedArray = originalArray.slice();
3. Sử dụng toán tử spread operator [...]
trong ES6:
- Toán tử
spread (...)
là một tính năng mới được giới thiệu trong ES6, cho phép chúng ta truyền các phần tử của một mảng vào trong một mảng khác. - Khi sử dụng toán tử spread operator để clone một mảng, chúng ta sẽ tạo ra một bản sao của mảng gốc. Các thay đổi trên mảng sao chép sẽ không ảnh hưởng đến mảng gốc, và ngược lại.
Ví dụ:
const originalArray = [1, 2, 3, 4];
const clonedArray = [...originalArray];
4. Sử dụng Object.assign
- Hàm
Object.assign()
được sử dụng để sao chép giá trị của tất cả các thuộc tính có thể liệt kê từ một hoặc nhiều đối tượng nguồn đến một đối tượng đích. Nó trả về đối tượng đích.
Ví dụ:
let originArr = [1, 2, 3];
let newArr = Object.assign([], originArr);
console.log(newArr); // Output: [1, 2, 3]
5. Sử dụng Array.map()
- Phương thức Array.map() cũng có thể được sử dụng để clone một mảng. Đây là một phương pháp khá đơn giản, bạn chỉ cần tạo một mảng mới bằng cách ánh xạ các giá trị của mảng gốc sang mảng mới. Ví dụ:
const arrOrigin = [1, 2, 3];
const newArr = arrOrigin.map(x => x);
Trong đó, arrOrigin.map()
sẽ tạo ra một mảng mới bằng cách ánh xạ các giá trị của mảng gốc arrOrigin
vào mảng mới. Mảng mới này sẽ được gán cho biến newArr
.
Ngoài ra chúng ta cũng có thể clone mảng qua các hàm như vòng lặp while()
, vòng lặp for()
, Array.filter()
,…
C48:
Điều gì sẽ xảy ra khi chúng ta làm thế này?
function bark() {
console.log("Woof!");
}
bark.animal = "dog";
- A: Hoàn toàn không có vấn đề gì!
- B:
SyntaxError
. Bạn không thể thêm thuộc tính theo cách này. - C:
undefined
- D:
ReferenceError
Xem đáp án
Đáp án: A
Điều này là có thể với Javascript, bởi vì function
cũng chỉ là object
mà thôi! (Mọi primitive types đều là object)
Function là một object đặc biệt. Phần code mà bạn viết không phải là function thực tế đâu. Function ở đây chính là một object với các thuộc tính. Và các thuộc tính này có thể gọi được.
C49:
Kết quả đoạn code sau là gì?
function addToList(item, list) {
return list.push(item);
}
const result = addToList("apple", ["banana"]);
console.log(result);
- A:
['apple', 'banana']
- B:
2
- C:
true
- D:
undefined
Xem đáp án
Đáp án: B
Hàm .push()
trả về độ dài của mảng mới! Trước đó, mảng chỉ hồm một phần tử là "banana"
và có độ dài là 1
. Sau khi thêm chuỗi "apple"
vào mảng, mảng lúc này có hai chuỗi và có độ dài là 2
. Do đó hàm addToList
sẽ trả về 2.
Hàm push
sẽ thay đổi chính bản thân mảng truyền vào. Do đó nếu chúng ta muốn trả về mảng thay vì chỉ trả về độ dài, chúng ta nên trả về trực tiếp mảng list
sau khi đã thêm item
vào đó.
C50:
Cách để lặp qua các thuộc tính đối tượng trong Javascript?
Xem đáp án
Cách thứ nhất: sử dụng for...in
, tuy nhiên, điều này cũng sẽ lặp lại qua các thuộc tính kế thừa của nó và chúng ta có thể thêm một lệnh kiểm tra obj.hasOwnProperty(property)
trước khi sử dụng nó.
for (var property in obj) {
console.log(property);
}
Cách thứ hai: sử dụng Object.keys()
là một phương thức tĩnh sẽ liệt kê tất cả các thuộc tính có thể liệt kê của đối tượng mà bạn truyền nó vào.
Object.keys(obj).forEach(function (property) {
...
})
Cách thứ ba: sử dụng Object.getOwnPropertyNames()
là một phương thức tĩnh sẽ liệt kê tất cả các thuộc tính có thể liệt kê và không thể liệt kê của đối tượng mà bạn truyền nó.
Object.getOwnPropertyNames(obj).forEach(function (property) {
...
})
Cách thứ tư: sử dụng Object.entries()
để tạo một mảng với tất cả các thuộc tính có thể liệt kê của nó và lặp qua.
Object.entries(items).map((item) => {
...
});
Object.entries(items).forEach((item) => {
...
});
for (const item of Object.entries(items)) {
...
}
C51:
Kết quả đoạn code sau là gì?
console.log("❤️" === "❤️");
- A:
true
- B:
false
Xem đáp án
Đáp án: A
Về cơ bản, emoji vẫn là các ký tự unicode mà thôi. Mã unicode cho hình trái tim là "U+2764 U+FE0F"
. Chúng luôn luôn là một, nên phép toán đơn giản trả về true
.
C52:
Kết quả đoạn code sau là gì?
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor;
return this.newColor;
}
constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}
const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");
- A:
orange
- B:
purple
- C:
green
- D:
TypeError
Xem đáp án
Đáp án: D
Hàm colorChange
là một hàm static (hàm tĩnh). Hàm static được thiết kế để chỉ để tồn tại ở mức class, và không thể truyền cho bất cứ instance con nào. Vì freddie
là một instance con, hàm static này sẽ không được truyền xuống, và do đó không thể gọi được tại freddie
instance: nó sẽ throw ra một TypeError
.
C53:
Giải thích về phép gán quá giá trị và phép gán qua tham chiếu?
Xem đáp án
Trong JavaScript, kiểu dữ liệu nguyên thuỷ được gán với giá trị, còn kiểu đối tượng được gán bằng tham chiếu.
Trước tiên, ta cần hiểu về điều gì xảy ra khi ta tạo một biến và gán giá trị cho nó.
var x = 2;
Trong ví dụ trên, ta tạo một biến x
và gán nó giá trị là “2”. Phép ”=” chỉ định một vài không gian trong bộ nhớ, để lưu trữ giá trị là “2” và trả về vị trí được chỉ định trong bộ nhớ. Do đó, biến x
ở trên trỏ đến vị trí trong bộ nhớ thay vì trỏ trực tiếp đến giá trị 2.
Phép gán thực hiện hành vi khác nhau khi làm việc với kiểu nguyên thuỷ và kiểu đối tượng.
Phép gán với kiểu nguyên thuỷ
var y = 234;
var z = y;
Ở ví dụ này, dòng đầu phép gán giá trị cho y
là kiểu nguyên thuỷ, sau đó ở dòng thứ hai, giá trị của y
được gán cho z
. Phép gán chỉ định một vùng không gian mới trong bộ nhớ và trả về địa chỉ của nó. Do đó, biến z
không chỉ đến vị trí của biến y
thay vào đó nó chỉ đến vùng không gian mới trong bộ nhớ.
var y = #8454; // y pointing to address of the value 234
var z = y;
var z = #5411; // z pointing to a completely new address of the value 234
// Changing the value of y
y = 23;
console.log(z); // Returns 234, since z points to a new address in the memory so changes in y will not effect z
Từ ví dụ trên, ta có thể thấy rằng các kiểu dữ liệu nguyên thủy khi được truyền cho một biến khác sẽ được truyền theo giá trị. Thay vì chỉ gán cùng một địa chỉ cho một biến khác, giá trị sẽ được gán và không gian bộ nhớ mới được tạo ra.
Phép gán với kiểu đối tượng
var obj = { name: "Vivek", surname: "Bisht" };
var obj2 = obj;
Trong ví dụ trên, phép gán truyền trực tiếp vị trí của biến obj
đến biến obj2
. Nói cách khác, tham chiếu của biến obj
được chuyển cho biến obj2
.
var obj = #8711; // obj pointing to address of { name: "Vivek", surname: "Bisht" }
var obj2 = obj;
var obj2 = #8711; // obj2 pointing to the same address
// changing the value of obj1
obj1.name = "Akki";
console.log(obj2);
// Returns {name:"Akki", surname:"Bisht"} since both the variables are pointing to the same address.
Từ ví dụ trên, ta có thể thấy rằng trong khi truyền các kiểu dữ liệu đối tượng, phép gán trực tiếp truyền địa chỉ (tham chiếu).
Do đó, các kiểu dữ liệu đối tượng luôn được truyền bằng tham chiếu.
C54:
Bạn nghĩ gì về AMD (Asynchronous Module Definition) và CommonJS?
Xem đáp án
Mặc dù JavaScript không hỗ trợ các module, nhưng cộng đồng các developer đã cố gắng tìm ra cách để làm việc này. Sau một thời gian phát triển, thì hiện nay có một số phương thức module hóa như sau:
- The Module pattern
- CommonJS
- Asynchronous Module Definition (AMD)
Trong những phương án trên, module pattern không yêu cầu bất cứ một công cụ hay thư viện nào, nó có thể hoạt động ở mọi môi trường JavaScript. CommonJS hướng tới mục tiêu là JavaScript chạy ở server-side. AMD chính là phương thức rất phổ biến với những ứng dụng Web, và nó cũng là phương thức mà RequireJS sử dụng.
Cả hai đều là cách để triển khai một module system, vốn không có trong JavaScript cho đến khi ES2015 ra đời. CommonJS là đồng bộ trong khi AMD là bất đồng bộ. CommonJS được thiết kế với sự phát triển phía máy chủ trong khi AMD, hỗ trợ tính năng tải các modules một cách bất đồng bộ, dành cho trình duyệt nhiều hơn.
Cú pháp AMD khá dài dòng và CommonJS gần với kiểu bạn viết câu lệnh import trong các ngôn ngữ khác. Hầu hết thời gian, tôi thấy AMD không cần thiết, bởi vì nếu bạn đưa tất cả JavaScript của mình vào một tệp gói được nối, bạn sẽ không được hưởng lợi từ các thuộc tính tải bất đồng bộ. Ngoài ra, cú pháp CommonJS gần với phong cách viết module của Node hơn và có ít chi phí chuyển đổi ngữ cảnh hơn khi chuyển đổi giữa phát triển JavaScript phía máy khách và phía máy chủ.
Thật vui vì với các modules ES2015, có hỗ trợ cả tải đồng bộ và bất đồng bộ, cuối cùng chúng ta có thể chỉ cần bám vào một cách tiếp cận. Mặc dù nó chưa được triển khai hoàn toàn trong các trình duyệt và trong Node, nhưng chúng ta luôn có thể sử dụng các bộ chuyển mã để chuyển đổi mã của mình.
C55:
Kết quả đoạn code sau là gì?
const set = new Set([1, 1, 2, 3, 4]);
console.log(set);
- A:
[1, 1, 2, 3, 4]
- B:
[1, 2, 3, 4]
- C:
{1, 1, 2, 3, 4}
- D:
{1, 2, 3, 4}
Xem đáp án
Đáp án: D
Set
là một tập hơp các giá trị không trùng nhau.
Chúng ta đưa đầu vào là một mảng [1, 1, 2, 3, 4]
với giá trị 1
bị trùng. Giá trị trùng đó sẽ bị loại bỏ. Kết quả là {1, 2, 3, 4}
.
C56:
Anonymous Function thường dùng cho trường hợp nào?
Xem đáp án
Anonymous Function có thể được sử dụng trong IIFE để đóng gói một số mã trong phạm vi cục bộ để các biến được khai báo trong đó không bị rò rỉ ra phạm vi toàn cục.
(function () {
// Some code here.
})();
Anonymous Function như một callback được sử dụng một lần và không cần sử dụng ở bất kỳ nơi khác. Code sẽ có vẻ tự kiểm soát và dễ đọc hơn khi các trình xử lý được xác định ngay bên trong code gọi chúng, thay vì phải tìm kiếm ở nơi khác để tìm phần thân hàm.
setTimeout(function () {
console.log("Hello world!");
}, 1000);
Đối số đến cấu trúc functional programming hoặc Lodash (tương tự như callback).
const arr = [1, 2, 3];
const double = arr.map(function (el) {
return el * 2;
});
console.log(double); // [2, 4, 6]
C57:
Ưu điểm và nhược điểm của việc sử dụng use strict
là gì?
Xem đáp án
Answer `use strict` là một câu lệnh được sử dụng để bật strict mode cho toàn bộ tập lệnh hoặc các chức năng riêng lẻ. Strict mode là một cách để chọn tham gia một biến thể bị hạn chế (hạn chế một số tính năng) của JavaScript.
Ưu điểm:
- Làm cho nó không thể vô tình tạo ra các biến toàn cục.
- Đẩy ra các
exception
nếu xóa các thuộc tính không thể bị xóa (trước đó việc xóa đó chỉ đơn giản là không có tác dụng và cũng không có thông báo gì). - Bắt buộc tên tham số hàm là duy nhất.
this
làundefined
trong global context.- Nó bắt một số blooper mã hóa phổ biến, thảy ra các exceptions.
- Nó vô hiệu hóa các tính năng khó hiểu hoặc không hiệu quả.
Nhược điểm:
- Nhiều tính năng bị thiếu mà một số nhà phát triển có thể đã quen dùng.
- Không còn quyền truy cập vào function.caller và function.arguments.
- Việc kết hợp các tập lệnh được viết ở các strict mode khác nhau có thể gây ra sự cố.
Nhìn chung, tôi nghĩ rằng “use strict” có lợi ích nhiều hơn bất lợi.
C58:
Lợi ích của việc sử dụng spread trong ES6 so với rest như thế nào?
Xem đáp án
Cú pháp spread của ES6 rất hữu ích khi viết mã theo mô hình functional vì chúng ta có thể dễ dàng tạo bản sao của mảng hoặc đối tượng mà không cần dùng đến Object.create
, slice
hoặc một hàm thư viện. Tính năng này thường được sử dụng trong các dự án Redux và rx.js.
function putDookieInAnyArray(arr) {
return [...arr, "dookie"];
}
const result = putDookieInAnyArray(["I", "really", "don't", "like"]);
// ["I", "really", "don't", "like", "dookie"]
const person = {
name: "Todd",
age: 29,
};
const copyOfTodd = { ...person };
Cú pháp rest của ES6 cung cấp một cách viết tắt để có thể truyền một số lượng đối số tùy ý vào một hàm. Nó giống như một phép nghịch đảo của cú pháp spread, lấy dữ liệu và nhồi nó vào một mảng chứ không phải giải nén một mảng dữ liệu, và nó hoạt động trong các đối số hàm như trong các array destructuring và object destructuring.
C59:
Giá trị trả về là gì?
const firstPromise = new Promise((res, rej) => {
setTimeout(res, 500, "one");
});
const secondPromise = new Promise((res, rej) => {
setTimeout(res, 100, "two");
});
Promise.race([firstPromise, secondPromise]).then(res => console.log(res));
- A:
"one"
- B:
"two"
- C:
"two" "one"
- D:
"one" "two"
Xem đáp án
Đáp án: B
Khi chúng ta đưa các promise vào trong một hàm Promise.race
, nó sẽ chỉ resolves hay rejects promise đầu tiên được resolves/rejects. Với hàm setTimeout
, chúng ta đưa vào một khoảng thời gian: 500 mili giây cho promise đầu tiên (firstPromise
), và 100 mili giây cho promise thứ hai (secondPromise
). Nó có nghĩa là secondPromise
sẽ hoàn thành trước và trả về giá trị 'two'
. res
khi này sẽ nhận giá trị 'two'
và được in ra console.
C60:
Kết quả đoạn code sau là gì?
function getInfo(member, year) {
member.name = "Lydia";
year = "1998";
}
const person = { name: "Sarah" };
const birthYear = "1997";
getInfo(person, birthYear);
console.log(person, birthYear);
- A:
{ name: "Lydia" }, "1997"
- B:
{ name: "Sarah" }, "1998"
- C:
{ name: "Lydia" }, "1998"
- D:
{ name: "Sarah" }, "1997"
Xem đáp án
Đáp án: A
Đối số
sẽ được đưa vào hàm dạng tham trị, trừ phi nó là object, khi đó nó sẽ được đưa vào hàm dạng tham chiếu. birthYear
là dạng giá trị, vì nó là string chứ không phải object. Khi chúng ta đưa vào dạng giá trị, một bản sao của giá trị đó sẽ được tạo ra (xem thêm câu 46).
birthYear
trỏ đến giá trị là "1997"
. Đối số year
cũng sẽ rỏ đến giá trị "1997"
, nhưng giá trị này chỉ là một bản sao của giá trị mà birthYear
trỏ tới mà thôi, hai giá trị đó hoàn toàn khác nhau. Do đó khi ta thay đổi giá trị year
bằng "1998"
, chúng ta chỉ thay đổi giá trị của year
mà thôi. birthYear
sẽ vẫn giữ giá trị là "1997"
.
person
là một object. Biến member
có một tham chiếu tới cùng object mà person
trỏ tới. Khi chúng ta thay đổi một thuộc tính của object mà member
trỏ tới, giá trị của person
cũng sẽ tự động thay đổi theo, vì chúng có chung tham chiếu. name
của person
khi này sẽ có giá trị mới là "Lydia"
.
C61:
Sự khác biệt giữa các Host objects và Native objects là gì?
Xem đáp án
-
Native objects là một phần của ngôn ngữ JavaScript được xác định bởi đặc tả ECMAScript, chẳng hạn như
String
,Math
,RegExp
,Object
,Function
, … -
Host objects được cung cấp bởi môi trường runtime (trình duyệt hoặc Node), chẳng hạn như window,
XMLHTTPRequest
,…
C62:
Kết quả đoạn code sau là gì?
// counter.js
let counter = 10;
export default counter;
// index.js
import myCounter from "./counter";
myCounter += 1;
console.log(myCounter);
- A:
10
- B:
11
- C:
Error
- D:
NaN
Xem đáp án
Đáp án: C
Một module khi được import sẽ là read-only: chúng ta sẽ không thể chỉnh sửa module đó, chỉ có bản thân module đó có thể chỉnh sửa giá trị của nó mà thôi.
Khi ta thay đổi giá trị cuả myCounter
, nó sẽ throw ra một lỗi: myCounter
là read-only và không thể thay đổi.
C63:
Làm thế nào để so sánh hai object trong JavaScript?
Xem đáp án
Các giá trị non-primitive, như các object (bao gồm cả hàm và mảng) được lưu dưới dạng tham chiếu, vì vậy cả hai phép so sánh ==
và ===
sẽ chỉ kiểm tra xem các tham chiếu có khớp nhau hay không, chứ không phải kiểm tra bất kỳ điều gì về các giá trị cơ bản.
Ví dụ: theo mặc định, mảng được ép thành chuỗi bằng cách chỉ cần nối tất cả các giá trị bằng dấu phẩy (,) ở giữa. Vì vậy, hai mảng có cùng nội dung sẽ không là true
khi so sánh bằng ==
var a = [1, 2, 3];
var b = [1, 2, 3];
var c = "1,2,3";
a == c; // true
b == c; // true
a == b; // false
Để deep-comparison các object, hãy sử dụng các thư viện bên ngoài như deep-equal hoặc triển khai một thuật toán của riêng bạn. Dưới đây là một ví dụ
function deepObjectEqual(object1, object2) {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
const val1 = object1[key];
const val2 = object2[key];
const areObjects = isObject(val1) && isObject(val2);
if (
(areObjects && !deepObjectEqual(val1, val2)) ||
(!areObjects && val1 !== val2)
) {
return false;
}
}
return true;
}
function isObject(object) {
return object != null && typeof object === "object";
}
Như bạn thấy bên trên, đoạn areObjects && !deepObjectEqual(val1, val2)
ta thực hiện đệ quy để có thể lặp hết vào trong các thuộc tính của object. Cùng xem ví dụ thử nhé:
const object1 = {
value: 1,
name: "Test",
address: {
city: "Ha Noi",
},
};
const object2 = {
value: 1,
name: "Test",
address: {
city: "Ha Noi",
},
};
deepObjectEqual(object1, object2); // => true
So sánh sâu sẽ giúp ta xác định một cách chính xác 2 object có thực sự bằng nhau về mặt nội dung hay không.
C64:
So sánh sự khác nhau của forEach()
và map()
?
Xem đáp án
Để hiểu sự khác biệt giữa hai hàm, chúng ta hãy xem mỗi hàm làm gì.
forEach()
trong Javascript
- Lặp qua các phần tử trong một mảng.
- Thực hiện một lệnh gọi lại cho mỗi phần tử.
- Không trả về giá trị.
const a = [1, 2, 3];
const doubled = a.forEach((num, index) => {
// Do something with num and/or index.
});
// doubled = undefined
map()
trong Javascript
- Lặp qua các phần tử trong một mảng.
- Ánh xạ mỗi phần tử thành một phần tử mới bằng cách gọi hàm trên mỗi phần tử, kết quả là tạo ra một mảng mới.
const a = [1, 2, 3];
const doubled = a.map(num => {
return num * 2;
});
// doubled = [2, 4, 6]
Sự khác biệt chính giữa forEach()
và map()
là map trả về một mảng mới. Nếu bạn cần kết quả, nhưng không muốn thay đổi mảng ban đầu, map là lựa chọn rõ ràng. Nếu bạn chỉ cần lặp lại một mảng, forEach()
là một lựa chọn tốt.
C65:
Kết quả đoạn code sau là gì?
const settings = {
username: "lydiahallie",
level: 19,
health: 90,
};
const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);
- A:
"{"level":19, "health":90}"
- B:
"{"username": "lydiahallie"}"
- C:
"["level", "health"]"
- D:
"{"username": "lydiahallie", "level":19, "health":90}"
Xem đáp án
Đáp án: A
Đối số thứ hai của JSON.stringify
là replacer. Replacer Có thể là một hàm hoặc một mảng, nó sẽ quy định xem giá trị nào sẽ được chuỗi hóa ra sao.
Nếu replacer là một mảng, chỉ có các thuộc tính có tên trong mảng được convert thành chuỗi JSON. Trong trường hợp này, chỉ có các thuộc tính "level"
và "health"
được đưa vào, "username"
bị loại bỏ. data
giờ sẽ là "{"level":19, "health":90}"
.
Nếu replacer là function, hàm này sẽ được gọi trên từng thuộc tính của object được chuỗi hóa. Giá trị trả về sẽ là giá trị được đưa vào chuỗi JSON. Nếu trả về undefined
, thuộc tính này sẽ bị loại bỏ khỏi chuỗi.
C66:
Kết quả đoạn code sau là gì?
const one = false || {} || null;
const two = null || false || "";
const three = [] || 0 || true;
console.log(one, two, three);
- A:
false
null
[]
- B:
null
""
true
- C:
{}
""
[]
- D:
null
null
true
Xem đáp án
Đáp án: C
Với phép toán ||
, ta sẽ trả về giá trị truethy đầu tiên. Nếu tất cả đều là falsy, giá trị cuối cùng sẽ được trả về.
(false || {} || null)
: object rỗng {}
là một giá trị truthy. Nó là giá trị truethy đầu tiên và duy nhất nên sẽ được trả về. Do đó one
sẽ là {}
.
(null || false || "")
: Tất cả toán hạng đều là falsy. Có nghĩa là toán hạng cuối cùng ""
sẽ được trả về. Do đó two
sẽ là ""
.
([] || 0 || "")
: mảng rỗng []
là một giá trị truthy. Nó là giá trị truthy đầu tiên nên sẽ được trả về. Do đó three
sẽ là []
.
C67:
Bạn biết gì về sự kiện load trong Javascript?
Xem đáp án
Sự kiện onload
có ý nghĩa rằng khi trình duyệt đã load xong mọi thứ (image, js, css) thì những đoạn code nằm bên trong đó mới được chạy. Có một lưu ý rằng nếu bạn sử dụng onload cho một thẻ HTML nào đó thì nó sẽ có tác dụng với thẻ HTML đó thôi nhưng nếu bạn dùng cho window thì nó sẽ có tác dụng cho toàn trang.
Hay nói cách khác những đoạn code nằm bên trong sự kiện onload sẽ được chạy sau cùng khi mà trình JS đã được biên dịch 1 lần. Chính vì vậy nếu trong sự kiện onload bạn gọi tới một hàm nào đó thì dù bạn đặt hàm đó phía trên hay phía dưới thì đều đúng, lý do là trình biên dịch đã thực hiện dịch 1 lần rồi nên hàm đó đã tồn tại.
Ví dụ: trong đoạn code dưới đây ta gọi hàm do_validate()
bên trong sự kiện window.onload
nên mặc dù hàm validate được đặt phía dưới nhưng vẫn đúng.
window.onload = function () {
do_validate();
};
function do_validate() {
alert(1);
}
Nếu vẫn chưa tin thì bạn làm ví dụ sau đây, trong ví dụ này ta thực hiện alert
lên thứ tự của quá trình biên dịch
alert(1);
window.onload = function () {
alert(3);
};
alert(2);
Mặc dù đoạn code alert(3)
nằm ở vị trí thứ hai nhưng nó lại xuất hiện cuối cùng.
C68:
Kết quả đoạn code sau là gì?
const box = { x: 10, y: 20 };
Object.freeze(box);
const shape = box;
shape.x = 100;
console.log(shape);
- A:
{ x: 100, y: 20 }
- B:
{ x: 10, y: 20 }
- C:
{ x: 100 }
- D:
ReferenceError
Xem đáp án
Đáp án: B
Object.freeze
khiến cho chúng ta không thể thêm vào, xóa đi hay thay đổi bất kì thuộc tính nào của object (trừ phi giá trị của thuộc tính lại chính là một object khác).
Khi chúng ta tạo ra biến shape
và set cho nó giá trị bằng với một object đã được đóng băng là box
, thì shape
cũng sẽ trỏ tới một object đã được đóng băng. Ta có thể check một object có đang bị đóng băng hay không bằng Object.isFrozen
. Trong trường hợp này, Object.isFrozen(shape)
trả về true, vì shape
đang trỏ tới một object bị đóng băng.
Do đó, cộng với việc x
không phải là object, ta sẽ không thể thay đổi giá trị của x
. x
sẽ vẫn là 10
, và { x: 10, y: 20 }
được ghi ra.
C69:
Kết quả đoạn code sau là gì?
function* generator(i) {
yield i;
yield i * 2;
}
const gen = generator(10);
console.log(gen.next().value);
console.log(gen.next().value);
- A:
[0, 10], [10, 20]
- B:
20, 20
- C:
10, 20
- D:
0, 10 and 10, 20
Xem đáp án
Đáp án: C
Một hàm bình thường không thể bị dừng giữa chừng khi được gọi. Tuy nhiên một generator thì khác, nó có thể “dừng lại” được, và sau đó nó sẽ tiếp tục từ vị trí nó dừng lại. Mỗi khi một generator gặp một từ khóa yield
, nó sẽ sinh ra giá trị ngay phía sau nó. Chú ý là generator không trả về giá trị, nó sinh ra giá trị.
Đầu tiên, chúng ta khởi tạo generator với giá trị i
là 10
. Generator được gọi bằng cách sử dụng phương thức next()
. Khi lần đầu gọi thì i
vẫn là 10
. Khi nó bắt gặp từ khóa yield
: nó sẽ sinh ra giá trị i
. Generator sẽ được “tạm dừng” tại đây, và ghi ra giá trị 10
.
Sau đó chung ta tiếp tục gọi generator bằng cách sử dụng tiếp phương thức next()
. Nó sẽ bắt đầu từ vị trí nó tạm dừng lúc trước, khi i
vẫn đang là 10
. Và khi nó bắt gặp từ khóa yield
, nó sẽ sinh ra giá trị i * 2
. i
là 10
, nên nó sẽ sinh ra 10 * 2
, tức 20
. Vậy kết quả cuối cùng là 10, 20
.
C70:
Kết quả đoạn code sau là gì?
const list = [1 + 2, 1 * 2, 1 / 2];
console.log(list);
- A:
["1 + 2", "1 * 2", "1 / 2"]
- B:
["12", 2, 0.5]
- C:
[3, 2, 0.5]
- D:
[1, 1, 1]
Xem đáp án
Đáp án: C
Mảng có thể nhận bất cứ giá trị nào. Số, chuỗi, objects, mảng khác, null, boolean, undefined, và nhiều dạng biểu thức nữa như ngày tháng, hàm, và các tính toán.
Giá trị của phần tử chính là giá trị trả về. 1 + 2
trả về 3
, 1 * 2
trả về 2
, và 1 / 2
trả về 0.5
.
C71:
Giải thích về bubbling event
và cách để ngăn chặn nó?
Xem đáp án
Bubbling event là khái niệm trong đó một sự kiện kích hoạt ở phần tử sâu nhất và kích hoạt trên các phần tử mẹ theo thứ tự lồng vào nhau. Do đó, khi click vào một phần tử con, sự kiện click của phần tử cha cũng được kích hoạt.
Một cách để ngăn sự kiện nổi bọt là sử dụng event.stopPropagation()
hoặc event.cancelBubble
trên IE < 9.
C72:
Kết quả đoạn code sau là gì?
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
Xem đáp án
Đáp án: B
Điều kiện trong mệnh đề if
kiểm tra xem giá trị của !typeof randomValue
bằng với "string"
hay không. Phép toán !
chuyển giá trị đó thành giá trị boolean. Nếu giá trị là truthy, giá trị trả về sẽ là false
, nếu giá trị là falsy, 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ị truthy "number"
, nghĩa là giá trị của !typeof randomValue
là một giá trị boolean false
.
!typeof randomValue === "string"
luôn trả về false, vì ta thực sự đang kiểm tra false === "string"
. Vì điều kiện đã trả về false
, code của mệnh đề else
sẽ chạy và Yay it's a string!
được in ra.
C73:
Kết quả đoạn code sau là gì?
class Dog {
constructor(name) {
this.name = name;
}
}
Dog.prototype.bark = function () {
console.log(`Woof I am ${this.name}`);
};
const pet = new Dog("Mara");
pet.bark();
delete Dog.prototype.bark;
pet.bark();
- A:
"Woof I am Mara"
,TypeError
- B:
"Woof I am Mara"
,"Woof I am Mara"
- C:
"Woof I am Mara"
,undefined
- D:
TypeError
,TypeError
Xem đáp án
Đáp án: A
Chúng ta có thể xóa các thuộc tính khỏe object bằng từ khóa delete
, kể cả với prototype. Khi chúng ta xóa một thuộc tính trên prototype, nó sẽ bị vô hiệu hóa hoàn toàn trong chuỗi prototype. Trong trường hợp này, hàm bark
sẽ bị vô hiệu hóa ngay sau khi chúng ta thực hiện hàm xóa delete Dog.prototype.bark
, tất nhiên ta vẫn có thể truy cập vào nó nhưng giá trị sẽ là undefined
.
Khi chúng ta chạy một thứ không phải là hàm
, nó sẽ bắn ra một TypeError
. Trong trường hợp này là TypeError: pet.bark is not a function
, vì bản thân thuộc tính pet.bark
là undefined
.
C74:
Làm sao để deep-freeze
một đối tượng trong JavaScript?
Xem đáp án
Nếu bạn muốn đảm bảo đối tượng được deep-freeze (đóng băng sâu), bạn phải tạo một hàm đệ quy để freeze
từng thuộc tính thuộc loại object:
without deep-freeze
:
let person = {
name: "Leonardo",
profession: {
name: "developer",
},
};
Object.freeze(person); // make object immutable
person.profession.name = "doctor";
console.log(person); //output { name: 'Leonardo', profession: { name: 'doctor' } }
with deep-freeze:
function deepFreeze(object) {
let propNames = Object.getOwnPropertyNames(object);
for (let name of propNames) {
let value = object[name];
object[name] =
value && typeof value === "object" ? deepFreeze(value) : value;
}
return Object.freeze(object);
}
let person = {
name: "Leonardo",
profession: {
name: "developer",
},
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property 'name' of object
C75:
Con trỏ This
trong javascript được dùng để làm gì?
Xem đáp án
Trong javascript, chúng ta dùng từ khóa this để đại diện cho một đối tượng (Object). Đối tượng đó là chủ thế của ngữ cảnh, hoặc là chủ thế của code đang được chạy.
Ví dụ:
var person = {
firstName: "kungfutech.edu.vn",
lastName: "Vien Huynh",
showName: function () {
console.log(this.firstName + " " + this.lastName);
},
};
//Ở đây this sẽ là object person
person.showName(); //kungfutech.edu.vn Vien Huynh
Một trường hợp khác, khi ta khai báo biến global và hàm global, toàn bộ các biến và hàm đó sẽ nằm trong một object có tên là window
. Lúc này, khi ta gọi hàm showName
, chính object window là object gọi hàm đó, this trỏ tới chính object window.
var firstName = "kungfutech.edu.vn",
lastName = "Vien Huynh";
// 2 biến này nằm trong object window
function showName() {
console.log(this.firstName + " " + this.lastName);
}
window.showName(); //kungfutech.edu.vn Vien Huynh - this trỏ tới object window
showName(); //kungfutech.edu.vn Vien Huynh - Object gọi hàm showName vẫn là object window
C76:
Sự khác biệt giữa shim
và polyfill
trong Javascript là gì?
Xem đáp án
-
Một Shim là bất kỳ đoạn mã nào thực hiện việc chặn một lời gọi API và cung cấp một lớp trừu tượng. Nó không bị hạn chế đối với một ứng dụng web hay HTML5 / CSS3.
-
Một Polyfill là một loại Shim trang bị thêm cho các trình duyệt cũ với các tính năng HTML5 / CSS3 hiện đại, thường sử dụng Javascript hoặc Flash.
-
Một Shim là một thư viện mang một API mới đến một môi trường cũ, chỉ sử dụng các phương tiện của môi trường đó. Do đó, polyfill là một shim cho một API trình duyệt.
C77:
Nêu một số trường hợp không nên sử dụng Arrow Functions trong ES6?
Xem đáp án
Các arrow functions KHÔNG nên được sử dụng:
-
Khi chúng ta muốn
function hoisting
- vì các Arrow Functions là ẩn danh. -
Khi chúng ta muốn sử dụng
this / arguments
trong một hàm - vì các Arrow Functions không cóthis / arguments
của riêng chúng, chúng phụ thuộc vào ngữ cảnh bên ngoài của chúng. -
Khi chúng ta muốn sử dụng hàm được đặt tên (named function) - vì các Arrow Functions là ẩn danh.
-
Khi chúng ta muốn sử dụng hàm như một phương thức khởi tạo - vì các Arrow Functions không có chức năng này.
-
Khi chúng ta muốn thêm một thuộc tính là một hàm vào trong object literal và sử dụng đối tượng trong đó - vì chúng ta không thể truy cập vào điều này (mà phải là chính đối tượng).
C78:
Kết quả đoạn code sau là gì?
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const member = new Person("Lydia", "Hallie");
Person.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
};
console.log(member.getFullName());
- A:
TypeError
- B:
SyntaxError
- C:
Lydia Hallie
- D:
undefined
undefined
Xem đáp án
Đáp án: A
Chúng ta không thể add thêm một thuộc tính cho một constructor giống như một object thông thường. Nếu bạn muốn add thêm thuộc tính nào đó cho tất cả các object một lần, bạn phải dùng prototype
. Trong trường hợp này cũng vậy.
Person.prototype.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
};
khi này member.getFullName()
sẽ hoạt động. Tại sao nên làm vậy? Hãy thử thêm chúng trực tiếp vào constructor xem sao. Không phải mọi instance Person
đều cần phương thức này. Nó sẽ dẫn tới việc lãng phí rất nhiều bộ nhớ, khi chúng đều phải lưu trữ thuộc tính này cho mỗi instance. Thay vì thế, nếu ta chỉ thêm chúng vào prototype
, ta sẽ chỉ tốn bộ nhớ một lần mà thôi, và mọi object khác đều có thể truy cập đến nó!
C79:
Từ khóa new
trong JavaScript là gì?
Xem đáp án
- Nó tạo ra một đối tượng mới. Loại đối tượng này chỉ đơn giản là
object
. - Nó đặt thuộc tính
internal
,inaccessible
,prototype
(tức là proto) của đối tượng mới này trở thành đối tượng prototype, external, accessible của hàm khởi tạo (constructor function), mọi function object đều tự động có một thuộc tính prototype. - Nó làm cho biến
this
trỏ tới đối tượng mới được tạo. - Nó thực thi hàm khởi tạo, sử dụng đối tượng mới được tạo bất cứ khi nào điều này được đề cập.
- Nó trả về đối tượng mới được tạo, trừ khi hàm khởi tạo trả về một tham chiếu đối tượng non-null. Trong trường hợp này, tham chiếu đối tượng đó được trả về thay thế.
function New(func) {
var res = {};
if (func.prototype !== null) {
res.__proto__ = func.prototype;
}
var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
return ret;
}
return res;
}
C80:
Sự khác biệt giữa null
, undefined
hoặc undeclared
là gì?
Xem đáp án
Các biến undeclared
(chưa được khai báo) được tạo khi bạn gán một giá trị cho một identifier chưa được tạo trước đó bằng cách sử dụng var
, let
hoặc const
. Các biến undeclared sẽ được xác định trên toàn bộ, bên ngoài của phạm vi hiện tại (current scope). Trong strict mode, một ReferenceError
sẽ được ném ra khi bạn cố gắng gán tới một biến undeclared
. Tránh chúng bằng mọi giá! Để kiểm tra chúng, hãy bọc chúng trong một khối try / catch
.
function foo() {
x = 1; // Throws a ReferenceError in strict mode
}
foo();
console.log(x); // 1
Một biến là undefined
là một biến đã được khai báo, nhưng không được gán giá trị. Nếu một hàm không trả về bất kỳ giá trị nào được gán cho một biến, thì biến đó cũng có giá trị undefined. Để kiểm tra nó, hãy so sánh bằng cách sử dụng toán tử Strict Equality (===)
hoặc typeof
. Lưu ý rằng bạn không nên sử dụng toán tử Abstract Equality (==)
để kiểm tra, vì nó cũng sẽ trả về true
nếu giá trị là null
.
var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === "undefined"); // true
console.log(foo == null); // true. Wrong, don't use this to check!
function bar() {}
var baz = bar();
console.log(baz); // undefined
Một biến là null
nếu được gán cho một giá trị null
. Nó đại diện cho không giá trị (no-value) và khác với undefined
là nó đã được gán một cách rõ ràng. Để kiểm tra null
, chỉ cần so sánh bằng cách sử dụng toán tử Strict Equality(===)
. Lưu ý rằng giống như ở trên, bạn không nên sử dụng toán tử Abstract equality(==)
để kiểm tra, vì nó cũng sẽ trả về true nếu giá trị là undefined
.
var foo = null;
console.log(foo === null); // true
console.log(typeof foo === "object"); // true
console.log(foo == undefined); // true. Wrong, don't use this to check!
Theo thói quen cá nhân, tôi không bao giờ để các biến của mình là undeclared
hoặc unassigned
. Tôi sẽ gán null
cho chúng một cách rõ ràng sau khi khai báo nếu tôi chưa có ý định sử dụng nó. Nếu bạn sử dụng linter (ví dụ ESLint) trong quy trình làm việc của mình, nó sẽ thường kiểm tra rằng bạn đang không tham chiếu tới các biến undeclared.
C81:
Kết quả đoạn code sau là gì?
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
Xem đáp án
Đáp án: A
Hàm updateEmail
là một cú pháp arrow function và nó không gắn với user
object. Điều này cho thấy từ khoá this
không trỏ tới user
object mà trỏ tới global scope. Giá trị của email
trong user
object không thay đổi. Khi ta in ra giá trị của user.email
, nó trả về giá trị ban đầu của my@email.com
.
C82:
IIFEs (Immediately Invoked Function Expressions) là gì?
Xem đáp án
Đó là một Immediately-Invoked Function Expression, gọi tắt là IIFE. Nó thực thi ngay sau khi được tạo:
(function IIFE() {
console.log("Hello!");
})();
// "Hello!"
IIFEs thường được sử dụng khi cố gắng tránh làm rối global namespace, bởi vì tất cả các biến được sử dụng bên trong IIFE (giống như trong bất kỳ hàm thông thường nào khác) đều không thể sử dụng bên ngoài phạm vi của nó.
C83:
Kết quả đoạn code sau là gì?
const set = new Set();
set.add(1);
set.add("Lydia");
set.add({ name: "Lydia" });
for (let item of set) {
console.log(item + 2);
}
- A:
3
,NaN
,NaN
- B:
3
,7
,NaN
- C:
3
,Lydia2
,[object Object]2
- D:
"12"
,Lydia2
,[object Object]2
Xem đáp án
Đáp án: C
Phép toán +
không chỉ dùng để cộng các số, mà nó còn dùng để nối chuỗi nữa. Mỗi khi Javascript engine gặp một giá trị trong phép toán không phải dạng số, nó sẽ chuyển các số trong phép toán đó sang dạng chuỗi.
Phép toán đầu tiên item là một số 1
, nên 1 + 2
trả về 3.
Ở phép toán thứ hai, item là một chuỗi "Lydia"
. trong khi đó 2
là một số, nên 2
sẽ bị chuyển sang dạng chuỗi, sau khi nối vào ta có chuỗi "Lydia2"
.
Ở phép toán thứ ba, { name: "Lydia" }
là một object. Tuy nhiên dù có là object hay gì đi nữa thì nó cũng sẽ bị chuyển sang dạng chuỗi. Đối với object thì khi chuyển sang dạng chuỗi nó sẽ trở thành "[object Object]"
. "[object Object]"
nối với "2"
trở thành "[object Object]2"
.
C84:
Kết quả đoạn code sau là gì?
let number = 0;
console.log(number++);
console.log(++number);
console.log(number);
- A:
1
1
2
- B:
1
2
2
- C:
0
2
2
- D:
0
1
2
Xem đáp án
Đáp án: C
Khi phép toán ++
nằm ở đằng sau (postfix):
- Trả về giá trị (trả về
0
) - Tăng giá trị lên (number giờ là
1
)
Khi phép toán ++
nằm ở đằng trước (prefix):
- Tăng giá trị lên (number giờ là
2
) - Trả về giá trị (trả về
2
)
Vậy kết quả là 0 2 2
.
C85:
Giải thích về Scope và Scope Chain trong Javascript?
Xem đáp án
Scope trong JS, xác định khả năng truy cập của các biến, hàm ở các phần khác nhau trong một đoạn code.
Nói chung, Scope cho biết phạm vi mà biến và hàm của ta có thể hay không thể truy cập. Có 3 loại scope trong JS:
- Global Scope
- Local hay Function Scope
- Block Scope
Global Scope
Các biến hoặc hàm được khai báo trong namespace
global đều có global scope, có nghĩa là tất cả các biến và hàm có global scope có thể được truy cập từ bất kỳ đâu bên trong code.
var globalVariable = "Hello world";
function sendMessage() {
return globalVariable; // can access globalVariable since it's written in global space
}
function sendMessage2() {
return sendMessage(); // Can access sendMessage function since it's written in global space
}
sendMessage2(); // Returns “Hello world”
Function Scope
Bất kỳ biến hoặc hàm nào được khai báo bên trong một hàm đều có function scope, có nghĩa là tất cả các biến và hàm được khai báo bên trong một hàm, có thể được truy cập từ bất cứ đâu bên trong hàm chứ không phải bên ngoài nó.
function awesomeFunction() {
var a = 2;
var multiplyBy2 = function () {
console.log(a * 2); // Can access variable "a" since a and multiplyBy2 both are written inside the same function
};
}
console.log(a); // Throws reference error since a is written in local scope and cannot be accessed outside
multiplyBy2(); // Throws reference error since multiplyBy2 is written in local scope
Block Scope
Block Scope liên quan đến các biến được khai báo bằng let
và const
. Các biến được khai báo với var không có block scope.
Block scope cho chúng ta biết rằng bất kỳ biến nào được khai báo bên trong một khối {}
, chỉ có thể được truy cập bên trong khối đó và không thể được truy cập bên ngoài khối đó.
{
let x = 45;
}
console.log(x); // Gives reference error since x cannot be accessed outside of the block
for (let i = 0; i < 2; i++) {
// do something
}
console.log(i); // Gives reference error since i cannot be accessed outside of the for loop block
Scope Chain
JavaScript Engine cũng sử dụng scope để tìm biến. Ví dụ:
var y = 24;
function favFunction() {
var x = 667;
var anotherFavFunction = function () {
console.log(x); // Does not find x inside anotherFavFunction, so looks for variable inside favFunction, outputs 667
};
var yetAnotherFavFunction = function () {
console.log(y); // Does not find y inside yetAnotherFavFunction, so looks for variable inside favFunction and does not find it, so looks for variable in global scope, finds it and outputs 24
};
anotherFavFunction();
yetAnotherFavFunction();
}
favFunction();
Như bạn có thể thấy trong đoạn code trên, nếu javascript engine không tìm thấy biến trong function scope, nó sẽ cố gắng kiểm tra biến ở phạm vi bên ngoài. Nếu biến không tồn tại trong phạm vi bên ngoài, nó sẽ cố gắng tìm biến trong global scope.
Nếu biến cũng không được tìm thấy trong không gian chung, thì lỗi tham chiếu sẽ được đưa ra.
C86:
Kết quả đoạn code sau là gì?
const colorConfig = {
red: true,
blue: false,
green: true,
black: true,
yellow: false,
};
const colors = ["pink", "red", "blue"];
console.log(colorConfig.colors[1]);
- A:
true
- B:
false
- C:
undefined
- D:
TypeError
Xem đáp án
Đáp án: D
Trong Javascript ta có hai cách để truy cập thuộc tính của một object: sử dụng ngoặc vuông []
, hoặc sử dụng chấm .
. Trong trương hợp này chúng ta sử dụng chấm (colorConfig.colors
) thay cho ngoặc vuông (colorConfig["colors"]
).
Với cách sử dụng chấm, Javascript sẽ tìm kiếm một thuộc tính có tên chính xác như tên ta đưa vào. Trong trường hợp này nó là thuộc tính colors
trong object colorConfig
Tuy nhiên trong object này không có thuộc tính nào tên là colors
, nên nó sẽ trả về undefined
. Sau đó chúng ta cố truy cậ vào thuộc tính 1 của nó bằng cách gọi [1]
. Chúng ta không thể làm như vậy trên giá trị undefined
, nên nó sẽ trả về TypeError
: Cannot read property '1' of undefined
.
Javascript thông dịch theo câu lệnh. Khi ta sử dụng ngoặc vuông, Nnó sẽ tìm mở ngoặc đầu tiên [
và tiếp tục cho tới khi gặp đóng ngoặc tương ứng ]
. Chỉ khi đó nó mới đánh giá câu lệnh. Nếu chúng ta sử dụng cú pháp colorConfig[colors[1]]
, nó sẽ trả về giá trị của thuộc tính red
trong object colorConfig
.
C87:
Giải thích cách hoạt động của JSONP và tại sao nó không thực sự là Ajax?
Xem đáp án
Answer JSONP
(JSON with Padding) là một phương pháp thường được sử dụng để vượt qua các chính sách cross-domain
trong trình duyệt web bởi vì việc gọi Ajax từ trang hiện tại đến một cross-origin domain
là không được cho phép.
JSONP hoạt động bằng cách đưa ra một request đến một cross-origin domain thông qua thẻ <script>
và thường với một tham số là callback query, ví dụ: https://example.com?callback=printData
. Sau đó, máy chủ sẽ bọc dữ liệu trong một hàm gọi là printData và trả lại cho máy khách.
<!-- https://mydomain.com -->
<script>
function printData(data) {
console.log(`My name is ${data.name}!`);
}
</script>
<script src="https://example.com?callback=printData"></script>
// File loaded from https://example.com?callback=printData
printData({ name: "kungfutech.edu.vn" });
Máy khách phải có hàm printData
trong phạm vi toàn cục của nó và hàm sẽ được thực thi bởi máy khách khi nhận được phản hồi từ cross-origin
domain.
JSONP có thể không an toàn và có một số tác động bảo mật. Vì JSONP thực sự là JavaScript, nó có thể làm mọi thứ khác mà JavaScript có thể làm, vì vậy bạn cần tin tưởng nhà cung cấp dữ liệu JSONP.
Ngày nay, CORS là phương pháp được khuyến nghị và JSONP được coi là một cuộc tấn công (hack).
C88:
Có thể reset một generator ES6 về state ban đầu của nó không?
Xem đáp án
Khi một generator bước sang trạng thái “completed”, nó sẽ không bao giờ rời khỏi nó và execution context liên quan của nó sẽ không bao giờ được tiếp tục.
Bất kỳ trạng thái thực thi nào được liên kết với generator cũng có thể bị loại bỏ tại thời điểm này.
C89:
Hạn chế của phương thức private
trong JavaScript là gì?
Xem đáp án
Một trong những hạn chế của phương thức private
trong JavaScript là chúng rất tốn kém bộ nhớ vì một bản sao mới của phương thức sẽ được tạo cho mỗi trường hợp.
var Employee = function (name, company, salary) {
this.name = name || ""; //Public attribute default value is null
this.company = company || ""; //Public attribute default value is null
this.salary = salary || 5000; //Public attribute default value is null
// Private method
var increaseSalary = function () {
this.salary = this.salary + 1000;
};
// Public method
this.dispalyIncreasedSalary = function () {
increaseSalary();
console.log(this.salary);
};
};
// Create Employee class object
var emp1 = new Employee("John", "Pluto", 3000);
// Create Employee class object
var emp2 = new Employee("Merry", "Pluto", 2000);
// Create Employee class object
var emp3 = new Employee("Ren", "Pluto", 2500);
Ở đây mỗi biến emp1, emp2, emp3 đều có bản sao riêng của phương thức private
increaseSalary
. Vì vậy, không nên sử dụng phương thức private
trừ khi nó cần thiết.
C90:
Kết quả đoạn code sau là gì?
let person = { name: "Lydia" };
const members = [person];
person = null;
console.log(members);
- A:
null
- B:
[null]
- C:
[{}]
- D:
[{ name: "Lydia" }]
Xem đáp án
Đáp án: D
Đầu tiên, chúng ta khai báo một biến person
là một object có thuộc tính name
.
Sau đó chúng ta khai báo một biến members
. Ta set giá trị đầu tiên của mảng là giá trị của biến person
. Khi sử dụng gán bằng, object sẽ được tham chiếu tới object mà nó được gán. Khi ta gán tham chiếu từ một biến sang biến khác, ta tạo ra một bản sao của tham chiếu đó. (nên nhớ rằng đó vẫn là 2 tham chiếu hoàn toàn khác nhau!)
Sau đó ta set giá trị của person
bằng null
.
Chúng ta chỉ đơn thuần là thay đổi giá trị của biến person
mà thôi, chứ không phải giá trị của phần tử đầu tiên ở trong mảng, vì chúng ta có một tham chiếu khác đến object đó. Phần tử đầu tiên của mảng members
vẫn giữ tham chiêu đến object gốc. Do vậy, khi chúng ta in ra mảng members
, phần tử đầu tiên sẽ vẫn in ra giá trị của objet gốc.
C91:
Kết quả đoạn code sau là gì?
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'
Xem đáp án
Đáp án: D
Hàm Promise.all
trả về những promise truyền vào song song nhau. Nếu một promise thất bại, hàm Promise.all
rejects với giá trị của promise đó. Trong trường hợp này, promise3
bị reject với giá trị "Third"
. Ta đang kiểm tra giá trị bị reject trong chuỗi hàm catch
khi goi hàm runPromises
để tìm ra lỗi trong hàm runPromises
. Chỉ có "Third"
được trả về vì promise3
reject giá trị này.
C92:
Output của đoạn code dưới đây là gì?
var x = 1;
var output = (function () {
delete x;
return x;
})();
console.log(output);
Xem đáp án
Đoạn mã trên có output là 1. Toán tử delete
được sử dụng để xóa thuộc tính khỏi object. x ở đây không phải là một object mà là biến toàn cục (global variable) có kiểu là number
.
C93:
3 giai đoạn của event propagation
là gì?
- A: Target > Capturing > Bubbling
- B: Bubbling > Target > Capturing
- C: Target > Bubbling > Capturing
- D: Capturing > Target > Bubbling
Xem đáp án
Đáp án: D
Trong capturing phase, event được truyền từ các phần tử cha cho tới phần tử target. Sau khi tới được phần tử target thì bubbling sẽ bắt đầu.
C94:
Kết quả đoạn code sau là gì?
const numbers = [1, 2, 3, 4, 5];
const [y] = numbers;
console.log(y);
- A:
[[1, 2, 3, 4, 5]]
- B:
[1, 2, 3, 4, 5]
- C:
1
- D:
[1]
Xem đáp án
Đáp án: C
Chúng ta có thể unpack các giá trị từ mảng hoặc thuộc tính từ objects bằng phương pháp destructuring
. Ví dụ:
[a, b] = [1, 2];
Giá trị của a
sẽ là 1
, b
sẽ là 2
. Thực tế, câu hỏi của chúng ta đơn giản là:
[y] = [1, 2, 3, 4, 5];
Có nghĩa là y
chính là giá trị đầu tiên trong mảng, tức số 1
. Do đó khi ta in ra y
thì sẽ là1
.
C95:
Kết quả đoạn code sau là gì?
const createMember = ({ email, address = {} }) => {
const validEmail = /.+@.+..+/.test(email);
if (!validEmail) throw new Error("Valid email pls");
return {
email,
address: address ? address : null,
};
};
const member = createMember({ email: "my@email.com" });
console.log(member);
- A:
{ email: "my@email.com", address: null }
- B:
{ email: "my@email.com" }
- C:
{ email: "my@email.com", address: {} }
- D:
{ email: "my@email.com", address: undefined }
Xem đáp án
Đáp án: C
Giá trị mặc định của address
là một object rỗng {}
. Khi ta cho biến member
bằng với object được trả về bởi hàm createMember
, ta đã không truyền vào một giá trị của address, nghĩa là giá trị của address là object rỗng {}
được mặc định. Object rỗng mang giá trị truthy, tức là điều kiện address ? address : null
trả về true
. Giá trị của address là một object rỗng {}
.
C96:
Khi nào cần sử dụng async
và defer
trong javascript?
Xem đáp án
Khi sử dụng Javascript trong HTML, chúng ta có thể sử dụng các thuộc tính async
và defer
để tối ưu hóa quá trình tải trang web và cải thiện trải nghiệm người dùng.
Thuộc tính async
cho phép các script được tải và thực thi song song với quá trình phân tích HTML, điều này thích hợp với các script độc lập và không phụ thuộc vào nhau. Trong trường hợp này, việc sử dụng thuộc tính async sẽ giúp cải thiện tốc độ tải trang.
Thuộc tính defer
cũng cho phép tải và thực thi script song song với quá trình phân tích HTML, nhưng việc thực thi sẽ diễn ra sau khi trình duyệt đã phân tích xong toàn bộ HTML. Điều này thích hợp cho các script phụ thuộc vào nhau hoặc được sử dụng lại. Trong trường hợp này, việc sử dụng thuộc tính defer sẽ giúp đảm bảo thứ tự thực thi của các script.
Tuy nhiên, nếu script của bạn rất nhỏ (chỉ vài dòng code), tốt nhất là sử dụng inline script, tức là đặt code script trực tiếp vào trong thẻ script, để tránh tải thêm một file JavaScript độc lập.
Tóm lại, việc hiểu rõ tính chất, nguyên lý của các thuộc tính async
và defer
sẽ giúp bạn sử dụng chúng đúng cách và tối ưu hóa tốc độ tải trang của website.
Ví dụ sử dụng async
<!DOCTYPE html>
<html>
<head>
<title>Async Example</title>
</head>
<body>
<h1>Async Example</h1>
<script async src="script.js"></script>
<p>This paragraph may appear before the script is loaded and executed.</p>
</body>
</html>
Trong ví dụ trên, script.js
là một tập tin script external, và được đặt trong thẻ script
với thuộc tính async. Điều này cho phép trang web tiếp tục render các phần khác của HTML trong khi trang web đang tải script.js
. Khi script.js được tải và chạy xong, nó sẽ thực thi.
Ví dụ sử dụng defer
<!DOCTYPE html>
<html>
<head>
<title>Defer Example</title>
<script defer src="script.js"></script>
</head>
<body>
<h1>Defer Example</h1>
<p>This paragraph may appear before the script is loaded, but it won't execute until the entire HTML document is parsed.</p>
</body>
</html>
Trong ví dụ trên, script.js
được đặt trong thẻ script với thuộc tính defer. Script này sẽ được tải trong quá trình phân tích HTML, nhưng nó sẽ không thực thi cho đến khi HTML được phân tích hoàn tất. Điều này giúp tăng tốc độ hiển thị trang web của bạn, vì nó cho phép trình duyệt render phần còn lại của HTML trước khi script.js
được thực thi.
C97:
Kết quả đoạn code sau là gì?
let num = 10;
const increaseNumber = () => num++;
const increasePassedNumber = number => number++;
const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);
console.log(num1);
console.log(num2);
- A:
10
,10
- B:
10
,11
- C:
11
,11
- D:
11
,12
Xem đáp án
Đáp án: A
Phép toán ++
sẽ trả về trước giá trị của toán hạng, sau đó tăng giá trị của toán hạng lên. Giá trị của num1
là 10
, vì increaseNumber
sẽ trả về giá trị của num
, đang là 10
, và sau đó mới tăng giá trị của num
lên.
num2
cũng là 10
, vì chúng ta đưa num1
vào increasePassedNumber
. number
bằng 10
(tức giá trị của num1
). Cũng giống như trên, phép toán ++
sẽ trả về trước giá trị của toán hạng, sau đó tăng giá trị của toán hạng lên. Giá trị của number
là 10
, do đó num2
cũng sẽ là 10
.
C98:
Giải thích sự khác biệt giữa undefined
và not defined
trong JavaScript?
Xem đáp án
Trong JavaScript nếu bạn sử dụng một biến không tồn tại và chưa được khai báo, thì JavaScript sẽ thảy ra một lỗi var name is not defined
và sau đó script sẽ bị ngừng thực thi. Nhưng nếu bạn sử dụng typeof undeclared_variable
thì nó sẽ trả về undefined
.
Trước khi bắt đầu thảo luận thêm, hãy hiểu sự khác biệt giữa khai báo và định nghĩa.
var x là một khai báo vì bạn chưa xác định nó giữ giá trị nào, bạn chỉ đang khai báo sự tồn tại của nó và nhu cầu cấp phát bộ nhớ.
var x; // declaring x
console.log(x); //output: undefined
var x = 1
vừa là khai báo vừa là định nghĩa (cũng có thể nói là chúng ta đang khởi tạo), ở đây việc khai báo và gán giá trị xảy ra nội tuyến cho biến x, trong JavaScript mọi khai báo biến và khai báo hàm đều đưa lên đầu phạm vi (scope) hiện tại của nó, nó được khai báo sau đó việc gán diễn ra theo thứ tự, thuật ngữ này được gọi là hoisting
.
Một biến được khai báo nhưng không được định nghĩa và khi chúng ta cố gắng truy cập vào nó, nó sẽ trả về kết quả là undefined
.
var x; // Declaration
if(typeof x === 'undefined') // Will return true
Một biến không được khai báo và cũng không được định nghĩa thì khi chúng ta cố gắng tham chiếu đến biến đó, kết quả sẽ là not defined
.
console.log(y); // Output: ReferenceError: y is not defined
C99:
Kết quả đoạn code sau là gì?
(() => {
let x = (y = 10);
})();
console.log(typeof x);
console.log(typeof y);
- A:
"undefined", "number"
- B:
"number", "number"
- C:
"object", "number"
- D:
"number", "undefined"
Xem đáp án
Đáp án: A
let x = y = 10;
chính là cách viết ngắn gọn của:
y = 10;
let x = y;
Khi ta set y
bằng 10
, thực tế chúng ta đã sử dụng biến global y
(window
nếu là trên browser, global
nếu là môi trường Node).Trên browser, window.y
sẽ là 10
.
Sau đó, chúng ta khai báo giá trị của x
với giá trị của y
, tức 10
. Tuy nhiên khi ta khai báo với từ khóa let
biến x sẽ chỉ tồn tại trong block scoped; hay trong trường hợp này là hàm thực hiện ngay lập tức
(immediately-invoked function - IIFE). Khi ta sử dụng phép toán typeof
, x
hoàn toàn chưa được định nghĩa: vì x
lúc này nằm bên ngoài block nó được định nghĩa lúc trước. Nghĩa là x
là undefined
. Do đó console.log(typeof x)
trả về "undefined"
.
Tuy nhiên với y
thì khác, ta đã có giá trị của y
khi set y
bằng 10
. Giá trị đó có thể truy cập được từ bất kì đâu bởi chúng là biến global. y
được định nghĩa với kiểu là "number"
. Do đó console.log(typeof y)
trả về "number"
.
C100:
Yêu cầu bạn hãy xóa các phần tử duplicate trong mảng, bạn sẽ làm như thế nào?
Xem đáp án
Hãy sử dụng đối tượng Set()
, Sets là một kiểu đối tượng mới trong ES6 (ES2015) cho phép tạo các collections có các giá trị không trùng nhau.
Các giá trị trong một set có thể là các giá trị nguyên thủy đơn giản như chuỗi hoặc số nguyên, hoặc các kiểu đối tượng phức tạp hơn như các đối tượng hoặc mảng. Ví dụ
const array = [1, 4, 99, 3, 1, 4, 15];
const noDups = Array.from(new Set(array));
console.log(noDups); //[1, 4, 99, 3, 15]
C101:
Sự khác nhau giữa bind
, call
và apply
trong Javascript?
Xem đáp án
Cả ba phương thức bind
, call
, và apply
trong JavaScript đều được sử dụng để thay đổi ngữ cảnh của hàm bằng cách thay đổi giá trị của từ khóa this và truyền đối số vào hàm. Tuy nhiên, có sự khác nhau giữa chúng:
call
và apply
đều được sử dụng để gọi một hàm và thiết lập giá trị của từ khóa this
bằng cách truyền nó vào như một đối số đầu tiên của hàm. Tuy nhiên, sự khác nhau giữa hai phương thức này là cách truyền đối số cho hàm:
call
: truyền các đối số vào hàm dưới dạng danh sách các đối số, ví dụ:func.call(thisValue, arg1, arg2, ...)
.apply
: truyền các đối số vào hàm dưới dạng một mảng các đối số, ví dụ:func.apply(thisValue, [arg1, arg2, ...])
.
bind
cũng được sử dụng để thiết lập giá trị của từ khóa this, tuy nhiên nó không gọi hàm mà trả về một hàm mới với ngữ cảnh được thiết lập. Hàm mới này có thể được lưu trữ và sử dụng sau này. Phương thức bind có cú pháp như sau: var newFunc = func.bind(thisValue, arg1, arg2, ...)
Ví dụ:
const person = {
firstName: "John",
lastName: "Doe",
fullName: function() {
return `${this.firstName} ${this.lastName}`;
}
}
const person2 = {
firstName: "Jane",
lastName: "Smith"
}
// sử dụng call
console.log(person.fullName.call(person2)); // Jane Smith
// sử dụng apply
console.log(person.fullName.apply(person2)); // Jane Smith
// sử dụng bind
const person3 = {
firstName: "Mike",
lastName: "Johnson"
}
const fullNameFunc = person.fullName.bind(person3);
console.log(fullNameFunc()); // Mike Johnson
Trong ví dụ trên, chúng ta có một đối tượng person
với phương thức fullName
để trả về tên đầy đủ của người đó. Sử dụng call
và apply
, chúng ta có thể thay đổi ngữ cảnh của this
để truy xuất đến tên của người khác. Sử dụng bind
, chúng ta tạo ra một hàm mới và lưu trữ nó vào biến fullNameFunc
để có thể sử dụng sau này.
C102:
Sự khác biệt giữa từ khóa await
và từ khóa yield
là gì?
Xem đáp án
Answer yield
có thể được coi là cơ sở xây dựng của await
. yield
nhận giá trị mà nó được cho và chuyển nó cho caller
. Caller
sau đó có thể làm bất cứ điều gì nó muốn với giá trị đó (1). Sau đó, caller có thể trả lại một giá trị cho generator (thông qua generate.next()), giá trị này sẽ trở thành kết quả của biểu thức yield (2) hoặc một lỗi sẽ được thảy ra bởi biểu thức yield (3).
async - await
có thể được coi là sử dụng yield. Tại (1), caller (tức là trình điều khiển async - await - tương tự như chức năng bạn đã đăng) sẽ bọc giá trị trong một promise bằng cách sử dụng một thuật toán tương tự như new Promise(r => r(value))
(lưu ý, không phải là Promise.resolve, nhưng đó không phải là một vấn đề lớn). Sau đó nó sẽ đợi promise để resolve. Nếu nó hoàn thành, nó sẽ chuyển giá trị đã hoàn thành trở lại (2). Nếu nó reject, nó sẽ thảy ra lý do reject để làm lỗi ở (3).
Vì vậy, tiện ích của async - await
là sử dụng yield
để lấy giá trị thu được như một promise và truyền lại giá trị đã resolve của nó, lặp lại cho đến khi hàm trả về giá trị cuối cùng của nó.
C103:
Kết quả đoạn code sau là gì?
function getPersonInfo(one, two, three) {
console.log(one);
console.log(two);
console.log(three);
}
const person = "Lydia";
const age = 21;
getPersonInfo`${person} is ${age} years old`;
- A:
"Lydia"
21
["", " is ", " years old"]
- B:
["", " is ", " years old"]
"Lydia"
21
- C:
"Lydia"
["", " is ", " years old"]
21
Xem đáp án
Đáp án: B
Nếu bạn dùng tagged template literals, giá trị của đối số đầu tiên luôn luôn là một mảng các string. Những đối số còn lại sẽ lấy giá trị từ biểu thức đưa vào!
C104:
Phép toán này dùng để làm gì?
JSON.parse();
- A: Parse JSON thành một giá trị JavaScript
- B: Parse một JavaScript object thành JSON
- C: Parse giá trị JavaScript bất kì thành JSON
- D: Parse JSON thành một JavaScript object
Xem đáp án
Đáp án: A
Với phương thức JSON.parse()
, ta sẽ parse một chuỗi JSON thành một giá trị JavaScript.
Ví dụ:
// Chuyển một số thành một chuỗi JSON, sau đó parse chuỗi JSON đó để trả về một giá trị JavaScript:
const jsonNumber = JSON.stringify(4); // '4'
JSON.parse(jsonNumber); // 4
// Chuyển một mảng thành một chuỗi JSON, sau đó parse chuỗi JSON để trả về một giá trị JavaScript:
const jsonArray = JSON.stringify([1, 2, 3]); // '[1, 2, 3]'
JSON.parse(jsonArray); // [1, 2, 3]
// Chuyển một object thành một chuỗi JSON, sau đó parse chuỗi JSON để trả về một giá trị JavaScript:
const jsonArray = JSON.stringify({ name: "Lydia" }); // '{"name":"Lydia"}'
JSON.parse(jsonArray); // { name: 'Lydia' }
C105:
Kết quả đoạn code sau là gì?
console.log(String.raw`Hello
world`);
- A:
Hello world!
- B:
Hello
world
- C:
Hello world
- D:
Hello
world
Xem đáp án
Đáp án: C
String.raw
trả về chuỗi nguyên bản, các ký tự (
, ,
etc.) sẽ vẫn là nguyên bản và không biến thành xuống dòng hay khoảng trắng! Nếu ta không để là chuỗi nguyên bản, sẽ có trường hợp xảy ra lỗi không mong muốn, ví dụ với đường dẫn:
const path = `C:DocumentsProjects able.html`
Sẽ cho ta chuỗi là:
"C:DocumentsProjects able.html"
Với String.raw
, nó sẽ trả về là:
C:DocumentsProjects able.html
Do đó, trong trường hợp này Hello world
sẽ được ghi ra.
C106:
Sự khác biệt giữa Map
và WeakMap
trong ES6 là gì?
Xem đáp án
Cả hai đều hoạt động khác nhau khi một đối tượng được tham chiếu bởi các keys/values
của chúng bị xóa. Hãy lấy ví dụ dưới đây:
var map = new Map();
var weakmap = new WeakMap();
(function () {
var a = {
x: 12,
};
var b = {
y: 12,
};
map.set(a, 1);
weakmap.set(b, 2);
})();
Khi IIFE ở trên được thực thi, chúng ta không có cách nào có thể tham chiếu đến {x: 12}
và {y: 12}
nữa. Trình thu gom rác sẽ hành động và xóa con trỏ khóa b khỏi “WeakMap” và cũng xóa {y: 12}
khỏi bộ nhớ. Nhưng trong trường hợp Map, trình thu gom rác không xóa một con trỏ khỏi Map và cũng không xóa {x: 12}
khỏi bộ nhớ.
WeakMap cho phép bộ thu gom rác thực hiện nhiệm vụ của nó nhưng Map thì không. Với các maps được viết tay, mảng các keys sẽ giữ các tham chiếu đến các đối tượng chính, ngăn chúng bị bộ thu gom rác thu thập. Trong WeakMaps, các tham chiếu đến các đối tượng chính được giữ “một cách yếu ớt”, có nghĩa là chúng không ngăn chặn việc bị bộ thu gom rác thu thập trong trường hợp không có tham chiếu nào khác đến đối tượng.
C107:
Kết quả đoạn code sau là gì?
function compareMembers(person1, person2 = person) {
if (person1 !== person2) {
console.log("Not the same!");
} else {
console.log("They are the same!");
}
}
const person = { name: "Lydia" };
compareMembers(person);
- A:
Not the same!
- B:
They are the same!
- C:
ReferenceError
- D:
SyntaxError
Xem đáp án
Đáp án: B
Object sẽ được truyền vào hàm theo reference. Khi chúng ta nói so sánh strict equal (===
), nghĩa là ta đang so sánh các reference của chúng.
Ta set giá trị mặc định của person2
là object person
, và đưa object person
vào làm giá trị cho đối số person1
.
Điều đó có nghĩa là chúng cùng trỏ đến một object trong bộ nhớ, do đó chúng bằng nhau, và They are the same!
được in ra.
C108:
Kết quả đoạn code sau là gì?
const name = "Lydia";
age = 21;
console.log(delete name); console.log(delete age);
- A:
false
,true
- B:
"Lydia"
,21
- C:
true
,true
- D:
undefined
,undefined
Xem đáp án
Đáp án: A
Phép toán delete
sẽ trả về một giá trị boolean: true
nếu xóa thành công, false
nếu thất bại. Tuy nhiên, nếu biến được khai báo với các từ khóa var
, const
hay let
thì nó sẽ không thể bị xóa bởi phép toán delete
.
Biến name
được khai báo với từ khóa const
, nên nó sẽ không thể bị xóa và trả về false
. Khi ta set age
bằng 21
, thực tế là ta đang sử dụng biến global age
. Ta có thể xóa sử dụng phép toán delete
, khi này delete age
trả về true
.
C109:
Generator trong Javascript là gì?
Xem đáp án
Generator là các chức năng có thể được exit
và re-entered
sau đó. Context của chúng sẽ được lưu qua các lần truy cập lại. Các hàm Generator được viết bằng cú pháp
function* nameYourFuntion([param[, param[, ... param]]]) {
statements
}
Trong đó thì:
- nameYourFuntion: tên hàm
- param: tham số đầu vào của hàm, tối đa 255 tham số
- statements: phần thân chứa nội dung của hàm.
Khi chúng ta gọi Generator function nameYourFuntion()
, nó không trả về các kiểu dữ liệu cơ bản mà đẩy về một iterator object
. Hàm next()
của iterator object được sử dụng để truy xuất các node dữ liệu sau mỗi bước resume lại generator function. Khi đó generator function sẽ thực thi hàm cho đến khi gặp từ khóa yield
, hoặc return
kế tiếp chưa được duyệt ở bước trước. (iterator định nghĩa một chuẩn để tạo ra list các giá trị hữu hạn hoặc thậm chí vô hạn. Nó giúp bạn lấy ra giá trị mong muốn khi list kết quả đã được khởi tạo xong toàn bộ.)
Yield
được sử dụng ở một vài nơi và có vẻ khái niệm cũng hơi khác nhau. Về cơ bản, yeild
là từ khóa dùng để tạm dừng và cũng để tiếp tục việc thực thi bên trong generator function
. Ví dụ
function* generatorFunc(index) {
while (index < 2) {
yield index++;
}
}
const iterator = generatorFunc(0);
console.log(iterator.next());
// log output: {value : 0, done : false}
console.log(iterator.next());
// log output: {value : 1, done : false}
console.log(iterator.next());
// log output: {value : underfined, done : true}
Như đã đề cập ở trên thì iterator được khởi tạo bằng generatorFunc với index bắt đầu bằng 0. Bạn có thể thấy yeild ở đây, trong ví dụ này chính là một phiên bản khác giống như return vậy. Nó trả về một đối tượng IteratorResult với hai thuộc tính là “value” và “done”.
value : kết quả của biểu thức trả về.
done : nhận giá trị false nếu quá trình generator chưa hoàn thành, true nếu ngược lại.
Giá trị của index được giữ lại sau mỗi lần chúng ta gọi next() và tất nhiên là cả ngữ cảnh của hàm generator cũng thế cho đến khi toàn bộ yield, return đã được duyệt qua..
C110:
Làm thế nào có thể ghi ra giá trị giống như trong comment khi console.log
?
function* startGame() {
const answer = yield "Do you love JavaScript?";
if (answer !== "Yes") {
return "Oh wow... Guess we're gone here";
}
return "JavaScript loves you back ❤️";
}
const game = startGame();
console.log(/* 1 */); // Do you love JavaScript?
console.log(/* 2 */); // JavaScript loves you back ❤️
- A:
game.next("Yes").value
andgame.next().value
- B:
game.next.value("Yes")
andgame.next.value()
- C:
game.next().value
andgame.next("Yes").value
- D:
game.next.value()
andgame.next.value("Yes")
Xem đáp án
Đáp án: C
Một generator sẽ “tạm dừng” khi nhìn thấy từ khóa yield
. Đầu tiên ra sẽ đưa ra chuỗi “Do you love JavaScript?”, bằng cách gọi game.next().value
.
Chương trình sẽ chạy từng dòng, cho tới khi nó tìm thấy từ khóa yield
. Có một từ khóa yield
tại dòng đầu tiên của hàm: chương trình sẽ dừng tại đâ! Điều đó có nghĩa là biến answer
chưa hề được định nghĩa!
Khi ta gọi game.next("Yes").value
, yield
trước đó sẽ được thay thế bởi giá trị được truyền vào hàm next()
, trong trường hợp này là"Yes"
. Theo đó giá trị của biến answer
giờ sẽ là "Yes"
. Điều kiện if sẽ trả về false
, và JavaScript loves you back ❤️
sẽ được ghi ra.
C111:
Kết quả đoạn code sau là gì?
function sum(a, b) {
return a + b;
}
sum(1, "2");
- A:
NaN
- B:
TypeError
- C:
"12"
- D:
3
Xem đáp án
Đáp án: C
JavaScript là một ngôn ngữ dynamically typed: chúng ta không khai báo kiểu dữ liệu khi khai báo biến. Giá trị có thể bị tự động convert sang một kiểu dữ liệu khác mà ta không hề hay biết, điều này được gọi là implicit type coercion. Coercion có nghĩa là convert từ kiểu này sang kiểu khác.
Trong ví dụ này, JavaScript sẽ convert số 1
sang dạng string. Mỗi khi ta cộng một số (1
) với một string ('2'
), số sẽ luôn được xem như là một string. Kết quả sẽ là một phép nối chuỗi giống như "Hello" + "World"
, vậy nên "1" + "2"
sẽ trả về là "12"
.
C112:
Symbol
trong ES6 là gì?
Xem đáp án
Symbol là một loại đối tượng mới, đặc biệt, có thể được sử dụng như một tên thuộc tính duy nhất trong các đối tượng.
Sử dụng Symbol thay vì string cho phép các modules khác nhau tạo ra các thuộc tính không xung đột với nhau. Các symbols cũng có thể được đặt ở chế độ riêng tư, để các thuộc tính của chúng không thể bị truy cập bởi những ai chưa có quyền truy cập trực tiếp vào Symbol.
Symbol là một loại primitive mới. Cũng giống như các primitive khác như number, string và boolean, Symbol có một hàm được sử dụng để tạo chúng. Không giống như các primitive khác, Symbols không có literal syntax (Literal là một giá trị mà nó thể hiện chính nó, ví dụ: số 12 — thể hiện là một giá trị kiểu number, hay “Javascript” — đại diện cho một giá trị kiểu string) - cách duy nhất để tạo chúng là với hàm tạo Symbol theo cách sau:
let symbol = Symbol();
Trên thực tế, các Symbol chỉ là một cách hơi khác để gắn các thuộc tính vào một đối tượng - bạn có thể dễ dàng cung cấp các Symbol thường dùng dưới dạng các phương thức chuẩn, giống như Object.prototype.hasOwnProperty
xuất hiện trong mọi thứ kế thừa từ Object.
C113:
So sánh sự khác nhau giữa Object.freeze()
và const
?
Xem đáp án
Answer const
và Object.freeze
là hai thứ hoàn toàn khác nhau:
- const áp dụng cho các ràng buộc biến. Nó tạo ra một ràng buộc bất biến, tức là bạn không thể gán một giá trị mới cho ràng buộc đó.
const person = {
name: "Leonardo",
};
let animal = {
species: "snake",
};
person = animal; // ERROR "person" is read-only
Object.freeze
hoạt động trên các giá trị và cụ thể hơn là các giá trị đối tượng. Nó làm cho một đối tượng trở nên bất biến, tức là bạn không thể thay đổi các thuộc tính của nó.
let person = {
name: "Leonardo",
};
let animal = {
species: "snake",
};
Object.freeze(person);
person.name = "Lima"; // TypeError: Cannot assign to read only property 'name' of object
console.log(person);
C114:
Kết quả đoạn code sau là gì?
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const lydia = new Person("Lydia", "Hallie");
const sarah = Person("Sarah", "Smith");
console.log(lydia);
console.log(sarah);
- A:
Person {firstName: "Lydia", lastName: "Hallie"}
vàundefined
- B:
Person {firstName: "Lydia", lastName: "Hallie"}
vàPerson {firstName: "Sarah", lastName: "Smith"}
- C:
Person {firstName: "Lydia", lastName: "Hallie"}
và{}
- D:
Person {firstName: "Lydia", lastName: "Hallie"}
vàReferenceError
Xem đáp án
Đáp án: A
Với sarah
, chúng ta khai báo mà không có từ khóa new
. Khi sử dụng new
, nó sẽ trỏ đến một object mới mà ta vừa tạo ra. Tuy nhiên nếu ta không dùng new
thì nó sẽ trỏ tới global object!
Chúng ta cho rằng this.firstName
là "Sarah"
và this.lastName
là "Smith"
. Tuy nhiên sự thực là chúng ta đã định nghĩa global.firstName = 'Sarah'
và global.lastName = 'Smith'
. Bản thân biến sarah
vẫn là undefined
.
C115:
Sự khác nhau giữa Anonymous function và Named function là gì?
Xem đáp án
Anonymous function là một hàm không có tên, thường được sử dụng để truyền vào các hàm khác như là một tham số hoặc định nghĩa trực tiếp trong một biểu thức.
Ví dụ:
var multiply = function(x, y) {
return x * y;
};
Named function là một hàm được đặt tên và được định nghĩa riêng biệt, có thể được gọi nhiều lần trong chương trình.
function multiply(x, y) {
return x * y;
}
Sự khác biệt chính giữa Anonymous function và Named function là Named function có tên và được sử dụng để tái sử dụng trong chương trình, trong khi Anonymous function được sử dụng để truyền vào các hàm khác như là một tham số hoặc định nghĩa trực tiếp trong một biểu thức.
C116:
Kết quả đoạn code sau là gì?
function getAge() {
"use strict";
age = 21;
console.log(age);
}
getAge();
- A:
21
- B:
undefined
- C:
ReferenceError
- D:
TypeError
Xem đáp án
Đáp án: C
Với "use strict"
, chúng ta sẽ đảm bảo được rằng ta sẽ không bao giờ khai báo biến global một cách vô ý. Tại đây chúng ta chưa khai báo biến age
, và khi dùng "use strict"
, nó sẽ throw ra một reference error. Nếu như không dùng "use strict"
, nó sẽ vẫn hoạt động, vì thuộc tính age
sẽ được thêm vào global object.
C117:
Đệ quy là gì?
Xem đáp án
Đệ quy là một kỹ thuật lặp đi lặp lại một hoạt động bằng cách tự gọi hàm lặp đi lặp lại cho đến khi nó đi đến kết quả.
function add(number) {
if (number <= 0) {
return 0;
} else {
return number + add(number - 1);
}
}
add(3) => 3 + add(2)
3 + 2 + add(1)
3 + 2 + 1 + add(0)
3 + 2 + 1 + 0 = 6
Ví dụ về một hàm đệ quy:
Hàm sau đây tính tổng của tất cả các phần tử trong một mảng bằng cách sử dụng đệ quy:
function computeSum(arr){
if(arr.length === 1){
return arr[0];
}
else{
return arr.pop() + computeSum(arr);
}
}
computeSum([7, 8, 9, 99]); // Returns 123
C118:
Giải thích chính sách same-origin
trong JavaScript?
Xem đáp án
Same-origin policy (SOP) là một trong những chính sách bảo mật quan trọng nhất trên trình duyệt hiện đại, nhằm ngăn chặn JavaScript code có thể tạo ra những request đến những nguồn khác với nguồn mà nó được trả về. Ba tiêu chí chính để so sánh request bao gồm:
- Domain (tên miền)
- Protocol (giao thức)
- Port (cổng kết nối)
Nói đơn giản thì request sẽ được coi là hợp lệ chỉ khi nó thỏa mãn 3 tiêu chí ở trên (cùng domain,cùng protocol và cùng port)
Ví dụ: khi chúng ta đang mở 2 tab, 1 tab là facebook, tab kia là 1 trang web nào đó có chứa mã độc. Sẽ rất nguy hiểm nếu như các đoạn script ở bên tab chứa mã độc có thể tự do thao tác lên tab facebook phía bên kia, và SOP sinh ra với nhiệm vụ ngăn chặn các hành động này.
Dưới đây là vd về list các pages vi phạm SOP của site origin( http://www.example.com) :
-
http://www.example.co.uk (khác domain)
-
http://example.org (khác domain)
-
https://example.com (khác protocol)
-
http://example.com:8080 (khác port)
C119:
Cho một ví dụ về curry
và tại sao cú pháp này mang lại lợi ích?
Xem đáp án
Currying
là một pattern trong đó một hàm có nhiều hơn một tham số được chia thành nhiều hàm, khi được gọi nối tiếp, sẽ tích lũy tất cả các tham số được yêu cầu cùng một lúc.
Kỹ thuật này có thể hữu ích để làm cho mã được viết theo kiểu functional dễ đọc và soạn thảo hơn. Điều quan trọng cần lưu ý là để một hàm được xử lý, nó cần bắt đầu như một hàm, sau đó được chia nhỏ thành một chuỗi các hàm mà mỗi hàm chấp nhận một tham số.
function curry(fn) {
if (fn.length === 0) {
return fn;
}
function _curried(depth, args) {
return function (newArgument) {
if (depth - 1 === 0) {
return fn(...args, newArgument);
}
return _curried(depth - 1, [...args, newArgument]);
};
}
return _curried(fn.length, []);
}
function add(a, b) {
return a + b;
}
var curriedAdd = curry(add);
var addFive = curriedAdd(5);
var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]
C120:
Kết quả đoạn code sau là gì?
function greeting() {
throw "Hello world!";
}
function sayHi() {
try {
const data = greeting();
console.log("It worked!", data);
} catch (e) {
console.log("Oh no an error!", e);
}
}
sayHi();
- A:
"It worked! Hello world!"
- B:
"Oh no an error: undefined
- C:
SyntaxError: can only throw Error objects
- D:
"Oh no an error: Hello world!
Xem đáp án
Đáp án: D
Với lệnh throw
, chúng ta có thể tạo ra các errors tùy ý. Với câu lệnh đó, chúng ta có thể throw các exception. Một exeption có thể là một chuỗi, một số, một boolean hoặc một object. Trong trường hợp này thì nó là chuỗi 'Hello world'
.
Với lệnh catch
chúng ta có thể xử lý những exeption được throw ra khi thực hiện try
. Một exeption đã được throw ra: chuỗi 'Hello world'
. e
chính là chuỗi đó và chúng ta sẽ in ra. Kết quả là 'Oh an error: Hello world'
.
C121:
Kết quả đoạn code sau là gì?
function checkAge(data) {
if (data === { age: 18 }) {
console.log("You are an adult!");
} else if (data == { age: 18 }) {
console.log("You are still an adult.");
} else {
console.log(`Hmm.. You don't have an age I guess`);
}
}
checkAge({ age: 18 });
- A:
You are an adult!
- B:
You are still an adult.
- C:
Hmm.. You don't have an age I guess
Xem đáp án
Đáp án: C
Khi test sự bằng nhau, các kiểu dữ liệu cơ bản sẽ so sánh giá trị của chúng, còn object thì so sánh tham chiếu. JavaScript sẽ kiểm tra xem các object đó có trỏ đến những vùng nhớ giống nhau hay không.
Hai object chúng ta đang so sánh không có được điều đó: object đối số tham chiếu đến một vùng nhớ khác với object chúng ta dùng để kiểm tra sự bằng nhau.
Đó là lý do tại sao cả { age: 18 } === { age: 18 }
và { age: 18 } == { age: 18 }
đều trả về false
.
C122:
Kết quả đoạn code sau là gì?
const user = { name: "Lydia", age: 21 };
const admin = { admin: true, ...user };
console.log(admin);
- A:
{ admin: true, user: { name: "Lydia", age: 21 } }
- B:
{ admin: true, name: "Lydia", age: 21 }
- C:
{ admin: true, user: ["Lydia", 21] }
- D:
{ admin: true }
Xem đáp án
Đáp án: B
Ta có thể kết hợp 2 object sử dụng phép toán spread operator
...
. Nó cho phép ta tạo ra bản sao của từng cặp key/values trong từng object và nối chúng lại với nhau thành một object mới. Trong trường hợp này chúng ta tạo ra các bản sao của các cặp key/value của object user
object, và nối chúng vào object admin
. admin
object khi này sẽ trở thành { admin: true, name: "Lydia", age: 21 }
.
C123:
Giải thích sự khác nhau giữa hai cách sử dụng dưới đây
function Person() {}
var person = Person();
var person = new Person();
Xem đáp án
Câu hỏi này khá mơ hồ. Dự đoán tốt nhất của tôi về ý định của nó là nó đang hỏi về các hàm tạo trong JavaScript. Về mặt kỹ thuật, function Person() {}
chỉ là một khai báo hàm bình thường. Quy ước là sử dụng PascalCase cho các hàm được sử dụng làm hàm tạo.
var person = Person()
gọi Person dưới dạng một hàm chứ không phải là một hàm tạo. Gọi như vậy là một sai lầm phổ biến nếu hàm được dự định sử dụng như một phương thức khởi tạo. Thông thường, hàm tạo không trả về bất kỳ thứ gì, do đó việc gọi hàm tạo giống như một hàm bình thường sẽ trả về undefined
và được gán cho biến dùng để làm instance.
var person = new Person()
tạo một instance của đối tượng Person bằng toán tử new
, toán tử này kế thừa từ Person.prototype
. Một giải pháp thay thế là sử dụng Object.create
, ví dụ: Object.create(Person.prototype
).
function Person(name) {
this.name = name;
}
var person = Person("John");
console.log(person); // undefined
console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined
var person = new Person("John");
console.log(person); // Person { name: "John" }
console.log(person.name); // "john"
C124:
Prototype trong Javascript là gì?
Xem đáp án
Tất cả các đối tượng javascript đều kế thừa các thuộc tính từ một prototype.
Ví dụ:
Đối tượng Date kế thừa các thuộc tính từ prototype Date.
Đối tượng Math kế thừa các thuộc tính từ prototype Math.
Đối tượng Array kế thừa các thuộc tính từ prototype Array.
Trên đầu chuỗi là Object.prototype
. Mọi prototype đều kế thừa các thuộc tính và phương thức từ Object.prototype
.
Prototype là một bản thiết kế của một đối tượng. Prototype cho phép chúng ta sử dụng các thuộc tính và phương thức trên một đối tượng ngay cả khi các thuộc tính và phương thức không tồn tại trên đối tượng hiện tại.
Ví dụ:
var arr = [];
arr.push(2);
console.log(arr); // Outputs [2]
Trong đoạn code trên, có thể thấy ta chưa xác định bất kỳ thuộc tính hoặc phương thức nào được gọi là push trên mảng arr
nhưng javascript engine không đưa ra lỗi.
Lý do là việc sử dụng các prototype. Như đã thảo luận trước đây, các đối tượng Array kế thừa các thuộc tính từ prototype Array.
Javascript engine thấy rằng phương thức push
không tồn tại trên đối tượng mảng hiện tại, do đó nó tìm kiếm phương thức push bên trong prototype Array và nó tìm thấy phương thức.
Bất cứ khi nào thuộc tính hoặc phương thức không được tìm thấy trên đối tượng hiện tại, javascript engine sẽ luôn tìm kiếm trong prototype của nó và nếu nó vẫn không tồn tại, nó sẽ tìm bên trong prototype của prototype, v.v.
C125:
Prototype Design Pattern trong Javascript như thế nào?
Xem đáp án
Prototype Pattern là một object-based. Nó tạo ra một phiên bản mới của đối tượng dựa trên đối tượng nguyên mẫu. Mục tiêu chính là tạo ra một đối tượng sử dụng làm làm bản thiết kế cho mỗi đối tượng được tạo sau đó.
Nếu bạn cảm thấy khởi tạo một đối tượng mới trực tiếp quá phức tạp và không hiệu quả, trong trường hợp này, sử dụng Prototype Pattern là một cách ý tưởng không tồi.
Bạn có thể triển khai Prototype Pattern theo các như sau:
// Khởi tạo đối tượng
function Person(name, age) {
this.name = name;
this.age = age;
this.showName = () => console.log(this.name);
}
// Clone prototype của đối tượng
function PersonPrototype(prototype) {
const _prototype = prototype;
this.clone = () => {
let person = new Person();
person.name = _prototype.name;
person.age = _prototype.age;
return person;
};
}
const person = new Person("Codestus.com", 20);
const prototypeOfPerson = new PersonPrototype(person);
const tester = prototypeOfPerson.clone();
tester.showName(); // "Codestus.com"
Ngoài ra, bạn cũng có thể sử dụng khái niệm kế thừa prototype đã tích hợp sẵn trong đối tượng Object.
const person = {
name: "Codestus.com",
age: 20,
};
const personA = Object.assign({}, person);
C126:
Temporal Dead Zone trong ES6 là gì?
Xem đáp án
Trong ES6, let
và const
được hoisting (giống như var
, class
và function
), nhưng có một khoảng thời gian giữa phạm vi truy cập và được khai báo là nơi chúng không thể được truy cập. Khoảng thời gian này là Temporal Dead Zone (TDZ).
Chúng ta cùng xem ví dụ dưới đây:
// console.log(aLet) // would throw ReferenceError
let aLet;
console.log(aLet); // undefined
aLet = 10;
console.log(aLet); // 10
Trong ví dụ này, Temporal Dead Zone kết thúc khi aLet được khai báo, thay vì được gán.
C127:
Thuật ngữ transpiling là gì?
Xem đáp án
Không có cách nào để polyfill các cú pháp mới đã được thêm vào ngôn ngữ. Vì vậy, lựa chọn tốt hơn là sử dụng một công cụ chuyển đổi mã mới hơn của bạn thành các mã tương đương cũ hơn. Quá trình này thường được gọi là transpiling, một thuật ngữ để chuyển đổi + biên dịch (transforming + compiling).
Thông thường, bạn chèn transpiler
(trình chuyển đổi) vào quy trình phát triển của mình, tương tự như trình ghép mã (linter) hoặc trình thu nhỏ (minifier) của bạn. Có khá nhiều bộ chuyển đổi tuyệt vời cho bạn lựa chọn:
-
Babel: Chuyển ES6+ thành ES5
-
Traceur: Chuyển ES6, ES7 và hơn thế nữa sang ES5
C128:
Command Pattern trong Javascript như thế nào?
Xem đáp án
Mục đích của Command Pattern là đóng gói các hành động dưới dạng đối tượng. Cho phép phân tách hệ thống các đối tượng nhận yêu cầu với các đối tượng thực thi yêu cầu, có khả năng ghép nối. Trong đó, các yêu cầu được gọi là sự kiện và các mã để thực thi yêu cầu được gọi là trình xử lý sự kiện.
Nào bây giờ hãy tưởng tượng bạn nhận được yêu cầu xây dựng hệ thống thanh toán cho một cửa hàng thương mại điện tử. Tuỳ thuộc vào loại phương thức thanh toán, bạn sẽ chọn cho mình một quy trình cụ thể.
Ví dụ
if (paymentMethod === "creditcard") {
// Xử lý thanh toán
}
Một số phương thức thanh toán chỉ cần một bước duy nhất, một số khác thì không. Dựa vào ví dụ trên, chúng ta sẽ thử xây dựng quy trình thanh toán.
Command Pattern là một giải pháp tốt để áp dụng trong ví dụ này. Cụ thể về ví dụ, hệ thống xử lý của bạn sẽ không biết nhiều thông tin về việc xử lý từng phương thức thanh toán như thế nào. Các yêu cầu xử lý thanh toán và nơi xử lý thanh toán sẽ tách biệt và được ghép nối lại từ hệ thống xử lý.
// Phần lõi điều hướng xử lý
function Command(operation) {
this.operation = operation;
}
Command.prototype.execute = function () {
this.operation.execute();
};
// Hàm phương thức thanh toán
function ProccessPaypalPayment() {
return {
execute: function () {
console.log("Payment with Paypal");
},
};
}
// Hàm phương thức thanh toán
function ProccessCreditCardPayment() {
return {
execute: function () {
console.log("Payment with Credit Card");
},
};
}
// Hàm xử lý thanh toán nhận yêu cầu và điều hướng xử lý thực thi
function PaymentHandler() {
let paymentCommand;
return {
setPaymentCommand(command) {
this.paymentCommand = command;
},
executeCommand() {
this.paymentCommand.execute();
},
};
}
function main() {
let systemPayment = new PaymentHandler();
systemPayment.setPaymentCommand(new Command(new ProccessPaypalPayment()));
systemPayment.executeCommand();
}
main();
C129:
Closure trong javascript là gì, cho ví dụ?
Xem đáp án
Trong javascript, closure
là một chức năng có quyền truy cập vào phạm vi cha, ngay cả sau khi scope đã đóng.
Đầu tiên bạn phải hiểu về scope như đã nói ở trên? Scope chính là tuổi thọ của một biến trong javascript. Bạn có thể thấy trong đó một biến được định nghĩa đóng một vai trò lớn trong khoảng thời gian của biến đó và các hàm trong chương trình của bạn có quyền truy cập vào đó. Ví dụ:
Khi sử dụng closure
function createCounter() {
let count = 0;
return function() {
count++;
console.log(count);
}
}
const counter = createCounter();
counter(); // in ra 1
counter(); // in ra 2
counter(); // in ra 3
Trong đoạn code trên, hàm createCounter() trả về một hàm lồng nhau, và biến count là một biến cục bộ (local variable) của hàm createCounter(). Tuy nhiên, khi chúng ta gọi hàm createCounter() và lưu kết quả vào biến counter, các lần gọi sau của hàm counter đều có thể truy cập và thay đổi giá trị của biến count, ngay cả khi hàm createCounter() đã kết thúc thực thi. Điều này là do hàm counter tạo ra một closure, cho phép nó truy cập vào biến count của hàm bên ngoài.
Closure còn được sử dụng rất nhiều trong các pattern thiết kế và thư viện JavaScript, ví dụ như IIFE (Immediately Invoked Function Expression), module pattern, debounce/throttle function, và nhiều hơn nữa.
C130:
Constructor Design Pattern trong Javascript như thế nào?
Xem đáp án
Trong các ngôn ngữ lập trình hướng đối tượng, constructor
là một phương thức đặt biệt được sử dụng để khởi tạo đối tượng mới cùng với các thuộc tính khởi tạo ban đầu.
Trong JavaScript, hầu như mọi thứ đều là một object và chúng ta thường quan tâm đến việc khởi tạo đối tượng với Object Constructor
.
// Sử dụng {}
const person = {};
// Sử dụng Object()
const person = new Object();
Ngoài ra, bạn cũng có thể khởi tạo 1 constructor bằng function
function Person() {}
const personA = new Person();
personA.name = "Codestus.com";
C131:
Singleton Design Pattern trong Javascript như thế nào?
Xem đáp án
Đây là một design pattern vô cùng nổi tiếng, chúng ta sử dụng singleton pattern
để hạn chế khởi tạo đối tượng, giảm bớt được khai báo đối tượng dư thừa, chỉ khởi tạo một lần duy nhất và có thể truy cập toàn cục. Đây sẽ là một pattern vô cùng hữu ích cho các trường hợp bạn phải xử lý 1 tác vụ ở nhiều nơi, có thể hạn chế được số lần khai báo đối tượng không cần thiết. Cùng thử xem ví dụ:
const utils = (() => {
let instance;
function initialize() {
return {
sum: function () {
let nums = Array.prototype.slice.call(arguments);
return nums.reduce((numb, total) => numb + total, 0);
},
};
}
return {
getInstance: function () {
// Nếu đối tượng này chưa được khởi tạo
if (!instance) {
// Khởi tạo lần đầu tiên
instance = new initialize();
}
// Không khởi tạo nữa, chỉ trả về đối tượng đã khởi tạo
return instance;
},
};
})();
const firstU = utils.getInstance(); // Cùng lấy 1 instance
const secondU = utils.getInstance(); // Cùng lấy 1 instance
console.log(firstU === secondU); // Trả về true là đúng vì cùng thuộc 1 instance duy nhất
console.log(firstU.sum(1, 2, 3, 4, 5)); // 15 // working
C132:
Currying function trong Javascript là gì?
Xem đáp án
Currying là khi bạn chia nhỏ một hàm có nhiều đối số thành một chuỗi các hàm có một phần đối số. Đây là một ví dụ trong JavaScript:
function add(a, b) {
return a + b;
}
add(3, 4); // returns 7
Đây là một hàm nhận hai đối số, a
và b
và trả về tổng của chúng. Bây giờ chúng ta sẽ dùng curry
cho hàm này:
function add(a) {
return function (b) {
return a + b;
};
}
Trong đại số học, việc xử lý các hàm có nhiều đối số (hoặc tương đương với một đối số là N-tuple) hơi không phù hợp. Vì vậy, làm thế nào để bạn đối phó với một cái gì đó bạn muốn diễn đạt một cách tự nhiên, chẳng hạn như, f(x, y)?
. Vâng, bạn coi điều đó tương đương với f(x)(y) - f(x)
, gọi nó là g
, là một hàm, và bạn áp dụng hàm đó cho y
.
Nói cách khác, bạn chỉ có các hàm nhận một đối số - nhưng một số hàm trong số đó trả về các hàm khác (Cũng nhận một đối số).
Ví dụ bạn có một hàm để tính giá trị discount, giảm ngay 10%
cho khách hàng thân thiết.
function discount(price, discount) {
return price * discount;
}
// Giảm ngay 50 đồng khi khách hàng đã tiêu 500 đồng.
const price = discount(500, 0.1); // $50
// $500 - $50 = $450
Khách hàng tiêu tiền điên cuồng, chúng ta liên tục gọi hàm
const price = discount(1500, 0.1); // $150
// $1,500 - $150 = $1,350
const price = discount(2000, 0.1); // $200
// $2,000 - $200 = $1,800
const price = discount(50, 0.1); // $5
// $50 - $5 = $45
const price = discount(5000, 0.1); // $500
// $5,000 - $500 = $4,500
const price = discount(300, 0.1); // $30
// $300 - $30 = $270
Chúng ta có thể đưa vào giá trị discount ở lần đầu tiên, đến các lần gọi tiếp theo, chúng ta ko cần truyền giá trị 10% này nữa
function discount(discount) {
return price => {
return price * discount;
};
}
const tenPercentDiscount = discount(0.1);
tenPercentDiscount(500); // $50
const twentyPercentDiscount = discount(0.2);
twentyPercentDiscount(500); // 100
// $500 - $100 = $400
twentyPercentDiscount(5000); // 1000
// $5,000 - $1,000 = $4,000
twentyPercentDiscount(1000000); // 200000
// $1,000,000 - $200,000 = $600,000
Nói một cách ngắn gọn, khi cần truyền vào 1 argument
ít thay đổi, cố định trong đa số các trường hợp, nghĩ đến currying
.
C133:
Giải thích Prototype Inheritance trong JavaScript là gì?
Xem đáp án
Trong một ngôn ngữ thực hiện kế thừa cổ điển như Java, C # hoặc C ++, bạn bắt đầu bằng cách tạo một class - một bản thiết kế cho các đối tượng của bạn - và sau đó bạn có thể tạo các đối tượng mới từ class đó hoặc bạn có thể mở rộng class, xác định một class mới để tăng cường class ban đầu.
Trong JavaScript, trước tiên bạn tạo một đối tượng (không có khái niệm về class trong Javascript), sau đó bạn có thể tăng cường đối tượng của riêng mình hoặc tạo các đối tượng mới từ nó. Mọi đối tượng trong Javascript đều có một prototype
. Hệ thống kế thừa của JavaScript là nguyên mẫu và không dựa trên class. Khi một thông báo đến một đối tượng, JavaScript sẽ cố gắng tìm một thuộc tính trong đối tượng đó trước, nếu không thể tìm thấy thì thông báo sẽ được gửi đến prototype của đối tượng, v.v. Hành vi đó được gọi là prototype chain (chuỗi nguyên mẫu) hoặc prototype inheritance.
Hàm tạo (constructor) là cách được sử dụng nhiều nhất trong JavaScript để tạo prototype chain. Khi chúng ta sử dụng new
, JavaScript đưa một tham chiếu ngầm đến đối tượng mới đang được tạo dưới dạng từ khóa this
. Nó cũng trả về tham chiếu này một cách ngầm định ở cuối hàm.
function Foo() {
this.kind = "foo";
}
var foo = new Foo();
foo.kind; //=> foo
C134:
Sử dụng Promise trong JavaScript như thế nào?
Xem đáp án
Promise dùng cho xử lý bất đồng bộ trong JavaScript
Trước promise, callback được dùng cho các thao tác bất đồng bộ. Nhưng callback có giới hạn của nó, nếu sử dụng quá nhiều callback code sẽ trở nên khó quản lý.
Đối tượng promise có 4 trạng thái:
- Pending: trạng thái bắt đầu, biểu diễn promise không phải là fulfilled, cũng không phải là rejected mà đang ở trạng thái pending.
- Fulfilled: trạng thái này có nghĩa là thao tác bất đồng bộ đã hoàn tất.
- Rejected: trạng này này có nghĩa là thao tác đã thất bại vì một vài lý do nào đó.
- Settked: trạng thái này thể hiện promise đã rejected hoặc fulfilled.
Một promise được tạo bằng cách sử dụng phương thức khởi tạo Promise, hàm này nhận một hàm callback với hai tham số, resolve
và reject
tương ứng.
- resolve hàm được gọi, khi thao tác bất đồng bộ thực hiện thành công.
- reject hàm được gọi, khi thao tác thất bại bởi một vài lỗi nào đó.
Ví dụ:
Promise được dùng cho các thao tác bất đồng bộ như yêu cầu của server, để dễ hiểu ta lấy ví dụ với một phép toán để tính tổng của ba phần tử.
function sumOfThreeElements(...elements){
return new Promise((resolve,reject)=>{
if(elements.length > 3 ){
reject("Only three elements or less are allowed");
}
else{
let sum = 0;
let i = 0;
while(i < elements.length){
sum += elements[i];
i++;
}
resolve("Sum has been calculated: "+sum);
}
})
}
Trong đoạn code trên, ta đang tính tổng của ba phần tử, nếu độ dài của mảng phần tử lớn hơn 3, thì promise sẽ bị rejected, ngược lại thì promise sẽ được resolved và tổng được trả về.
Chúng ta có thể sử dụng bất kỳ promise nào bằng cách gắn các phương thức then()
và catch()
vào đối tượng sử dụng.
- then() phương này được truy cập khi kết quả của promise là fulfilled.
- catch() phương này được truy cập khi kết quả của promise là rejected.
Ví dụ:
sumOfThreeElements(4, 5, 6)
.then(result=> console.log(result))
.catch(error=> console.log(error));
// In the code above, the promise is fulfilled so the then() method gets executed
sumOfThreeElements(7, 0, 33, 41)
.then(result => console.log(result))
.catch(error=> console.log(error));
// In the code above, the promise is rejected hence the catch() method gets executed
C135:
Higher-Order Function trong Javascript là gì?
Xem đáp án
Một higher-order function là một hàm nhận một hoặc nhiều hàm làm đối số, hàm này sử dụng để hoạt động trên một số dữ liệu hoặc trả về một hàm khác. Các higher-order functions có nghĩa là trừu tượng hóa một số hoạt động được thực hiện lặp đi lặp lại.
Ví dụ cổ điển về điều này là map
, nhận một mảng và một hàm làm đối số, sau đó map sử dụng hàm này để biến đổi từng item trong mảng, trả về một mảng mới với dữ liệu đã biến đổi. Các ví dụ phổ biến khác trong JavaScript là forEach
, filter
và reduce
…
Một higher-order function không nhất thiết chỉ được thao tác với các mảng mà còn có nhiều trường hợp sử dụng để trả về một hàm từ một hàm khác, ví dụ như Function.prototype.bind
trong JavaScript.
** Ví dụ 1:** chúng ta có một mảng các số và chúng ta muốn tạo một mảng mới gấp đôi mỗi giá trị của mảng đầu tiên. Hãy để xem làm thế nào chúng ta có thể giải quyết vấn đề có và không có Higher-Order Functions.
Không Higher-order function
const arr1 = [1, 2, 3];
const arr2 = [];
for (let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i] * 2);
}
// prints [ 2, 4, 6 ]
console.log(arr2);
Có Higher-order function
const arr1 = [1, 2, 3];
const arr2 = arr1.map(function (item) {
return item * 2;
});
console.log(arr2);
Chúng ta cũng có thể làm code ngắn hắn bằng cách sử dụng cú pháp arrow function:
const arr1 = [1, 2, 3];
const arr2 = arr1.map(item => item * 2);
console.log(arr2);
C136:
Những công dụng của WeakMap
trong ES6 là gì?
Xem đáp án
WeakMap chỉ có trong ES6 trở lên. WeakMap cung cấp một cách thức để mở rộng các đối tượng từ bên ngoài mà không can thiệp vào việc thu gom rác (garbage collection). Bất cứ khi nào bạn muốn mở rộng một đối tượng nhưng không thể vì nó bị bịt kín - hoặc từ một nguồn bên ngoài - thì một WeakMap có thể được áp dụng.
WeakMap là một tập hợp các cặp khóa và giá trị, trong đó khóa phải là một đối tượng.
var map = new WeakMap();
var pavloHero = {
first: "Pavlo",
last: "Hero",
};
var gabrielFranco = {
first: "Gabriel",
last: "Franco",
};
map.set(pavloHero, "This is Hero");
map.set(gabrielFranco, "This is Franco");
console.log(map.get(pavloHero)); //This is Hero
Khía cạnh thú vị của WeakMap là nó giữ một tham chiếu yếu (weak reference) đến khóa bên trong map. Tham chiếu yếu có nghĩa là nếu đối tượng bị phá hủy, bộ thu gom rác (garbage collector) sẽ xóa toàn bộ entry khỏi WeakMap, do đó sẽ giải phóng bộ nhớ.
C137:
Giải thích về Hoisting trong Javascript?
Xem đáp án
Hoisting là một hành vi mặc định trong Javascript, nó sẽ chuyển tất cả khai báo biến và hàm lên trên cùng.
Điều này có nghĩa là bất kể hàm và biến được khai báo ở đâu, chúng cũng sẽ đuọc chuyển lên đầu scope. Scope có thể là toàn cục hoặc cục bộ.
Ví dụ 1:
hoistedVariable = 3;
console.log(hoistedVariable);
// output là 3 vì biến được khởi tạo trước khi khai báo.
var hoistedVariable;
Ví dụ 2:
hoistedFunction();
// Outputs " Hello world! " kể cả khi hàm được khai báo sau khi gọi.
function hoistedFunction() {
console.log(" Hello world! ");
}
Ví dụ 3:
// Hoisting takes place in the local scope as well
function doSomething() {
x = 33;
console.log(x);
var x;
}
Lưu ý: Khai báo biến được hoisting chứ phép gán biến thì không.
var x;
console.log(x); // Output sẽ trả về "undefined" vì phép gán không được hoisting
x = 23;
Lưu ý: Để tránh hoisting bạn có thể dùng “use strict”
"use strict";
x = 23; // Báo lỗi x chưa được khai báo
var x;