Sự khác nhau giữa null và undefined trong Javascript?
Sự khác nhau giữa null và undefined trong Javascript?
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.
So sánh == và === trong Javascript?
Trong JavaScript, việc hiểu rõ sự khác biệt giữa toán tử so sánh == (so sánh trừu tượng) và === (so sánh nghiêm ngặt) là rất quan trọng, vì nó ảnh hưởng đến cách giá trị được so sánh trong code của bạn.
==)Toán tử == so sánh hai giá trị sau khi thực hiện chuyển đổi kiểu dữ liệu (nếu cần), để chúng có thể được so sánh một cách công bằng. Nếu hai giá trị không cùng kiểu, JavaScript sẽ cố gắng chuyển đổi chúng sang một kiểu chung trước khi so sánh. Ví dụ, khi so sánh một số với một chuỗi, chuỗi sẽ được chuyển đổi thành một số trước khi thực hiện so sánh
===)Ngược lại, toán tử === thực hiện so sánh nghiêm ngặt, nghĩa là nó sẽ không thực hiện chuyển đổi kiểu dữ liệu giữa hai giá trị được so sánh. Điều này có nghĩa là nếu hai giá trị có kiểu dữ liệu khác nhau, toán tử === sẽ ngay lập tức trả về false mà không cần xem xét giá trị cụ thể của chúng
Sử dụng ==:
console.log(3 == "3"); // true, vì "3" được chuyển đổi thành số trước khi so sánh
console.log(true == '1'); // true, vì '1' được chuyển đổi thành true
Sử dụng ===:
console.log(3 === "3"); // false, vì một bên là số và một bên là chuỗi
console.log(true === '1'); // false, vì một bên là boolean và một bên là chuỗi
Nên sử dụng === (so sánh nghiêm ngặt) thay vì == (so sánh trừu tượng) trong hầu hết các trường hợp để tránh những kết quả không mong muốn do chuyển đổi kiểu dữ liệu tự động. Sử dụng === giúp code của bạn trở nên dễ đọc và dễ hiểu hơn, đồng thời giảm thiểu nguy cơ gặp phải lỗi logic do chuyển đổi kiểu dữ liệu không rõ ràng
Tóm lại, == và === đều là toán tử so sánh trong JavaScript nhưng hoạt động theo hai cách khác nhau. == sẽ chuyển đổi kiểu dữ liệu trước khi so sánh, trong khi === yêu cầu cả kiểu dữ liệu và giá trị phải giống nhau. Để viết code chính xác và dễ bảo trì, bạn nên sử dụng === trừ khi có một lý do chính đáng để sử dụng ==.
Sự khác biệt của biến dùng var, let và const trong javascript là gì?
Trong JavaScript, việc khai báo biến có thể sử dụng ba từ khóa là var, let, và const, mỗi từ khóa có những đặc điểm và cách sử dụng riêng biệt. Dưới đây là sự khác biệt chính giữa chúng:
var là function scope nếu nó được khai báo bên trong một hàm. Nếu khai báo ngoài hàm, var sẽ có phạm vi toàn cục (global scope){}) mà chúng được khai báovar được hoisted (nâng lên đầu phạm vi của chúng) và khởi tạo với giá trị undefined trước khi mã được thực thiReferenceError nếu truy cập trước khi khai báoconst yêu cầu phải được khởi tạo giá trịvar ít được khuyến khích sử dụng trong các dự án JavaScript hiện đạiTóm lại, let và const được giới thiệu trong ES6 như là những cải tiến so với var, với mục đích giải quyết các vấn đề liên quan đến phạm vi và hoisting của var. Sử dụng let và const giúp làm cho mã JavaScript trở nên dễ đọc và dễ bảo trì hơn.
Javascript có các kiểu dữ liệu nào? Bạn biết gì về chúng
Trong JavaScript, có tổng cộng 8 kiểu dữ liệu cơ bản, bao gồm cả kiểu dữ liệu nguyên thủy (primitive types) và kiểu dữ liệu phức tạp (complex type). Dưới đây là danh sách và mô tả ngắn gọn về từng kiểu:
"Hello, world!".42, 3.14.true) hoặc sai (false).null, dùng để biểu diễn "không có giá trị".undefined.NumberMỗi kiểu dữ liệu trong JavaScript có những đặc điểm và cách sử dụng riêng, phù hợp với các tình huống cụ thể trong lập trình. Điều quan trọng là lựa chọn đúng kiểu dữ liệu để biểu diễn dữ liệu một cách chính xác và hiệu quả nhất.
Hàm anonymous là gì và khi nào nên sử dụng?
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 example.com");
}
// 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 example.com");
};
// gọi sau hàm
showDomain(); // hoạt động vì hàm đã tồn tại
Nếu hàm cần được truyền ở nhiều nơi:
Nếu hàm chỉ truyền 1 nơi?
Trong Javascript, đây có phải là một pure function không?
function sum(a, b) {
return a + b;
}
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.
Strict mode trong JavaScript là gì?
Strict mode trong JavaScript là một tính năng cho phép bạn đặt một chương trình hoặc một hàm vào một "chế độ nghiêm ngặt" (strict operating context). Khi được kích hoạt, nó giúp phát hiện ra các lỗi tiềm ẩn, ngăn chặn hoặc báo lỗi cho một số hành vi không an toàn của JavaScript, và cải thiện hiệu suất thực thi mã. Để sử dụng strict mode, bạn chỉ cần thêm chuỗi "use strict"; vào đầu file JavaScript hoặc đầu một hàm
delete: Không cho phép sử dụng delete với một biến, một hàm, hoặc một đối số hàmwith: Cấm sử dụng câu lệnh with, giúp cải thiện hiệu suất mã và ngăn chặn các vấn đề về phạm vi biếnthis trong hàm không được tự động gán cho global object: Trong strict mode, giá trị this trong các hàm không được gọi trong một context đối tượng sẽ là undefined, giúp ngăn chặn việc thay đổi không mong muốn đối với global object.eval, arguments không thể được sử dụng làm tên biến hoặc tên hàm.Để kích hoạt strict mode, bạn thêm chuỗi "use strict"; vào đầu file JavaScript hoặc đầu một hàm. Điều này áp dụng strict mode cho toàn bộ script hoặc chỉ cho hàm đó
"use strict";
function myFunction() {
// Code ở đây sẽ chạy trong strict mode
}
Hoặc:
function myFunction() {
"use strict";
// Chỉ code trong hàm này chạy trong strict mode
}
Strict mode là một công cụ hữu ích trong JavaScript, giúp làm cho mã của bạn trở nên an toàn và dễ bảo trì hơn bằng cách cung cấp một lớp kiểm tra và bảo vệ thêm. Nó khuyến khích các thực hành lập trình tốt và giúp phát hiện lỗi sớm trong quá trình phát triển.
Array trong Javascript là gì?
Trong JavaScript, một Array (mảng) là một kiểu dữ liệu tham chiếu (reference type) được sử dụng để lưu trữ nhiều giá trị trong một biến duy nhất. Mỗi giá trị trong mảng được gọi là một phần tử (element), và mỗi phần tử có một chỉ số (index) bắt đầu từ 0.
Có hai cách chính để khai báo một mảng trong JavaScript:
Sử dụng Array literals, được biểu diễn bằng dấu ngoặc vuông []:
var foo = [];
var numbers = [1, 2, 3, 4, 5];
Sử dụng Array constructor, được khởi tạo bằng từ khóa new:
var foo = new Array();
var numbers = new Array(1, 2, 3, 4, 5);
length: Thuộc tính trả về độ dài của mảngpush(): Phương thức thêm một hoặc nhiều phần tử vào cuối mảng và trả về độ dài mới của mảngpop(): Phương thức xóa phần tử cuối cùng của mảng và trả về phần tử đã xóashift(): Phương thức xóa phần tử đầu tiên của mảng và trả về phần tử đóunshift(): Phương thức thêm một hoặc nhiều phần tử vào đầu mảng và trả về độ dài mới của mảngmap(), filter(), find(), reduce(): Các phương thức mạnh mẽ để tìm kiếm, biến đổi và xử lý dữ liệu trong mảngincludes(): Phương thức kiểm tra sự tồn tại của một phần tử trong mảng và trả về true hoặc falseindexOf(): Phương thức trả về chỉ mục đầu tiên tìm thấy của phần tử trong mảng, hoặc -1 nếu không tìm thấyjoin(): Phương thức nối các phần tử của mảng thành một chuỗi, với một ký tự nối (nếu được cung cấp)typeof sẽ trả về objectfor, forEach, và for...of để duyệt qua các phần tửMảng là một công cụ linh hoạt và mạnh mẽ trong JavaScript, cho phép lập trình viên lưu trữ và quản lý tập hợp dữ liệu một cách hiệu quả.
Kết quả đoạn result của đoạn code sau là gì? Hãy giải thích tại sao?
function addToList(item, list) {
return list.push(item);
}
const result = addToList("apple", ["banana"]);
console.log(result);
['apple', 'banana']2trueundefinedHà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 đó.
Kết quả của hàm sau là gì? Hãy giải thích tại sao?
function sayHi() {
console.log(name);
console.log(age);
var name = "Lydia";
let age = 21;
}
sayHi();
Lydia và undefinedLydia và ReferenceErrorReferenceError và 21undefined và ReferenceErrorTrong 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.
Kết quả đoạn code javascript sau là true hay false? Hãy giải thích tại sao?
console.log("❤️" === "❤️");
truefalseVề 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.
Ép kiểu ngầm (implicit type coercion) trong JavaScript là gì?
Ép kiểu ngầm (implicit type coercion) trong JavaScript là quá trình tự động hoặc ngầm định chuyển đổi giá trị từ một kiểu dữ liệu này sang kiểu dữ liệu khác, như từ chuỗi sang số[1]. Điều này thường xảy ra khi bạn áp dụng các toán tử cho các giá trị có kiểu khác nhau hoặc khi giá trị được sử dụng trong một ngữ cảnh cụ thể mà ở đó kiểu dữ liệu cần phải được chuyển đổi
Ví dụ về ép kiểu ngầm:
const value1 = "5";
const value2 = 9;
let sum = value1 + value2;
console.log(sum); // Kết quả là "59"
Trong ví dụ trên, JavaScript đã tự động chuyển đổi số 9 thành một chuỗi và sau đó nối hai giá trị lại với nhau, kết quả là một chuỗi "59". JavaScript đã có thể chọn chuyển đổi chuỗi "5" thành một số và trả về tổng là 14, nhưng nó không làm vậy. Để có kết quả này, bạn cần phải chuyển đổi rõ ràng "5" thành một số bằng cách sử dụng phương thức Number()
Có ba loại chuyển đổi trong JavaScript:
Chuyển đổi cho các giá trị nguyên thủy và đối tượng hoạt động khác nhau. Ví dụ, chuyển đổi một đối tượng thành một giá trị nguyên thủy sẽ diễn ra thông qua phương thức nội bộ [[ToPrimitive]] của đối tượng đó
Một số quy tắc chuyển đổi ngầm định:
+ với một số và một chuỗi, số sẽ được chuyển đổi thành chuỗi và sau đó thực hiện nối chuỗi||, &&, !), giá trị sẽ được chuyển đổi ngầm định sang boolean== thực hiện cả so sánh và ép kiểu ngầm định nếu cần thiết, trong khi toán tử bằng chặt === không thực hiện ép kiểu và chỉ trả về true nếu cả kiểu và giá trị của hai biến là giống nhauÉp kiểu ngầm có thể hữu ích nhưng cũng có thể gây ra những lỗi không mong muốn, đặc biệt là khi so sánh các giá trị với toán tử bằng lỏng lẻo. Do đó, việc sử dụng toán tử bằng chặt được khuyến nghị để tránh những hành vi không mong muốn này
Kết quả đoạn code sau là gì? Và hãy giải thích?
const numbers = [1, 2, 3, 4, 5];
const [y] = numbers;
console.log(y);
[[1, 2, 3, 4, 5]][1, 2, 3, 4, 5]1[1]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.
Bạn biết gì về AMD (Asynchronous Module Definition) và CommonJS trong Javascript?
AMD là một quy chuẩn cho ngôn ngữ lập trình JavaScript, định nghĩa một API cho việc định nghĩa các module mã và các phụ thuộc của chúng, và tải chúng một cách bất đồng bộ nếu muốn. Các triển khai AMD mang lại nhiều lợi ích như cải thiện hiệu suất trang web, giảm lỗi trang do cho phép các nhà phát triển định nghĩa các phụ thuộc cần tải trước khi một module được thực thi. Ngoài ra, AMD cũng cung cấp một số khả năng tương thích với CommonJS, cho phép sử dụng một giao diện exports và require() tương tự trong mã, mặc dù giao diện define() của riêng nó được ưu tiên hơn
CommonJS là cách đóng gói mã JavaScript ban đầu cho Node.js. Node.js cũng hỗ trợ tiêu chuẩn ECMAScript modules được sử dụng bởi trình duyệt và các môi trường JavaScript khác. Trong Node.js, mỗi file được xem như một module riêng biệt. Ví dụ, một file tên là foo.js có thể tải module circle.js nằm trong cùng thư mục với foo.js. CommonJS chủ yếu được sử dụng trong các ứng dụng JS phía server với Node, và mỗi module được xuất thông qua việc gán giá trị mới cho thuộc tính module.exports
Cả hai hệ thống module này đều có ưu và nhược điểm riêng, tùy thuộc vào môi trường và yêu cầu cụ thể của ứng dụng. Trong khi AMD tối ưu cho việc tải bất đồng bộ và phù hợp với các ứng dụng web lớn, CommonJS lại phù hợp với các ứng dụng Node.js phía server với việc tải đồng bộ các module.
Mặc dù có sự khác biệt, cả hai hệ thống này đều hướng tới mục tiêu chung là cung cấp một cách tổ chức và quản lý mã JavaScript một cách hiệu quả, giúp phát triển các ứng dụng lớn và phức tạp trở nên dễ dàng hơn.
Kết quả set của đoạn code sau là gì? Hãy giải thích tại sao?
const set = new Set([1, 1, 2, 3, 4]);
console.log(set);
[1, 1, 2, 3, 4][1, 2, 3, 4]{1, 1, 2, 3, 4}{1, 2, 3, 4}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}.
Trong Javascript, Anonymous Function thường dùng cho trường hợp nào?
Hàm anonymous, hay còn gọi là hàm ẩn danh, là một loại hàm không có tên trong JavaScript. Hàm này có thể được sử dụng như một đối số cho các hàm khác, hoặc được gán vào một biến để sử dụng. Hàm anonymous thường được tạo ra và sử dụng ngay tại thời điểm chạy của chương trình
function mà không cần đặt tên cho hàm// Gán hàm anonymous vào biến
var showMsg = function() {
console.log("Hello, this is an anonymous function!");
};
showMsg();
// Sử dụng hàm anonymous làm callback
setTimeout(function() {
console.log("This message is shown after 1 second.");
}, 1000);
// IIFE
(function() {
console.log("This function runs immediately upon definition.");
})();
Hàm anonymous là một công cụ linh hoạt và mạnh mẽ trong JavaScript, giúp lập trình viên có thể viết code một cách gọn gàng và hiệu quả, đặc biệt trong việc xử lý các tác vụ đơn lẻ hoặc tạo ra các IIFE.
Ưu điểm và nhược điểm của việc sử dụng use strict là gì?
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:
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ì).this là undefined trong global context.Nhược điểm:
Nhìn chung, tôi nghĩ rằng "use strict" có lợi ích nhiều hơn bất lợi.
Lợi ích của việc sử dụng spread so với rest như thế nào trong Javascript?
Trong JavaScript ES6, cả spread và rest sử dụng cú pháp ba dấu chấm (...), nhưng chúng phục vụ cho các mục đích khác nhau và mang lại những lợi ích riêng biệt.
Object.assign() hoặc Array.slice(), giúp code ngắn gọn và dễ đọc hơnconcat()apply()Set hoặc Map thành mảngarguments kiểu Array-like, giúp code dễ đọc và dễ hiểu hơnTóm lại, spread operator thường được sử dụng để "trải" các giá trị ra, trong khi rest parameters được sử dụng để "nhóm" các giá trị lại. Spread operator mang lại lợi ích trong việc tạo bản sao, kết hợp mảng, và truyền đối số, còn rest parameters giúp xử lý số lượng đối số không xác định trong hàm. Cả hai đều là những cải tiến quan trọng trong ES6, giúp lập trình viên viết code ngắn gọn, hiệu quả và dễ bảo trì hơn.
Lập trình bất đồng bộ trong Javascript là gì?
Lập trình bất đồng bộ trong JavaScript là một mô hình lập trình cho phép việc xử lý các tác vụ mà không cần chờ đợi kết quả của tác vụ trước đó hoàn thành. Điều này giúp cải thiện hiệu suất và thời gian phản hồi của ứng dụng, đặc biệt là khi thực hiện các tác vụ nặng như truy cập mạng hoặc I/O
JavaScript là một ngôn ngữ đơn luồng (single-threaded), nghĩa là nó chỉ có thể xử lý một câu lệnh tại một thời điểm[8]. Để khắc phục vấn đề blocking khi thực hiện các tác vụ nặng, JavaScript sử dụng các WebAPIs do trình duyệt cung cấp để thực thi các tác vụ này một cách bất đồng bộ
Các phương thức xử lý bất đồng bộ phổ biến trong JavaScript bao gồm:
async được khai báo trước một hàm để biểu thị rằng hàm đó sẽ trả về một Promise, và await được sử dụng để chờ đợi một Promise hoàn thànhBất đồng bộ giúp giải quyết vấn đề blocking của đồng bộ, được gọi là non-blocking, nghĩa là khi chạy một tác vụ nặng, những lệnh tiếp theo được phép chạy ngay mà không cần chờ tác vụ kia hoàn thành. Tuy nhiên, lập trình bất đồng bộ cũng đặt ra thách thức trong việc xử lý lỗi và quản lý tài nguyên dùng chung
Trong thực tế, việc sử dụng lập trình bất đồng bộ trong JavaScript là rất phổ biến, đặc biệt là trong các ứng dụng web hiện đại, nơi mà việc tối ưu hiệu suất và trải nghiệm người dùng là rất quan trọng
Javascript là ngôn ngữ pass-by-reference hay pass-by-value không?
Trong JavaScript, việc truyền tham số vào hàm tuân theo nguyên tắc "pass-by-value" (truyền theo giá trị). Tuy nhiên, cách thức hoạt động của nguyên tắc này phụ thuộc vào loại dữ liệu của biến được truyền vào hàm: kiểu dữ liệu nguyên thủy (primitive types) hay kiểu dữ liệu tham chiếu (reference types).
Các kiểu dữ liệu nguyên thủy bao gồm số (number), chuỗi (string), boolean, null, undefined, symbol (ES6), và bigint (ES2020). Khi một biến kiểu nguyên thủy được truyền vào hàm, JavaScript sẽ tạo một bản sao của giá trị đó và truyền bản sao vào hàm. Bất kỳ thay đổi nào đối với tham số trong hàm sẽ không ảnh hưởng đến biến bên ngoài hàm, vì thực chất giá trị được truyền vào hàm là một bản sao
Các kiểu dữ liệu tham chiếu bao gồm các đối tượng (objects), mảng (arrays), và hàm (functions). Khi một biến kiểu tham chiếu được truyền vào hàm, JavaScript sẽ truyền địa chỉ tham chiếu của biến đó vào hàm, chứ không phải là một bản sao của giá trị. Do đó, bất kỳ thay đổi nào đối với thuộc tính hoặc phần tử của đối tượng hoặc mảng trong hàm sẽ ảnh hưởng trực tiếp đến biến bên ngoài hàm. Tuy nhiên, nếu bạn thay đổi tham chiếu của biến trong hàm (ví dụ, gán biến đó vào một đối tượng mới), thì tham chiếu bên ngoài hàm sẽ không bị thay đổi
Điều quan trọng cần lưu ý là mặc dù các kiểu dữ liệu tham chiếu được truyền theo địa chỉ tham chiếu, nhưng JavaScript vẫn tuân theo nguyên tắc "pass-by-value" vì địa chỉ tham chiếu đó được truyền dưới dạng một giá trị. Điều này có nghĩa là JavaScript luôn truyền tham số dưới dạng giá trị, nhưng đối với kiểu dữ liệu tham chiếu, giá trị đó là địa chỉ tham chiếu của đối tượng
Tóm lại, JavaScript là một ngôn ngữ "pass-by-value", nhưng cách thức hoạt động của nguyên tắc này khác nhau giữa kiểu dữ liệu nguyên thủy và kiểu dữ liệu tham chiếu.
Kết quả đoạn code javascript sau là gì và giải thích?
function getInfo(member, year) {
member.name = "Lydia";
year = "1998";
}
const person = { name: "Sarah" };
const birthYear = "1997";
getInfo(person, birthYear);
console.log(person, birthYear);
{ name: "Lydia" }, "1997"{ name: "Sarah" }, "1998"{ name: "Lydia" }, "1998"{ name: "Sarah" }, "1997"Đố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".
Sự khác biệt giữa các Host objects và Native objects trong Javascript là gì?
Sự khác biệt giữa các Host objects và Native objects trong JavaScript chủ yếu nằm ở nguồn gốc và mục đích sử dụng của chúng.
Native objects (Đối tượng nguyên thuỷ) là các đối tượng được định nghĩa bởi đặc tả ECMAScript của ngôn ngữ JavaScript. Chúng là một phần không thể thiếu của ngôn ngữ và được cung cấp sẵn bởi môi trường thực thi JavaScript. Native objects bao gồm các đối tượng như String, Math, RegExp, Object, Function, và nhiều đối tượng khác. Chúng được gọi là built-in objects, pre-defined objects, hoặc global objects và có sẵn cho tất cả người dùng, bất kể máy tính hay môi trường thực thi. Các chức năng của native objects không thay đổi khi thay đổi máy tính hoặc môi trường và có thể được sử dụng như cả constructors (ví dụ: String(), Array(), Object()) và giá trị nguyên thủy (ví dụ: true, 999)
Host objects (Đối tượng máy chủ) là các đối tượng được cung cấp bởi môi trường thời gian chạy, chẳng hạn như trình duyệt web hoặc Node.js. Chúng không được định nghĩa bởi đặc tả ECMAScript mà được cung cấp bởi môi trường thời gian chạy và có thể khác nhau giữa các môi trường. Ví dụ, trong môi trường trình duyệt, các host objects có thể bao gồm window, XMLHttpRequest, và các đối tượng DOM như NodeList. Trong khi đó, một số đối tượng như console được cung cấp bởi cả trình duyệt và máy chủ Node.js. Host objects có thể khác nhau giữa các môi trường và là đặc thù cho từng host (môi trường)
Sự hiểu biết về sự khác biệt giữa native objects và host objects giúp lập trình viên JavaScript hiểu rõ hơn về cách các đối tượng hoạt động trong các môi trường khác nhau và cách tận dụng chúng một cách hiệu quả trong các ứng dụng của mình
Kết quả đoạn code sau là gì? Và giải thích?
// counter.js
let counter = 10;
export default counter;
// index.js
import myCounter from "./counter";
myCounter += 1;
console.log(myCounter);
1011ErrorNaNMộ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.
Hãy cho biết kết quả đoạn code sau?
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius,
};
shape.diameter();
shape.perimeter();
20 and 62.8318530717958620 and NaN20 and 63NaN and 63Chú ý 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.
Callback hell trong Javascript là gì?
Callback hell trong JavaScript, còn được gọi là "pyramid of doom" hoặc "hadouken", là một tình huống phổ biến trong lập trình bất đồng bộ với JavaScript, nơi mà các hàm callback được lồng vào nhau sâu và rộng, dẫn đến mã nguồn khó đọc, khó bảo trì và dễ gây lỗi. Điều này thường xảy ra khi bạn có nhiều thao tác bất đồng bộ cần phải được thực hiện theo một trình tự nhất định và kết quả của mỗi thao tác phụ thuộc vào thao tác trước đó.
Ví dụ về callback hell:
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
// ... thêm nhiều lớp lồng nhau khác ...
});
}
});
Trong ví dụ trên, bạn có thể thấy rằng các hàm callback được lồng vào nhau nhiều lần, tạo ra một "hình kim tự tháp" của mã nguồn, và nhiều dấu }) ở cuối, làm cho mã nguồn trở nên rối rắm và khó theo dõi.
Để tránh callback hell, có một số phương pháp và kỹ thuật được khuyến nghị:
async.js giúp quản lý và kiểm soát luồng thực thi của các hàm bất đồng bộTránh callback hell không chỉ giúp cải thiện chất lượng mã nguồn mà còn giúp đảm bảo rằng ứng dụng của bạn có thể mở rộng và bảo trì dễ dàng hơn.
Làm thế nào để so sánh hai object trong JavaScript?
So sánh hai đối tượng trong JavaScript không giống như so sánh các kiểu dữ liệu nguyên thủy (primitive types) như số hoặc chuỗi. Đối với các đối tượng, việc so sánh chúng dựa trên tham chiếu (reference) chứ không phải giá trị. Điều này có nghĩa là ngay cả khi hai đối tượng có cùng cấu trúc và giá trị, chúng vẫn không bằng nhau nếu chúng không tham chiếu đến cùng một vị trí trong bộ nhớ
Khi sử dụng toán tử === để so sánh hai đối tượng, JavaScript sẽ kiểm tra xem cả hai có trỏ đến cùng một đối tượng trong bộ nhớ hay không. Nếu hai biến tham chiếu đến cùng một đối tượng, kết quả sẽ là true; nếu không, kết quả sẽ là false
Để so sánh hai đối tượng dựa trên giá trị của chúng, bạn cần sử dụng các phương pháp khác. Dưới đây là một số cách phổ biến:
Sử dụng JSON.stringify(): Phương pháp này chuyển đổi đối tượng thành chuỗi JSON và sau đó so sánh hai chuỗi này. Tuy nhiên, phương pháp này có hạn chế là thứ tự của các thuộc tính trong đối tượng phải giống nhau và không xử lý được các đối tượng có tham chiếu tuần hoàn
let a = { name: 'Dionysia', age: 29 };
let b = { name: 'Dionysia', age: 29 };
console.log(JSON.stringify(a) === JSON.stringify(b)); // true hoặc false tùy thuộc vào thứ tự của thuộc tính
Sử dụng thư viện Lodash: Phương thức _.isEqual() của Lodash thực hiện so sánh sâu (deep comparison) giữa hai đối tượng, bao gồm cả các đối tượng lồng nhau và không quan tâm đến thứ tự của các thuộc tính
let a = { age: 29, name: 'Dionysia' };
let b = { name: 'Dionysia', age: 29 };
console.log(_.isEqual(a, b)); // true
So sánh sâu tự viết: Bạn có thể tự viết hàm để so sánh sâu giữa hai đối tượng, bằng cách đệ quy kiểm tra từng thuộc tính và giá trị của chúng
function isDeepEqual(object1, object2) {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
const val1 = object1[key];
const val2 = object2[key];
const areObjects = isObject(val1) && isObject(val2);
if (
(areObjects && !isDeepEqual(val1, val2)) ||
(!areObjects && val1 !== val2)
) {
return false;
}
}
return true;
}
Khi so sánh hai đối tượng trong JavaScript, bạn không thể sử dụng toán tử == hoặc === vì chúng chỉ so sánh tham chiếu chứ không phải giá trị. Để so sánh giá trị của hai đối tượng, bạn có thể sử dụng JSON.stringify() cho các trường hợp đơn giản hoặc _.isEqual() từ thư viện Lodash cho một giải pháp mạnh mẽ và đáng tin cậy hơn. Nếu bạn cần một giải pháp tùy chỉnh, việc viết một hàm so sánh sâu tự viết có thể là lựa chọn phù hợp
So sánh sự khác nhau của forEach() và map() trong Javascript?
forEach() và map() là hai phương thức phổ biến trong JavaScript được sử dụng để lặp qua các phần tử của mảng, nhưng chúng có những sự khác biệt quan trọng về cách thức hoạt động và mục đích sử dụng.
Giá Trị Trả Về:
forEach(): Không trả về giá trị nào (undefined). Phương thức này thường được sử dụng để thực hiện một hành động nào đó cho mỗi phần tử của mảng mà không cần tạo ra một mảng mớimap(): Trả về một mảng mới, trong đó mỗi phần tử là kết quả của việc áp dụng một hàm đã cho lên từng phần tử của mảng ban đầu. map() thích hợp khi bạn muốn "biến đổi" các phần tử của mảng và tạo ra một mảng mới mà không thay đổi mảng gốcKhả Năng Chaining (Liên Kết Các Phương Thức):
forEach(): Không thể chain (liên kết) với các phương thức khác vì nó trả về undefinedmap(): Có thể được chain với các phương thức khác như reduce(), sort(), filter() vì nó trả về một mảng mới, cho phép thực hiện các thao tác tiếp theo trên mảng đóTác Động Đến Mảng Ban Đầu:
forEach(): Có thể thay đổi mảng ban đầu thông qua các hành động được thực hiện trong callback. Tuy nhiên, việc này không phải là mục đích chính của forEach() và thường được tránh để duy trì tính bất biến của dữ liệumap(): Không thay đổi mảng ban đầu. Mọi thay đổi đều được phản ánh trong mảng mới được trả về, giữ nguyên tính bất biến của mảng gốcforEach() khi bạn cần thực hiện một hành động cho mỗi phần tử của mảng mà không cần tạo ra một mảng mới hoặc khi bạn không quan tâm đến kết quả trả về từ việc thực hiện hành động đómap() khi bạn muốn tạo ra một mảng mới từ việc biến đổi mỗi phần tử của mảng ban đầu, ví dụ như tăng giá trị của mỗi phần tử lên 1, hoặc chuyển đổi dữ liệu từ dạng này sang dạng khácTóm lại, lựa chọn giữa forEach() và map() phụ thuộc vào mục đích sử dụng: nếu bạn cần một mảng mới từ việc biến đổi các phần tử, hãy sử dụng map(). Nếu bạn chỉ muốn thực hiện một hành động nào đó mà không cần mảng mới, forEach() sẽ là lựa chọn phù hợp.
Hãy 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?
Để 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, bạn có thể thực hiện như sau:
function lucky_sevens(arr) {
// Kiểm tra nếu mảng có ít hơn 3 phần tử thì không thể có 3 phần tử liên tiếp có tổng bằng 7
if (arr.length < 3) return false;
// Duyệt qua mảng, bắt đầu từ chỉ số 0 đến chỉ số length - 3
for (let i = 0; i < arr.length - 2; i++) {
// Tính tổng của ba phần tử liên tiếp
let sum = arr[i] + arr[i + 1] + arr[i + 2];
// Nếu tổng bằng 7, trả về true
if (sum === 7) return true;
}
// Nếu không tìm thấy ba phần tử liên tiếp nào có tổng bằng 7, trả về false
return false;
}
Giải thích:
lucky_sevens nhận vào một mảng arr.false.for để duyệt qua mảng từ phần tử đầu tiên đến phần tử áp chót thứ hai (vì ta cần ba phần tử để tính tổng).i.true.false.Lưu ý: Mã giả định rằng mảng đầu vào chỉ chứa các số nguyên.
Trong Javascript, sự khác biệt giữa throw Error('msg') so với throw new Error('msg') là gì?
Trong JavaScript, không có sự khác biệt đáng kể giữa việc sử dụng throw Error('msg') và throw new Error('msg'). Cả hai cách này đều tạo ra một instance mới của đối tượng Error và ném (throw) nó như một ngoại lệ. Điều này được xác nhận bởi việc gọi hàm Error với hoặc không có từ khóa new đều tạo ra một instance mới của Error
Khi bạn sử dụng throw để ném một ngoại lệ, bạn có thể ném bất kỳ loại giá trị nào, không chỉ là các đối tượng Error. Tuy nhiên, thực hành tốt nhất là ném một instance của Error hoặc một lớp kế thừa từ Error, vì điều này cung cấp thông tin lỗi chi tiết hơn và cho phép xử lý lỗi một cách hiệu quả hơn
Sử dụng Error hoặc new Error để tạo một ngoại lệ cung cấp cho bạn một số lợi ích như:
message) để giải thích nguyên nhân của lỗi.Error, như stack, để có được thông tin chi tiết về ngăn xếp gọi tại thời điểm lỗi xảy ra, giúp việc debug trở nên dễ dàng hơn.Error, bạn có thể bắt (catch) nó trong một khối catch và xử lý lỗi một cách linh hoạt.Ví dụ về việc ném và bắt một lỗi:
try {
throw new Error('Đây là một lỗi!');
} catch (e) {
console.log(e.message); // In ra: Đây là một lỗi!
}
Tóm lại, việc sử dụng throw Error('msg') và throw new Error('msg') trong JavaScript đều tạo ra một ngoại lệ mới với thông điệp lỗi được chỉ định. Không có sự khác biệt đáng kể nào giữa hai cách này về mặt kỹ thuật hoặc hành vi
Kết quả data đoạn code sau là gì? Hãy giải thích tại sao?
const settings = {
username: "lydiahallie",
level: 19,
health: 90,
};
const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);
"{"level":19, "health":90}""{"username": "lydiahallie"}""["level", "health"]""{"username": "lydiahallie", "level":19, "health":90}"Đố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.
Kết quả đoạn code sau là gì? Hãy giải thích chi tiết tại sao?
const one = false || {} || null;
const two = null || false || "";
const three = [] || 0 || true;
console.log(one, two, three);
false null []null "" true{} "" []null null trueVớ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à [].
Kết quả đoạn code javascript sau là gì? Hãy giải thích tại sao?
console.log(String.raw`Hello
world`);
Hello world!Hello worldHello worldString.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.
Bạn biết gì về load event trong Javascript?
Sự kiện load trong JavaScript là một sự kiện quan trọng, được kích hoạt khi một trang web hoàn toàn tải xong tất cả nội dung bao gồm hình ảnh, tệp JavaScript, tệp CSS, và các phụ thuộc khác. Sự kiện này thường được sử dụng trong thẻ <body> để thực thi một đoạn script ngay sau khi trang web tải xong hoàn toàn
load:load xảy ra khi đối tượng đã được tải hoàn toàn.load khác với sự kiện DOMContentLoaded, sự kiện này được kích hoạt ngay khi DOM của trang đã tải xong, không cần chờ đợi các tài nguyên khác hoàn tất việc tảiload:Bạn có thể sử dụng sự kiện load bằng cách thêm thuộc tính onload vào một phần tử HTML hoặc gán nó cho một đối tượng trong JavaScript. Dưới đây là một số cách để đăng ký sự kiện load:
<body onload="myFunction()">
window.onload = function() {
// Mã lệnh được thực thi sau khi trang tải xong
};
Hoặc sử dụng addEventListener để thêm một trình xử lý sự kiện:
window.addEventListener("load", function() {
// Mã lệnh được thực thi sau khi trang tải xong
});
load:DOMContentLoaded thay vì load<head> và các tệp JavaScript ở cuối thẻ <body>Sự kiện load là một công cụ hữu ích để đảm bảo rằng tất cả các tài nguyên cần thiết cho trang web đã sẵn sàng trước khi thực hiện các thao tác liên quan đến DOM hoặc các tài nguyên khác.
Kết quả shape của đoạn code sau là gì? Hãy giải thích tại sao?
const box = { x: 10, y: 20 };
Object.freeze(box);
const shape = box;
shape.x = 100;
console.log(shape);
{ x: 100, y: 20 }{ x: 10, y: 20 }{ x: 100 }ReferenceErrorObject.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.
Kết quả gen.next().value của đoạn code sau là gì? Hãy giải thích tại sao?
function* generator(i) {
yield i;
yield i * 2;
}
const gen = generator(10);
console.log(gen.next().value);
console.log(gen.next().value);
[0, 10], [10, 20]20, 2010, 200, 10 and 10, 20Mộ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.
Kết quả list của đoạn code sau là gì? Hãy giải thích tại sao?
const list = [1 + 2, 1 * 2, 1 / 2];
console.log(list);
["1 + 2", "1 * 2", "1 / 2"]["12", 2, 0.5][3, 2, 0.5][1, 1, 1]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.
Giải thích về bubbling event trong Javascript và làm sao để ngăn chặn nó?
Bubbling event là một khái niệm trong quá trình lan truyền sự kiện (event propagation) trong DOM của JavaScript. Khi một sự kiện xảy ra trên một phần tử, nó không chỉ được xử lý tại phần tử đó mà còn được "bong bóng" lên qua các phần tử cha của nó trong cấu trúc DOM, từ phần tử mục tiêu (target element) cho đến đối tượng cửa sổ toàn cầu (global window object)
Khi một sự kiện được kích hoạt, sau khi được xử lý tại phần tử mục tiêu, nó sẽ bắt đầu "bong bóng" lên qua các phần tử cha. Sự kiện sẽ đi qua tất cả các phần tử cha theo thứ tự lồng nhau của chúng trong giai đoạn "bubbling". Trong quá trình này, bất kỳ trình xử lý sự kiện (event handler) nào được đăng ký trên các phần tử cha cũng sẽ được thực thi
Để ngăn chặn sự kiện tiếp tục "bong bóng" lên các phần tử cha, bạn có thể sử dụng phương thức stopPropagation() trên đối tượng sự kiện. Phương thức này ngăn chặn sự lan truyền của sự kiện hiện tại trong các giai đoạn capturing và bubbling. Tuy nhiên, nó không ngăn chặn các hành vi mặc định từ việc xảy ra; ví dụ, các click trên liên kết vẫn được xử lý. Nếu bạn muốn ngăn chặn cả hành vi mặc định, bạn cần sử dụng phương thức preventDefault()
Dưới đây là một ví dụ về cách ngăn chặn bubbling event:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Event Bubbling Example</title>
</head>
<body>
<div id="parent">
<button id="child">Click me</button>
</div>
<script>
// Trình xử lý sự kiện cho phần tử cha
document.getElementById("parent").addEventListener("click", function() {
alert("Parent element clicked!");
});
// Trình xử lý sự kiện cho phần tử con và ngăn chặn bubbling
document.getElementById("child").addEventListener("click", function(event) {
alert("Child element clicked!");
event.stopPropagation(); // Ngăn chặn sự kiện "bong bóng" lên phần tử cha
});
</script>
</body>
</html>
Trong ví dụ trên, khi bạn click vào nút có id là "child", chỉ có thông báo "Child element clicked!" được hiển thị và sự kiện không "bong bóng" lên phần tử cha, do đó thông báo "Parent element clicked!" sẽ không được hiển thị.
Tóm lại, bubbling event là một phần quan trọng của việc xử lý sự kiện trong JavaScript, cho phép các sự kiện được xử lý theo cấp độ phân cấp. Tuy nhiên, trong một số trường hợp, bạn có thể muốn ngăn chặn sự kiện này để tránh kích hoạt các trình xử lý sự kiện không mong muốn trên các phần tử cha, và bạn có thể làm điều này bằng cách sử dụng phương thức stopPropagation()
Đoạn code sau kết quả là gì? Hãy giải thích tại sao?
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();
I have resolved!, second và I have resolved!, secondsecond, I have resolved! và second, I have resolved!I have resolved!, second và second, I have resolved!second, I have resolved! và I have resolved!, secondCó 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.
Đoạn code javascript sau output là gì? Hãy giải thích tại sao?
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!");
}
It's not a string!Yay it's a string!TypeErrorundefinedĐ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.
Object trong Javascript là gì?
Trong JavaScript, một object (đối tượng) là một cấu trúc dữ liệu cho phép lưu trữ và quản lý dữ liệu dưới dạng cặp khóa (key) và giá trị (value). Object trong JavaScript có thể được so sánh với một thực thể độc lập, có thuộc tính và kiểu, giống như các đối tượng thực tế trong cuộc sống hàng ngày. Mỗi cặp khóa và giá trị trong một object được gọi là một property, trong đó khóa thường là một chuỗi hoặc symbol, và giá trị có thể là bất kỳ kiểu dữ liệu nào, bao gồm cả một object khác
Object trong JavaScript có thể được khai báo sử dụng cú pháp object literal, bằng cách sử dụng dấu ngoặc nhọn {} và liệt kê các property bên trong. Mỗi property được phân tách bởi dấu phẩy
const person = {
name: "John Doe",
age: 30
};
Có hai cách để truy cập vào các property của object:
objectName.propertyNameobjectName["propertyName"]Trong một object, giá trị của property có thể là một hàm, khi đó property đó được gọi là một phương thức (method) của object. Phương thức là các hàm được gắn với object và thường được sử dụng để xác định hành vi của object
const person = {
name: "John Doe",
greet: function() {
console.log("Hello, " + this.name);
}
};
person.greet(); // Output: Hello, John Doe
Object trong JavaScript có thể chứa các object khác, tạo thành cấu trúc dữ liệu phức tạp với nhiều cấp độ lồng nhau
const person = {
name: "John Doe",
address: {
street: "123 Main St",
city: "Anytown"
}
};
console.log(person.address.city); // Output: Anytown
Object trong JavaScript rất linh hoạt và cho phép thêm hoặc xóa các property sau khi object đã được tạo. Điều này giúp JavaScript trở thành một ngôn ngữ lập trình mạnh mẽ và linh hoạt, phù hợp với nhiều loại ứng dụng khác nhau
Object trong JavaScript là một khái niệm cốt lõi, giúp lập trình viên có thể lưu trữ và quản lý dữ liệu một cách hiệu quả. Sự linh hoạt và khả năng lưu trữ dữ liệu phức tạp làm cho object trở thành một công cụ không thể thiếu trong việc phát triển ứng dụng web.
Coercion trong JavaScript là gì?
Coercion trong JavaScript là quá trình chuyển đổi tự động hoặc ngầm định giá trị từ một kiểu dữ liệu này sang kiểu dữ liệu khác, như từ chuỗi sang số. Coercion có thể xảy ra khi bạn áp dụng các toán tử cho các giá trị có kiểu khác nhau hoặc khi giá trị được sử dụng trong một ngữ cảnh cụ thể mà ở đó kiểu dữ liệu cần phải được chuyển đổi
Coercion có thể là rõ ràng (explicit) hoặc ngầm định (implicit). Coercion rõ ràng xảy ra khi lập trình viên biểu thị ý định chuyển đổi giữa các kiểu bằng cách viết mã phù hợp, như sử dụng hàm Number(value) để chuyển một giá trị thành số. Trong khi đó, coercion ngầm định xảy ra khi JavaScript tự động chuyển đổi giữa các kiểu khác nhau mà không cần sự can thiệp của lập trình viên
Ví dụ về coercion ngầm định:
const value1 = "5";
const value2 = 9;
let sum = value1 + value2;
console.log(sum); // Kết quả là "59"
Trong ví dụ trên, JavaScript đã tự động chuyển đổi số 9 thành một chuỗi và sau đó nối hai giá trị lại với nhau, kết quả là một chuỗi "59". Để có kết quả là tổng số, bạn cần phải chuyển đổi rõ ràng "5" thành một số bằng cách sử dụng phương thức Number()
Coercion ngầm định có thể hữu ích nhưng cũng có thể gây ra những lỗi không mong muốn, đặc biệt là khi so sánh các giá trị với toán tử bằng lỏng lẻo ==. Do đó, việc sử dụng toán tử bằng chặt === được khuyến nghị để tránh những hành vi không mong muốn này
Kết quả đoạn code javascript sau là gì? Hãy giải thích?
function Car() {
this.make = "Lamborghini";
return { make: "Maserati" };
}
const myCar = new Car();
console.log(myCar.make);
"Lamborghini""Maserati"ReferenceErrorTypeErrorKhi 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".
Giải thích về phương thức call(), aplly() và bind() trong Javascript?
Trong JavaScript, call(), apply(), và bind() là ba phương thức quan trọng của Function, giúp chúng ta làm việc với từ khóa this và các đối số của hàm một cách linh hoạt. Mỗi phương thức có cách sử dụng và mục đích riêng.
call()Phương thức call() cho phép bạn gọi một hàm với một giá trị this được chỉ định và các đối số được truyền vào một cách rõ ràng. Cú pháp của call() là func.call(thisArg, arg1, arg2, ...), trong đó thisArg là giá trị this muốn gán, và arg1, arg2, ... là các đối số truyền vào hàm
apply()Phương thức apply() rất giống với call(), nhưng thay vì truyền các đối số một cách rõ ràng, apply() cho phép bạn truyền một mảng các đối số. Cú pháp của apply() là func.apply(thisArg, [argsArray]), với thisArg là giá trị this muốn gán, và [argsArray] là mảng chứa các đối số
bind()Phương thức bind() tạo ra một hàm mới, có giá trị this được ràng buộc với giá trị được cung cấp. Khác với call() và apply(), bind() không tự động gọi hàm mà trả về một hàm mới với context this đã được set. Cú pháp của bind() là func.bind(thisArg[, arg1[, arg2[, ...]]]), trong đó thisArg là giá trị this muốn gán, và arg1, arg2, ... là các đối số mặc định cho hàm mới
call() và apply() thường được sử dụng khi bạn muốn gọi một hàm với một giá trị this cụ thể hoặc khi bạn muốn mượn một hàm từ một đối tượng khác mà không tạo ra một phương thức mới cho đối tượng đó. Sự khác biệt chính giữa hai phương thức này là cách thức truyền đối số: call() nhận đối số dưới dạng danh sách, trong khi apply() nhận đối số dưới dạng mảngbind() thường được sử dụng khi bạn cần ràng buộc this cho một hàm để sử dụng sau này, ví dụ như trong các sự kiện hoặc khi bạn cần đảm bảo rằng this trong một hàm luôn trỏ đến đối tượng mong muốn, bất kể nó được gọi như thế nàolet person = {
fullName: function() {
return this.firstName + " " + this.lastName;
}
};
let person1 = {
firstName: "John",
lastName: "Doe"
};
// Sử dụng call()
console.log(person.fullName.call(person1)); // John Doe
// Sử dụng apply()
console.log(person.fullName.apply(person1)); // John Doe
// Sử dụng bind()
let getPersonName = person.fullName.bind(person1);
console.log(getPersonName()); // John Doe
Tóm lại, call(), apply(), và bind() là các phương thức mạnh mẽ giúp bạn kiểm soát giá trị của this trong JavaScript, mỗi phương thức có những ứng dụng và cách sử dụng riêng biệt phù hợp với nhu cầu cụ thể trong lập trình
Sử dụng method nào đượ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 }
entriesvaluesfromEntriesforEachHà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 }.
Output đoạn code javascript sau là gì? Hãy giải thích tại sao?
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();
"Woof I am Mara", TypeError"Woof I am Mara","Woof I am Mara""Woof I am Mara", undefinedTypeError, TypeErrorChú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.
Làm sao để deep-freeze một đối tượng trong JavaScript?
Để "deep-freeze" một đối tượng trong JavaScript, bạn cần sử dụng phương thức Object.freeze() kết hợp với đệ quy. Phương thức Object.freeze() ngăn chặn việc thêm mới, xóa bỏ, hoặc thay đổi các thuộc tính của đối tượng, cũng như ngăn chặn việc thay đổi prototype của đối tượng. Tuy nhiên, Object.freeze() chỉ áp dụng một "shallow freeze", nghĩa là nó chỉ đóng băng các thuộc tính trực tiếp của đối tượng và không áp dụng cho các đối tượng lồng nhau. Để đạt được một "deep freeze", bạn cần áp dụng đệ quy để đóng băng mọi đối tượng lồng nhau
Dưới đây là một ví dụ về cách thực hiện "deep freeze" một đối tượng:
function deepFreeze(object) {
// Lấy tất cả tên thuộc tính của đối tượng
const propNames = Reflect.ownKeys(object);
// Đóng băng các thuộc tính trước khi đóng băng chính đối tượng
for (const name of propNames) {
const value = object[name];
if ((value && typeof value === "object") || typeof value === "function") {
deepFreeze(value);
}
}
return Object.freeze(object);
}
const obj2 = {
internal: {
a: null,
},
};
deepFreeze(obj2);
obj2.internal.a = "anotherValue"; // Thay đổi này sẽ không có hiệu lực
Trong ví dụ trên, hàm deepFreeze sẽ đệ quy qua tất cả các thuộc tính của đối tượng. Nếu một thuộc tính là một đối tượng hoặc một hàm, hàm deepFreeze sẽ được gọi đệ quy cho thuộc tính đó trước khi đóng băng chính đối tượng. Cuối cùng, Object.freeze() được gọi để đóng băng đối tượng. Kết quả là, bạn không thể thay đổi, thêm mới, hoặc xóa bỏ bất kỳ thuộc tính nào của đối tượng, kể cả trong các đối tượng lồng nhau
Lưu ý rằng việc sử dụng "deep freeze" có thể ảnh hưởng đến hiệu suất nếu đối tượng có cấu trúc phức tạp và lồng nhau sâu. Do đó, hãy cân nhắc kỹ trước khi áp dụng cho toàn bộ đối tượng lớn hoặc phức tạp.
Con trỏ this trong javascript là gì?
Con trỏ this trong JavaScript được dùng để tham chiếu đến đối tượng mà nó thuộc về, hay nói cách khác, this đại diện cho ngữ cảnh mà đoạn mã đang được thực thi. Trong JavaScript, giá trị của this không cố định mà phụ thuộc vào cách mà hàm hoặc phương thức được gọi
thisthis tham chiếu đến đối tượng đóthis tham chiếu đến đối tượng global, tức là window trong trình duyệt và global trong Node.js. Tuy nhiên, trong strict mode, this sẽ là undefinedthis được "bắt" từ ngữ cảnh bên ngoài hàm mũi tên, tức là nó không có this riêng mà sử dụng this từ ngữ cảnh bên ngoài của nóthisconst person = {
name: "Alice",
greet: function() {
console.log("Hello, " + this.name);
}
};
person.greet(); // "Hello, Alice" - `this` tham chiếu đến đối tượng `person`
Trong trường hợp hàm được gọi không thông qua một đối tượng:
function showName() {
console.log(this.name);
}
const user = { name: "Bob" };
window.name = "GlobalName";
showName(); // "GlobalName" - `this` tham chiếu đến `window` trong trình duyệt
this trong các trường hợp đặc biệtthis trong một hàm callback hoặc một hàm được truyền như một tham số, giá trị của this có thể không phải là như mong đợi. Trong trường hợp này, các phương thức như bind, call, và apply có thể được sử dụng để rõ ràng xác định giá trị của thisthis là một từ khóa quan trọng trong JavaScript, giúp xác định ngữ cảnh thực thi của mã. Hiểu rõ cách this hoạt động là cần thiết để viết mã chính xác và tránh các lỗi không mong muốn liên quan đến ngữ cảnh thực thi.
Sự khác biệt giữa shim và polyfill trong Javascript là gì?
Trong JavaScript, shim và polyfill là hai khái niệm thường được sử dụng để giúp code JavaScript hoạt động trên các trình duyệt khác nhau, đặc biệt là các trình duyệt cũ không hỗ trợ các tính năng mới. Dù có mục đích tương tự nhau, chúng có những đặc điểm và cách sử dụng khác nhau.
Tóm lại, cả shim và polyfill đều là những công cụ quan trọng trong việc viết mã JavaScript tương thích với nhiều trình duyệt, nhưng chúng khác nhau về mục đích và cách thức hoạt động. Shim cung cấp một API mới cho môi trường cũ mà không thêm vào thực thi bị thiếu, trong khi polyfill thêm vào thực thi cho các tính năng bị thiếu để đảm bảo tính tương thích trên các trình duyệt
Nêu một số trường hợp không nên sử dụng arrow functions trong Javascript?
Trong JavaScript ES6, Arrow Functions mang lại cú pháp ngắn gọn và tiện lợi cho việc viết hàm, nhưng không phải lúc nào chúng cũng là lựa chọn tốt nhất. Dưới đây là một số trường hợp bạn không nên sử dụng Arrow Functions:
this trong ngữ cảnh động: Arrow Functions không có this riêng của chúng. this trong Arrow Functions được xác định bởi ngữ cảnh bên ngoài (lexical this) khi hàm được khai báo, không phải khi hàm được gọi. Điều này có thể gây ra vấn đề khi bạn cần this để tham chiếu đến đối tượng chứa hàm đó, như trong các phương thức của đối tượng hoặc khi xử lý sự kiệnthis sẽ không trỏ đến đối tượng đó mà trỏ đến ngữ cảnh bên ngoài, thường là window hoặc undefined trong strict mode. Điều này làm cho Arrow Functions không phù hợp để định nghĩa phương thức trong đối tượngnew với Arrow Function. Điều này là do Arrow Functions không có this riêng và không có thuộc tính prototypearguments object: Arrow Functions không cung cấp đối tượng arguments giống như các hàm thông thường. Nếu bạn cần truy cập vào các đối số được truyền vào hàm mà không được định nghĩa trước trong danh sách tham số, Arrow Functions sẽ không phù hợpthis giống như khi định nghĩa phương thức trong đối tượng. this trong Arrow Functions sẽ không trỏ đến instance của classTóm lại, mặc dù Arrow Functions mang lại nhiều lợi ích về cú pháp ngắn gọn và tiện lợi, nhưng chúng không phù hợp cho mọi tình huống, đặc biệt là khi cần sử dụng this trong ngữ cảnh động, định nghĩa phương thức trong đối tượng hoặc class, sử dụng làm hàm khởi tạo, hoặc khi cần truy cập vào đối tượng arguments.
Từ khóa new trong JavaScript để làm gì?
Từ khóa new trong JavaScript được sử dụng để tạo một thể hiện mới của một đối tượng từ một hàm tạo (constructor function) hoặc từ một lớp (class). Khi một hàm được gọi với từ khóa new, hàm đó sẽ được sử dụng như một hàm tạo, và new sẽ thực hiện các bước sau
newInstance) vào thuộc tính [[Prototype]] của hàm tạo, nếu prototype là một đối tượng. Nếu không, newInstance sẽ là một đối tượng đơn giản với Object.prototype làm [[Prototype]] của nó.newInstance như là this trong hàm tạo.Một hàm có thể biết liệu nó có được gọi với new hay không bằng cách kiểm tra new.target. new.target chỉ không xác định khi hàm được gọi mà không có new
Ví dụ về việc sử dụng new:
function Car(color) {
if (!new.target) {
// Được gọi như một hàm.
return `${color} car`;
}
// Được gọi với new.
this.color = color;
}
const a = Car("red"); // a là "red car"
const b = new Car("red"); // b là `Car { color: "red" }`
Nếu không sử dụng new, hàm tạo có thể được gọi như một hàm thông thường (tức là không có new), và trong trường hợp này không có đối tượng mới nào được tạo và giá trị của this cũng khác
Tóm lại, từ khóa new cho phép các nhà phát triển tạo ra một thể hiện của một kiểu đối tượng do người dùng định nghĩa hoặc một trong những kiểu đối tượng tích hợp sẵn có trong JavaScript
DOM là gì?
DOM trong ngữ cảnh của phát triển web là viết tắt của Document Object Model. Đây là một giao diện lập trình ứng dụng (API) đa nền tảng và độc lập với ngôn ngữ lập trình, cho phép các tài liệu HTML hoặc XML được xem như một cấu trúc cây, trong đó mỗi nút của cây đại diện cho một phần của tài liệu
DOM không phải là một phần của ngôn ngữ JavaScript; nó là một Web API được sử dụng để xây dựng các trang web. JavaScript thường được sử dụng để tương tác với DOM và thực hiện các thao tác như thêm, xóa hoặc sửa đổi các phần tử HTML, lắng nghe và xử lý sự kiện, và thay đổi CSS của các phần tử
Ưu điểm của promise so với callback là gì?
Ưu điểm của Promise so với Callback trong lập trình JavaScript bao gồm:
.catch() duy nhất ở cuối chuỗi promise để bắt bất kỳ lỗi nào có thể đã xảy ra ở bất kỳ điểm nào trong chuỗi. Điều này đơn giản hóa việc theo dõi và gỡ lỗi.Tóm lại, việc chọn Promises thay vì callbacks không chỉ cải thiện các khía cạnh kỹ thuật của mã mà còn cải thiện trải nghiệm tổng thể của việc viết và bảo trì mã. Các lợi ích bao gồm cải thiện khả năng đọc, loại bỏ callback hell, xử lý lỗi tốt hơn, khả năng chuỗi, đơn giản hóa thiết kế, một đường dẫn thực thi đơn lẻ và xử lý lỗi toàn cầu.
Sự khác biệt giữa null, undefined hoặc undeclared trong Javascript là gì?
Trong JavaScript, null, undefined và undeclared đều đại diện cho các trạng thái khác nhau của biến, và việc hiểu rõ sự khác biệt giữa chúng là quan trọng để viết mã nguồn chính xác và tránh lỗi.
nullnull là một giá trị nguyên thủy trong JavaScript, được sử dụng để biểu thị một biến có giá trị "không có gì" hoặc "trống rỗng". Nó thể hiện sự vắng mặt ý định của một giá trị đối tượngnull bằng toán tử typeof, kết quả trả về là "object"null thường được gán một cách chủ động vào biến để biểu thị rằng biến đó không trỏ đến bất kỳ đối tượng nàoundefinedundefined biểu thị rằng một biến đã được khai báo nhưng chưa được gán giá trị, hoặc một thuộc tính không tồn tại trong đối tượngundefined bằng toán tử typeof, kết quả trả về là "undefined"undefined thường xuất hiện tự nhiên khi một biến được khai báo mà không có giá trị khởi tạo, hoặc khi truy cập vào một thuộc tính không tồn tại của đối tượngundeclaredundeclared là biến chưa được khai báo trong phạm vi hiện tại, tức là không sử dụng var, let, hoặc const để khai báoundeclared không tồn tại trong phạm vi hiện tại.ReferenceErrornull và undefined: Trong so sánh trừu tượng (sử dụng ==), null và undefined được coi là bằng nhau (null == undefined trả về true). Tuy nhiên, trong so sánh nghiêm ngặt (sử dụng ===), chúng không bằng nhau (null === undefined trả về false)undeclared: Khi truy cập một biến undeclared, JavaScript sẽ báo lỗi ReferenceError vì biến đó không tồn tại trong phạm vi hiện tạinull và undefined đều là giá trị nguyên thủy trong JavaScript nhưng biểu thị hai trạng thái khác nhau: null là một giá trị rỗng được gán một cách chủ động, trong khi undefined là giá trị mặc định cho biến chưa được khởi tạo. undeclared là trạng thái của một biến không được khai báo và sẽ gây ra lỗi nếu cố gắng sử dụng nó
Đoạn code kết quả sau là gì và hãy giải thích tại sao?
const user = {
email: "my@email.com",
updateEmail: (email) => {
this.email = email;
},
};
user.updateEmail("new@email.com"); console.log(user.email);
my@email.comnew@email.comundefinedReferenceErrorHà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.
IIFEs (Immediately Invoked Function Expressions) trong Javascript là gì?
IIFEs, viết tắt của Immediately Invoked Function Expressions, là một kỹ thuật trong JavaScript để thực thi một hàm ngay lập tức sau khi nó được định nghĩa. IIFEs thường được sử dụng để tạo ra một phạm vi lexical (phạm vi từ vựng) bằng cách sử dụng phạm vi hàm, giúp tránh việc các biến bị nâng lên (hoisting) từ bên trong các khối lệnh, bảo vệ khỏi việc làm bẩn không gian toàn cục (global namespace) và đồng thời cho phép truy cập công khai đến các phương thức trong khi vẫn giữ được tính riêng tư
Cấu trúc cơ bản của một IIFE bao gồm một hàm ẩn danh được bao quanh bởi toán tử nhóm () để ngăn chặn việc truy cập vào các biến bên trong IIFE cũng như ngăn chặn việc làm bẩn không gian toàn cục. Phần thứ hai tạo ra biểu thức hàm được gọi ngay lập tức () thông qua đó trình biên dịch JavaScript sẽ trực tiếp diễn giải hàm
IIFEs có thể được viết theo nhiều cách khác nhau, nhưng một quy ước phổ biến là đặt biểu thức hàm - và tùy chọn toán tử gọi hàm của nó - trong cặp dấu ngoặc đơn, để rõ ràng báo cho trình phân tích cú pháp biết rằng đây là một biểu thức
Ví dụ về một IIFE đơn giản:
(function () {
// Mã lệnh của bạn ở đây
})();
Hoặc sử dụng biến thể hàm mũi tên (arrow function):
(() => {
// Mã lệnh của bạn ở đây
})();
IIFEs có thể được sử dụng để tránh làm bẩn không gian toàn cục, thực hiện các hàm bất đồng bộ như setTimeout(), hoặc tạo ra các biến riêng tư mà bạn muốn ngăn chặn sự thay đổi hoặc chỉnh sửa không mong muốn
Một ví dụ về việc sử dụng IIFE để tạo ra các biến riêng tư:
const counter = (function () {
let i = 0;
return {
get: function () { return i; },
set: function (val) { i = val; },
increment: function () { return ++i; }
};
})();
Trong ví dụ trên, biến i là riêng tư và không thể truy cập từ bên ngoài IIFE. Các phương thức get, set, và increment là công khai và có thể được gọi từ bên ngoài IIFE, nhưng chúng vẫn có thể truy cập biến i riêng tư bên trong IIFE
IIFEs cũng có thể được đặt tên, mặc dù việc đặt tên không làm thay đổi thực tế là hàm không "rò rỉ" ra không gian toàn cục và không thể được gọi lại sau khi thực thi
Ngoài ra, IIFEs có thể được sử dụng để tạo ra các biến và phương thức riêng tư và công khai trong mô hình module
Tóm lại, IIFEs là một công cụ hữu ích trong JavaScript để tạo ra phạm vi riêng tư, tránh làm bẩn không gian toàn cục và thực thi mã ngay lập tức.
Kết quả đoạn javascript sau là gì? Hãy giải thích tại sao?
const set = new Set();
set.add(1);
set.add("Lydia");
set.add({ name: "Lydia" });
for (let item of set) {
console.log(item + 2);
}
3, NaN, NaN3, 7, NaN3, Lydia2, [object Object]2"12", Lydia2, [object Object]2Phé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".
Kết quả của đoạn code javascript sau là gì? Hãy giải thích chi tiết tại sao?
let number = 0;
console.log(number++);
console.log(++number);
console.log(number);
1 1 21 2 20 2 20 1 2Khi phép toán ++ nằm ở đằng sau (postfix):
0)1)Khi phép toán ++ nằm ở đằng trước (prefix):
2)2)Vậy kết quả là 0 2 2.
Hãy giải thích về Scope và Scope Chain trong Javascript?
Scope trong JavaScript xác định khả năng truy cập của biến và hàm ở các phần khác nhau trong một đoạn code. Có hai loại scope chính: global scope và local scope
Biến được khai báo ngoài tất cả các hàm là biến toàn cục (global) và có thể được truy cập từ bất kỳ đâu trong mã JavaScript. Ví dụ:
var name = 'GlobalName'; // Biến toàn cục
function showName() {
console.log(name); // Truy cập biến toàn cục từ bên trong hàm
}
showName(); // In ra: GlobalName
Biến được khai báo bên trong một hàm là biến cục bộ (local) và chỉ có thể được truy cập từ bên trong hàm đó. Mỗi hàm định nghĩa một scope riêng, và biến cục bộ không thể được truy cập từ bên ngoài hàm đó:
function showName() {
var localName = 'LocalName'; // Biến cục bộ
console.log(localName); // Truy cập biến cục bộ từ bên trong hàm
}
showName(); // In ra: LocalName
console.log(localName); // Uncaught ReferenceError: localName is not defined
Scope chain là cơ chế mà JavaScript sử dụng để tìm kiếm biến và hàm. Khi một biến không được tìm thấy trong scope hiện tại, JavaScript sẽ tìm kiếm trong scope cha, và tiếp tục như vậy cho đến khi tìm thấy biến hoặc đến khi đạt đến global scope
Mỗi function định nghĩa một scope riêng, và mỗi function được định nghĩa bên trong một function khác sẽ tạo ra một nested scope. Biến được định nghĩa trong một scope có thể được truy cập từ bất kỳ nested scope nào bên trong nó:
var globalName = 'GlobalName'; // Biến toàn cục
function outerFunction() {
var outerName = 'OuterName'; // Biến cục bộ của outerFunction
function innerFunction() {
var innerName = 'InnerName'; // Biến cục bộ của innerFunction
console.log(innerName); // InnerName
console.log(outerName); // OuterName
console.log(globalName); // GlobalName
}
innerFunction();
}
outerFunction();
Trong ví dụ trên, innerFunction có thể truy cập cả innerName, outerName, và globalName thông qua scope chain. Tuy nhiên, outerFunction không thể truy cập innerName vì nó nằm ngoài scope của outerFunction.
Scope chain cũng liên quan đến cơ chế hoisting, nơi mà các khai báo biến và hàm được di chuyển lên đầu scope trước khi code được thực thi. Điều này có nghĩa là bạn có thể tham chiếu đến một biến trước cả khi nó được khai báo trong cùng một scope:
function showName() {
console.log(name); // undefined do hoisting
var name = 'LocalName';
console.log(name); // LocalName
}
showName();
Trong trường hợp trên, console.log(name) đầu tiên in ra undefined vì biến name đã được hoisted, nhưng chưa được gán giá trị.
Hiểu rõ về scope và scope chain là chìa khóa để viết code chống lỗi và trở thành developer tốt hơn. Bạn sẽ hiểu được ở đâu biến và hàm có thể được truy cập, có thể thay đổi scope của ngữ cảnh trong code của bạn, và viết code nhanh hơn, dễ bảo trì hơn, cũng như debug dễ dàng hơn
Kết quả data của đoạn code sau là gì? Hãy giải thích tại sao?
async function getData() {
return await Promise.resolve("I made it!");
}
const data = getData();
console.log(data);
"I made it!"Promise {<resolved>: "I made it!"}Promise {<pending>}undefinedMộ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!"
Đoạn code sau có xảy ra lỗi không?
const colorConfig = {
red: true,
blue: false,
green: true,
black: true,
yellow: false,
};
const colors = ["pink", "red", "blue"];
console.log(colorConfig.colors[1]);
truefalseundefinedTypeErrorTrong 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.
Giải thích cách hoạt động của JSONP và tại sao nó không thực sự là Ajax?
JSONP là một kỹ thuật được sử dụng để vượt qua hạn chế về chính sách cùng nguồn (same-origin policy) trong các ứng dụng web. Chính sách này ngăn chặn các trang web từ việc yêu cầu tài nguyên từ một miền khác, gây khó khăn trong việc truy cập dữ liệu từ các API nằm trên các miền khác nhau. JSONP giải quyết vấn đề này bằng cách sử dụng thẻ <script> để thực hiện yêu cầu, vì thẻ <script> không bị hạn chế bởi chính sách cùng nguồn
Cách hoạt động của JSONP bao gồm việc gửi một yêu cầu đến máy chủ với một tham số callback trong URL. Máy chủ sau đó trả về dữ liệu dưới dạng một hàm JavaScript, với dữ liệu được bao quanh bởi hàm callback đã được chỉ định. Khi dữ liệu được trả về, hàm callback sẽ được thực thi tự động, cho phép dữ liệu được xử lý bởi trang web
AJAX, viết tắt của Asynchronous JavaScript and XML, là một kỹ thuật cho phép trang web gửi và nhận dữ liệu từ máy chủ một cách không đồng bộ, mà không cần phải tải lại trang web. Điều này giúp cải thiện trải nghiệm người dùng bằng cách làm cho các ứng dụng web trở nên nhanh chóng và đáp ứng hơn
AJAX hoạt động bằng cách sử dụng đối tượng XMLHttpRequest để gửi yêu cầu đến máy chủ và nhận dữ liệu trả về. Dữ liệu trả về có thể ở nhiều định dạng khác nhau, bao gồm XML, JSON, HTML, hoặc văn bản thuần túy. Sau khi nhận được dữ liệu, JavaScript được sử dụng để cập nhật trang web mà không cần tải lại trang
<script><script> để gửi yêu cầu. AJAX hỗ trợ cả GET và POST, cho phép gửi dữ liệu phức tạp hơn đến máy chủMặc dù JSONP và AJAX đều là các kỹ thuật cho phép truy cập dữ liệu không đồng bộ trong các ứng dụng web, chúng hoạt động dựa trên các nguyên tắc khác nhau và có những ưu nhược điểm riêng. JSONP là một giải pháp để vượt qua hạn chế về chính sách cùng nguồn nhưng có rủi ro bảo mật cao hơn, trong khi AJAX cung cấp một cách an toàn và linh hoạt hơn để tương tác với máy chủ, nhưng bị hạn chế bởi chính sách cùng nguồn
Có thể reset một generator trong Javascript về state ban đầu của nó không?
Không thể reset một generator trong JavaScript để trở về trạng thái ban đầu mà không gọi lại hàm generator. Một khi generator đã đạt trạng thái "hoàn thành" (khi thuộc tính done trở thành true), nó sẽ ở trạng thái đó vĩnh viễn và không thể được reset. Điều này có nghĩa là bất kỳ trạng thái thực thi nào liên quan đến generator có thể được loại bỏ. Nếu bạn muốn sử dụng lại generator, bạn cần phải khởi tạo một thể hiện mới của nó
Tuy nhiên, có một số cách để "reset" giá trị mà generator đã yield về giá trị ban đầu hoặc cập nhật nó bằng cách truyền một đối số vào phương thức next(). Điều này chỉ có thể thực hiện trước khi generator hoàn thành. Ví dụ, bạn có thể truyền một giá trị vào phương thức next() để cập nhật biến trạng thái bên trong generator, nhưng điều này không thực sự "reset" generator về trạng thái ban đầu của nó mà chỉ thay đổi giá trị mà nó sẽ yield tiếp theo
Nếu bạn muốn sử dụng lại một generator sau khi nó đã hoàn thành, giải pháp duy nhất là khởi tạo lại nó bằng cách gọi lại hàm generator. Điều này tạo ra một thể hiện mới của generator, cho phép bạn bắt đầu quá trình lặp lại từ đầu
Hạn chế của phương thức private trong JavaScript là gì?
Trong JavaScript, việc sử dụng các phương thức và thuộc tính private được giới thiệu như một phần của cú pháp lớp (class syntax) trong ECMAScript 2015 (ES6) và được mở rộng với các tính năng bổ sung trong các phiên bản sau. Các phương thức và thuộc tính private trong một lớp chỉ có thể được truy cập từ bên trong lớp đó. Tuy nhiên, việc sử dụng các phương thức và thuộc tính private trong JavaScript có một số hạn chế:
Tóm lại, mặc dù các thuộc tính và phương thức private trong JavaScript mang lại nhiều lợi ích về bảo mật và đóng gói, nhưng cũng đi kèm với một số hạn chế liên quan đến khả năng tương thích, gỡ lỗi, phản xạ, và thiết kế. Các nhà phát triển cần cân nhắc kỹ lưỡng khi quyết định sử dụng tính năng này trong các dự án của mình.
Kết quả members của đoạn code sau là gì? Hãy giải thích tại sao?
let person = { name: "Lydia" };
const members = [person];
person = null;
console.log(members);
null[null][{}][{ name: "Lydia" }]Đầ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.
Trong Javascript, hàm setInterval trả về cái gì?
setInterval(() => console.log("Hi"), 1000);
undefinedNó trả về một id duy nhất. Id này dùng để clear interval sau này với hàm clearInterval().
Đoạn code javascript sau có xảy ra lỗi không và giải thích tại sao?
let c = { greeting: "Hey!" };
let d;
d = c;
c.greeting = "Hello";
console.log(d.greeting);
HelloHeyundefinedReferenceErrorTypeErrorTrong 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.
Kết quả đoạn code javascript sau là gì? Hãy giải thích chi tiết?
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));
[['First', 'Second'], ['Fourth']][['First', 'Second'], ['Third', 'Fourth']][['First', 'Second']]'Third'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.
Kết quả output của đoạn code sau là gì? Hãy giải thích tại sao?
var x = 1;
var output = (function () {
delete x;
return x;
})();
console.log(output);
Đ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.
Kết quả của đoạn code sau là gì? Hãy giải thích?
let a = 3;
let b = new Number(3);
let c = 3;
console.log(a == b);
console.log(a === b);
console.log(b === c);
true false truefalse false truetrue false falsefalse true truenew 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.
3 giai đoạn của event propagation trong Javascript là gì?
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.
Kết quả member là gì? Hãy giải thích tại sao?
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);
{ email: "my@email.com", address: null }{ email: "my@email.com" }{ email: "my@email.com", address: {} }{ email: "my@email.com", address: undefined }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 {}.
Khi nào cần sử dụng async và defer trong javascript?
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.
<!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.
<!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.
Kết quả của đoạn code sau là gì? Hãy giải thích tại sao?
let num = 10;
const increaseNumber = () => num++;
const increasePassedNumber = number => number++;
const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);
console.log(num1);
console.log(num2);
10, 1010, 1111, 1111, 12Phé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.
Kết quả hàm sau là gì? Hãy giải thích tại sao?
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));
Calculated! 20 Calculated! 20 Calculated! 20Calculated! 20 From cache! 20 Calculated! 20Calculated! 20 From cache! 20 From cache! 20Calculated! 20 From cache! 20 ErrorHà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.
Giải thích sự khác biệt giữa undefined và not defined trong JavaScript?
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
Đoạn code sau kết quả là gì? Giải thích tại sao?
(() => {
let x = (y = 10);
})();
console.log(typeof x);
console.log(typeof y);
"undefined", "number""number", "number""object", "number""number", "undefined"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".
Kết quả của vòng lặp sau là gì? Hãy giải thích tại sao?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
0 1 2 and 0 1 20 1 2 and 3 3 33 3 3 and 0 1 2Bở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.
Kết quả đoạn code sau là gì? Hãy giải thích?
+true;
!"Lydia";
1 and falsefalse and NaNfalse and falsePhé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.
Sự khác nhau giữa bind, call và apply trong Javascript là gì?
Trong JavaScript, bind, call và apply là ba phương thức của đối tượng Function và chúng đều được sử dụng để thiết lập giá trị của this khi gọi một hàm. Dưới đây là sự khác biệt chính giữa chúng:
call()call() gọi hàm ngay lập tức với giá trị this được chỉ định.apply()call(), apply() cũng gọi hàm ngay tức thì.apply() nhận một mảng các tham sốbind()bind() không gọi hàm ngay khi nó được sử dụng, mà trả về một hàm mới với giá trị this được ràng buộc sẵn.call(), nhưng hàm mới này có thể được gọi sau đó với giá trị this đã được thiết lậpcall(): Khi bạn muốn gọi một hàm ngay lập tức và bạn biết số lượng tham số cụ thể.apply(): Khi bạn muốn gọi một hàm ngay lập tức nhưng tham số được lưu trữ trong một mảng.bind(): Khi bạn muốn tạo ra một hàm mới với this được ràng buộc sẵn để sử dụng sau này, hoặc khi bạn muốn tránh việc this bị thay đổi trong các callback hoặc event handler.let person = {
name: 'Alice',
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
// Sử dụng call()
person.greet.call({ name: 'Bob' }); // "Hello, my name is Bob"
// Sử dụng apply()
person.greet.apply({ name: 'Charlie' }); // "Hello, my name is Charlie"
// Sử dụng bind()
let boundGreet = person.greet.bind({ name: 'David' });
boundGreet(); // "Hello, my name is David"
Trong ví dụ trên, call() và apply() được sử dụng để gọi hàm greet ngay lập tức với this được thiết lập là một đối tượng mới. bind() tạo ra một hàm mới boundGreet mà khi gọi sau này sẽ sử dụng this đã được ràng buộc từ trước.
Sự khác biệt giữa await và từ khóa yield trong Javascript là gì?
Trong JavaScript, await và yield là hai từ khóa được sử dụng trong các ngữ cảnh khác nhau, với mục đích và cách hoạt động riêng biệt. Dưới đây là sự khác biệt chính giữa chúng:
awaitawait được sử dụng trong một hàm async để tạm dừng việc thực thi hàm cho đến khi Promise được giải quyết (resolved) hoặc bị từ chối (rejected).await là đơn giản hóa việc sử dụng các hàm không đồng bộ, giúp mã nguồn trở nên dễ đọc và viết giống như mã đồng bộ, mặc dù nó thực sự là không đồng bộ.await được sử dụng trước một Promise, việc thực thi của hàm async sẽ tạm dừng cho đến khi Promise đó hoàn thành. Sau đó, hàm tiếp tục thực thi với giá trị được giải quyết từ Promise.yieldyield được sử dụng trong một hàm generator (được định nghĩa bằng cách thêm * sau từ khóa function) để tạm dừng và tiếp tục việc thực thi của hàm.yield cho phép một hàm generator tạm dừng việc thực thi và trả về một giá trị (hoặc undefined). Hàm generator có thể được tiếp tục lại từ điểm tạm dừng, cho phép tạo ra một chuỗi các giá trị theo yêu cầu thay vì tính toán tất cả cùng một lúc.yield được gọi, nó trả về một đối tượng có hai thuộc tính: value (giá trị được trả về bởi yield) và done (một boolean chỉ ra liệu hàm generator đã hoàn thành hay chưa). Việc thực thi của hàm generator tạm dừng tại yield và có thể được tiếp tục lại bằng cách gọi phương thức next().await được sử dụng trong hàm async để làm cho việc xử lý các Promise trở nên dễ dàng hơn, giúp mã nguồn không đồng bộ trông giống như đồng bộ.yield được sử dụng trong hàm generator để tạo ra một chuỗi các giá trị theo yêu cầu, cho phép tạm dừng và tiếp tục việc thực thi của hàm.Cả hai từ khóa đều mang lại lợi ích trong việc xử lý các tác vụ không đồng bộ và tạo ra dữ liệu theo yêu cầu, nhưng chúng được sử dụng trong các ngữ cảnh và mục đích khác nhau.
Ouput đoạn code sau là gì và giải thích tại sao?
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`;
"Lydia" 21 ["", " is ", " years old"]["", " is ", " years old"] "Lydia" 21"Lydia" ["", " is ", " years old"] 21Nế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!
Đoạn code dưới đây những chỗ nào không hợp lệ?
const bird = {
size: "small",
};
const mouse = {
name: "Mickey",
small: true,
};
mouse.bird.size không hợp lệmouse[bird.size] không hợp lệmouse[bird["size"]] không hợp lệ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.
Sự khác biệt giữa Map và WeakMap trong Javascript là gì?
Trong ECMAScript 6 (ES6), Map và WeakMap là hai cấu trúc dữ liệu mới được giới thiệu để lưu trữ cặp khóa-giá trị. Mặc dù cả hai đều lưu trữ cặp khóa-giá trị, chúng có một số sự khác biệt quan trọng:
Map, khóa có thể là bất kỳ kiểu dữ liệu nào, bao gồm cả đối tượng, chuỗi, số, và các kiểu dữ liệu khác.Map là duyệt được, có nghĩa là bạn có thể lặp qua các phần tử của nó theo thứ tự chèn vào.Map thông qua thuộc tính size.Map có hiệu suất tốt trong việc thêm và xóa các phần tử.Map giữ các khóa với một tham chiếu mạnh, điều này có nghĩa là miễn là một khóa tồn tại trong Map, nó sẽ không được thu gom rác (garbage collected).WeakMap, khóa phải là một đối tượng. Không thể sử dụng các kiểu dữ liệu nguyên thủy như chuỗi hoặc số làm khóa.WeakMap không phải là duyệt được, và không có phương thức hoặc thuộc tính nào để lấy hoặc kiểm tra toàn bộ nội dung của nó.WeakMap không có thuộc tính size và không có cách nào để xác định số lượng phần tử mà nó chứa.WeakMap giữ các khóa với một tham chiếu yếu, có nghĩa là nếu không còn tham chiếu nào khác đến một đối tượng được sử dụng làm khóa, đối tượng đó có thể được thu gom rác. Điều này giúp WeakMap trở thành một lựa chọn tốt cho việc lưu trữ dữ liệu riêng tư hoặc dữ liệu tạm thời mà không ngăn chặn việc thu gom rác của các đối tượng khóa.Map và WeakMap đều hữu ích cho các trường hợp sử dụng khác nhau. Map thích hợp cho việc lưu trữ cặp khóa-giá trị mà bạn muốn duyệt qua hoặc khi bạn cần biết số lượng phần tử. WeakMap thích hợp cho các trường hợp mà bạn muốn tránh ngăn chặn việc thu gom rác của các đối tượng khóa, hoặc khi bạn không cần duyệt qua các phần tử và không quan tâm đến số lượng phần tử trong cấu trúc dữ liệu.
Kết quả đoạn code javascript sau là gì và giải thích tại sao?
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);
Not the same!They are the same!ReferenceErrorSyntaxErrorObject 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.
Kết quả đoạn js code sau là gì? Hãy giải thích tại sao?
const name = "Lydia";
age = 21;
console.log(delete name);
console.log(delete age);
false, true"Lydia", 21true, trueundefined, undefinedPhé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.
Generator trong Javascript là gì?
Generator trong JavaScript là một loại hàm đặc biệt có thể tạm dừng và tiếp tục việc thực thi của nó. Các hàm generator được định nghĩa bằng cách thêm một dấu sao (*) ngay sau từ khóa function. Khi được gọi, hàm generator không thực thi ngay lập tức mà thay vào đó, nó trả về một đối tượng Generator mà qua đó việc thực thi có thể được kiểm soát.
yield và sau đó có thể tiếp tục từ điểm tạm dừng đó.Để sử dụng một hàm generator, bạn cần:
function.yield bên trong hàm để trả về giá trị.Generator.next() trên đối tượng Generator để tiếp tục việc thực thi và nhận giá trị tiếp theo.function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = numberGenerator(); // Tạo đối tượng Generator
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
Trong ví dụ trên, mỗi lần gọi phương thức next() trên đối tượng Generator sẽ nhận được giá trị tiếp theo từ hàm generator.
Generator là một công cụ mạnh mẽ trong JavaScript cho phép viết mã có khả năng tạm dừng và tiếp tục, tạo ra dữ liệu theo yêu cầu và thực hiện các tác vụ không đồng bộ một cách hiệu quả. Các hàm generator thường được sử dụng trong các tình huống cần xử lý các chuỗi dữ liệu lớn hoặc phức tạp, hoặc khi cần kiểm soát chặt chẽ quá trình thực thi của hàm.
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 ❤️
game.next("Yes").value and game.next().valuegame.next.value("Yes") and game.next.value()game.next().value and game.next("Yes").valuegame.next.value() and game.next.value("Yes")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.
Thuộc tính NaN trong JavaScript là gì?
Thuộc tính NaN trong JavaScript đại diện cho "Not-a-Number", tức là một giá trị không phải là một số hợp lệ. Dưới đây là một số điểm quan trọng về NaN:
NaN là một giá trị đặc biệt trong JavaScript, biểu thị một giá trị không xác định hoặc không thể biểu diễn được dưới dạng sốNaN là kết quả của một số phép toán không hợp lệ, chẳng hạn như phép chia 0 cho 0, lấy căn bậc hai của một số âm, hoặc cố gắng chuyển đổi một chuỗi không phải là số thành sốNaN có kiểu là number, nó không bằng bất kỳ giá trị số nào khác, kể cả chính nó. Điều này có nghĩa là NaN !== NaN trả về trueNaN hay không, bạn không thể sử dụng toán tử so sánh bình thường. Thay vào đó, bạn nên sử dụng hàm Number.isNaN() hoặc hàm isNaN(). Number.isNaN() trả về true nếu giá trị đó là NaN và kiểu dữ liệu là Number, trong khi isNaN() chuyển đổi giá trị thành số trước khi kiểm traNaN là một thuộc tính không ghi được, không liệt kê được và không cấu hình được của đối tượng NumberVí dụ, để kiểm tra một giá trị có phải là NaN:
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(123)); // false
NaN và cách xử lý nó đóng một vai trò quan trọng trong việc xác định và xử lý các tình huống lỗi hoặc không hợp lệ trong các phép toán số học trong JavaScript
Kết của hàm sum sau là gì? Hãy giải thích tại sao?
function sum(a, b) {
return a + b;
}
sum(1, "2");
NaNTypeError"12"3JavaScript 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".
Symbol trong ES6 là gì?
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.
Bạn biết những cách nào để lặp quả các phần tử của mảng trong Javascript?
Trong JavaScript, có nhiều cách để lặp qua các phần tử của một mảng. Dưới đây là một số phương pháp phổ biến:
forĐây là cách truyền thống nhất để lặp qua mảng, sử dụng chỉ số để truy cập từng phần tử:
const items = ['item1', 'item2', 'item3'];
for (let i = 0; i < items.length; i++) {
console.log(items[i]);
}
for...ofES6 giới thiệu vòng lặp for...of cho phép lặp qua các phần tử của mảng một cách trực tiếp mà không cần sử dụng chỉ số:
const items = ['item1', 'item2', 'item3'];
for (const item of items) {
console.log(item);
}
forEachPhương thức forEach cho phép bạn thực hiện một hàm lên mỗi phần tử của mảng:
const items = ['item1', 'item2', 'item3'];
items.forEach(item => {
console.log(item);
});
mapPhương thức map tạo ra một mảng mới từ việc áp dụng một hàm lên mỗi phần tử của mảng ban đầu:
const items = ['item1', 'item2', 'item3'];
const copy = items.map(item => item);
filterPhương thức filter tạo ra một mảng mới chứa các phần tử thỏa mãn điều kiện được định nghĩa trong hàm callback:
const numbers = [65, 44, 12, 4];
const filteredNumbers = numbers.filter(number => number > 20);
reducePhương thức reduce thực hiện một hàm lên từng phần tử của mảng, kết quả được tích lũy lại và trả về một giá trị duy nhất:
const numbers = [65, 44, 12, 4];
const sum = numbers.reduce((total, number) => total + number, 0);
while và do...whileCác vòng lặp while và do...while cũng có thể được sử dụng để lặp qua mảng, nhưng chúng ít được sử dụng hơn do cú pháp không tiện lợi bằng các phương pháp trên và thường không cung cấp cùng mức độ rõ ràng về ý định lặp qua mảng
JavaScript là ngôn ngữ kiểu tĩnh hay kiểu động? Hãy giải thích
JavaScript là một ngôn ngữ lập trình có kiểu động (dynamically typed). Điều này có nghĩa là kiểu dữ liệu của biến được xác định tại thời điểm thực thi chương trình dựa trên giá trị của biến tại thời điểm đó, không cần phải khai báo kiểu dữ liệu một cách rõ ràng khi viết mã
Trong JavaScript, bạn có thể gán bất kỳ loại giá trị nào cho một biến mà không cần xác định kiểu dữ liệu trước. Ví dụ, bạn có thể gán một số cho một biến, sau đó gán một chuỗi cho cùng một biến mà không gặp phải lỗi kiểu dữ liệu
Điều này khác biệt với các ngôn ngữ có kiểu tĩnh (statically typed) như Java hoặc C++, nơi bạn cần khai báo kiểu dữ liệu của biến ngay từ đầu và không thể thay đổi sau khi đã được xác định. Kiểu dữ liệu của biến trong các ngôn ngữ này được kiểm tra tại thời điểm biên dịch (compile time), trước khi chương trình được thực thi
Sử dụng Promise trong JavaScript như thế nào?
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:
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.

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.

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
So sánh sự khác nhau giữa Object.freeze() và const trong Javascript là gì?
Trong JavaScript, Object.freeze() và const đều được sử dụng để hạn chế sự thay đổi của dữ liệu, nhưng chúng hoạt động theo những cách khác nhau và phục vụ cho các mục đích khác nhau.
Object.freeze()Object.freeze() được sử dụng để làm cho một đối tượng trở nên bất biến. Khi một đối tượng được "đóng băng" bằng Object.freeze(), bạn không thể thêm, xóa, hoặc thay đổi bất kỳ thuộc tính nào của đối tượng đóObject.freeze() chỉ áp dụng một "shallow freeze", nghĩa là nó chỉ đóng băng các thuộc tính ở cấp độ ngoài cùng của đối tượng. Đối với các đối tượng lồng nhau, bạn cần áp dụng Object.freeze() một cách đệ quy để đóng băng toàn bộ cấu trúc đối tượngconstconst được sử dụng để khai báo một biến với giá trị không thể được gán lại sau khi đã được khởi tạo. Tuy nhiên, nếu giá trị đó là một đối tượng, các thuộc tính bên trong đối tượng vẫn có thể được thay đổiconst áp dụng cho việc gán lại biến chứ không phải tính bất biến của giá trị mà biến đó tham chiếu. Điều này có nghĩa là const ngăn chặn việc gán một đối tượng hoặc giá trị mới cho biến, nhưng không ngăn chặn việc thay đổi nội dung của một đối tượngObject.freeze() khi bạn muốn đảm bảo rằng một đối tượng không thể bị thay đổi, bao gồm cả việc thêm, xóa, hoặc chỉnh sửa các thuộc tính của nó. Điều này hữu ích trong việc tạo ra các đối tượng bất biến trong ứng dụng của bạnconst khi bạn muốn khai báo một biến mà giá trị của nó không được phép thay đổi qua việc gán lại. Tuy nhiên, nếu biến đó tham chiếu đến một đối tượng, hãy nhớ rằng các thuộc tính bên trong đối tượng vẫn có thể thay đổi trừ khi bạn cũng sử dụng Object.freeze() để đóng băng đối tượngTóm lại, Object.freeze() và const đều có vai trò trong việc tạo ra và bảo vệ tính bất biến trong JavaScript, nhưng chúng hoạt động ở các cấp độ khác nhau và không thể thay thế cho nhau. Object.freeze() ngăn chặn sự thay đổi của đối tượng, trong khi const ngăn chặn việc gán lại biến.
Ouput đoạn code javascript sau là gì và hãy giải thích tại sao?
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);
Person {firstName: "Lydia", lastName: "Hallie"} và undefinedPerson {firstName: "Lydia", lastName: "Hallie"} và Person {firstName: "Sarah", lastName: "Smith"}Person {firstName: "Lydia", lastName: "Hallie"} và {}Person {firstName: "Lydia", lastName: "Hallie"} và ReferenceErrorVớ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.
Làm sao để sao chép (clone) một mảng trong Javascript?
Trong JavaScript, có nhiều cách để clone một mảng, mỗi cách có những ưu và nhược điểm riêng. Dưới đây là một số phương pháp phổ biến:
...)Spread Operator cho phép bạn sao chép tất cả các phần tử của một mảng vào một mảng mới.
const originalArray = [1, 2, 3];
const clonedArray = [...originalArray];
slice()Phương thức slice() trả về một bản sao của một phần của mảng vào một mảng mới. Khi không truyền tham số, slice() sẽ sao chép toàn bộ mảng.
const originalArray = [1, 2, 3];
const clonedArray = originalArray.slice();
Array.from()Array.from() tạo một mảng mới từ một đối tượng có thể lặp lại hoặc giống mảng.
const originalArray = [1, 2, 3];
const clonedArray = Array.from(originalArray);
concat()Phương thức concat() được sử dụng để hợp nhất hai hoặc nhiều mảng. Khi sử dụng concat() với một mảng rỗng, nó sẽ tạo ra một bản sao của mảng.
const originalArray = [1, 2, 3];
const clonedArray = [].concat(originalArray);
map()Phương thức map() tạo một mảng mới với kết quả của việc gọi một hàm cho mỗi phần tử mảng. Khi sử dụng map() chỉ để trả về chính phần tử đó, bạn sẽ nhận được một bản sao của mảng.
const originalArray = [1, 2, 3];
const clonedArray = originalArray.map(item => item);
reduce()Phương thức reduce() thực hiện một hàm reducer trên mỗi phần tử của mảng, kết quả là một giá trị duy nhất. Để clone mảng, bạn có thể sử dụng reduce() để thêm từng phần tử vào một mảng mới.
const originalArray = [1, 2, 3];
const clonedArray = originalArray.reduce((acc, item) => {
acc.push(item);
return acc;
}, []);
forEach() kết hợp push()Bạn cũng có thể sử dụng vòng lặp forEach() để duyệt qua mỗi phần tử của mảng gốc và push() chúng vào một mảng mới.
const originalArray = [1, 2, 3];
let clonedArray = [];
originalArray.forEach(item => clonedArray.push(item));
Mỗi phương pháp trên có thể được sử dụng tùy thuộc vào tình huống cụ thể và yêu cầu về hiệu suất hoặc độ phức tạp của code
Đoạn code sau có xảy ra lỗi gì không? Hãy giải thích tại sao?
function bark() {
console.log("Woof!");
}
bark.animal = "dog";
SyntaxError. Bạn không thể thêm thuộc tính theo cách này.undefinedReferenceErrorĐ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.
Sự khác nhau giữa anonymous function và named function trong Javascript là gì?
Trong JavaScript, hàm (function) là một khối mã có thể được gọi nhiều lần. Hàm có thể được phân loại thành anonymous function (hàm ẩn danh) và named function (hàm có tên) dựa trên việc chúng có được đặt tên hay không.
anonymous functionanonymous function là một hàm không có tên. Hàm này thường được sử dụng trong các biểu thức hoặc khi được truyền như một đối số cho hàm khác.let myFunction = function() {
// Mã hàm ở đây
};
named functionnamed function là một hàm có tên rõ ràng, có thể được sử dụng để gọi lại chính nó hoặc từ các phần khác của mã.function myFunction() {
// Mã hàm ở đây
}
named function dễ dàng tái sử dụng và gọi lại, trong khi anonymous function thường được sử dụng một lần hoặc được gán cho một biến.named function sẽ hiển thị tên trong stack trace, giúp việc debug trở nên dễ dàng hơn. anonymous function thường không có tên trong stack trace, có thể gây khó khăn khi debug.named function có thể tự gọi lại mình mà không cần tham chiếu đến một biến, trong khi anonymous function cần một biến tham chiếu để gọi lại chính nó.anonymous function và named function đều có vai trò quan trọng trong JavaScript, tùy thuộc vào ngữ cảnh sử dụng và yêu cầu cụ thể của mã. anonymous function thích hợp cho các hàm callback và các trường hợp không cần tái sử dụng, trong khi named function tốt cho việc tái sử dụng và debug.
Kết quả của đoạn code javascript sau là gì và giải thích tại sao?
function getAge() {
"use strict";
age = 21;
console.log(age);
}
getAge();
21undefinedReferenceErrorTypeErrorVớ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.
Giải thích chính sách same-origin trong JavaScript?
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:
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) :
Bạn biết những cách nào để lặp qua các thuộc tính đối tượng trong Javascript?
Trong JavaScript, có ba cách chính để lặp qua các thuộc tính của một đối tượng:
Vòng lặp for...in: Lặp qua tất cả các thuộc tính có thể liệt kê của một đối tượng, bao gồm cả các thuộc tính được kế thừa từ prototype
for (let key in object) {
if (object.hasOwnProperty(key)) {
console.log(key, object[key]);
}
}
Phương thức Object.keys(obj): Trả về một mảng chứa tất cả các khóa (keys) của các thuộc tính có thể liệt kê của một đối tượng (không bao gồm các thuộc tính từ prototype). Bạn có thể kết hợp với forEach để lặp qua các khóa này
Object.keys(object).forEach(function(key) {
console.log(key, object[key]);
});
Phương thức Object.getOwnPropertyNames(obj): Tương tự như Object.keys nhưng trả về tất cả các thuộc tính của đối tượng, kể cả những thuộc tính không thể liệt kê[5].
Object.getOwnPropertyNames(object).forEach(function(key) {
console.log(key, object[key]);
});
Ngoài ra, từ ES2017 trở đi, bạn cũng có thể sử dụng:
Phương thức Object.entries(obj): Trả về một mảng chứa các cặp khóa-giá trị của các thuộc tính có thể liệt kê của một đối tượng. Mỗi cặp khóa-giá trị là một mảng với hai phần tử: khóa và giá trị[5].
Object.entries(object).forEach(([key, value]) => {
console.log(key, value);
});
Phương thức Object.values(obj): Trả về một mảng chứa giá trị của tất cả các thuộc tính có thể liệt kê của một đối tượng
Object.values(object).forEach(function(value) {
console.log(value);
});
Lưu ý rằng khi sử dụng for...in, bạn nên kiểm tra xem thuộc tính có phải là thuộc tính riêng của đối tượng (không phải từ prototype) bằng cách sử dụng hasOwnProperty để tránh lặp qua các thuộc tính không mong muốn.
Đoạn code javascript sau có xảy ra lỗi không? Hãy giải thích tại sao?
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();
"It worked! Hello world!""Oh no an error: undefinedSyntaxError: can only throw Error objects"Oh no an error: Hello world!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'.
Kết quả của đoạn code javascript sau là gì? Và hãy giải thích tại sao?
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");
orangepurplegreenTypeErrorHà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.
Kết quả đoạn code sau là gì? Và hãy giải thích tại sao?
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 });
You are an adult!You are still an adult.Hmm.. You don't have an age I guessKhi 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.
Kết quả đoạn code sau là gì? Hãy giải thích tại sao?
const user = { name: "Lydia", age: 21 };
const admin = { admin: true, ...user };
console.log(admin);
{ admin: true, user: { name: "Lydia", age: 21 } }{ admin: true, name: "Lydia", age: 21 }{ admin: true, user: ["Lydia", 21] }{ admin: true }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 }.
Bạn có thể so sánh việc sử dụng Module Pattern với Constructor/Prototype Pattern không?
Mô hình Module và Mô hình Constructor/Prototype là hai cách phổ biến để cấu trúc mã JavaScript, mỗi mô hình có những ưu và nhược điểm riêng biệt tùy thuộc vào nhu cầu cụ thể của dự án.
Mô hình Module sử dụng các hàm tự gọi (IIFE - Immediately Invoked Function Expression) để tạo ra một không gian tên riêng biệt, giúp che giấu các biến và hàm riêng tư khỏi phạm vi toàn cục. Điều này giúp tránh xung đột tên biến và tạo ra một cấu trúc mã rõ ràng, dễ bảo trì.
Ưu điểm:
Nhược điểm:
Mô hình Constructor/Prototype sử dụng các hàm constructor và prototype để tạo và kế thừa các đối tượng. Điều này cho phép tái sử dụng mã và tối ưu hóa bộ nhớ bằng cách chia sẻ các hàm giữa các thực thể của cùng một lớp thông qua prototype.
Ưu điểm:
Nhược điểm:
Tóm lại, việc lựa chọn giữa Mô hình Module và Mô hình Constructor/Prototype phụ thuộc vào yêu cầu cụ thể của dự án và sở thích cá nhân. Mô hình Module phù hợp cho việc tạo các module độc lập với khả năng che giấu thông tin, trong khi Mô hình Constructor/Prototype phù hợp cho việc tạo các đối tượng có cấu trúc kế thừa và tái sử dụng mã.
Giải thích sự khác biệt giữa function Person(){}, var person = Person(), và var person = new Person() là gì?
Sự khác biệt giữa function Person(){}, var person = Person(), và var person = new Person() là như sau:
function Person(){}: Đây là một khai báo hàm bình thường. Cách viết này tạo ra một hàm có tên là Person. Theo quy ước, việc sử dụng PascalCase (viết hoa chữ cái đầu tiên của mỗi từ) cho tên hàm như vậy ám chỉ rằng hàm này được thiết kế để sử dụng như một hàm tạo (constructor function). Hàm tạo là một hàm đặc biệt được sử dụng để tạo ra các đối tượng mới.var person = Person(): Câu lệnh này gọi hàm Person như một hàm bình thường và gán giá trị trả về của hàm cho biến person. Nếu hàm Person được thiết kế để sử dụng như một hàm tạo nhưng lại được gọi mà không sử dụng từ khóa new, thì đây là một sai lầm phổ biến. Trong trường hợp này, thay vì tạo ra một đối tượng mới, hàm sẽ trả về undefined và giá trị này sẽ được gán cho biến person. Điều này dẫn đến việc không thể truy cập các thuộc tính của đối tượng như person.name vì person sẽ không phải là một đối tượng.var person = new Person(): Câu lệnh này sử dụng từ khóa new để gọi hàm Person như một hàm tạo. Khi sử dụng new, JavaScript sẽ tạo một đối tượng mới, sau đó gọi hàm Person với this được thiết lập để tham chiếu đến đối tượng mới đó. Hàm Person có thể gán các thuộc tính cho đối tượng thông qua this. Đối tượng mới sau đó sẽ được trả về và gán cho biến person. Điều này cho phép tạo ra các đối tượng có các thuộc tính và phương thức được định nghĩa trong hàm tạo Person.Như vậy, sự khác biệt chính giữa ba cách sử dụng này là: function Person(){} tạo ra một hàm tạo, var person = Person() gọi hàm đó như một hàm bình thường và có thể dẫn đến việc không tạo ra đối tượng mới như mong đợi, trong khi var person = new Person() sử dụng hàm tạo đúng cách để tạo ra một đối tượng mới với các thuộc tính và phương thức được định nghĩa trong hàm tạo.
Polyfill trong JavaScript là gì?
Polyfill trong JavaScript là một đoạn mã (thường là JavaScript trên Web) được sử dụng để cung cấp các chức năng hiện đại trên các trình duyệt cũ không hỗ trợ natively các chức năng đó. Ví dụ, một polyfill có thể được sử dụng để mô phỏng chức năng của text-shadow trong IE7 bằng cách sử dụng các bộ lọc độc quyền của IE, hoặc mô phỏng các đơn vị rem hoặc media queries bằng cách sử dụng JavaScript để điều chỉnh động cách trình bày phù hợp, hoặc bất kỳ nhu cầu nào khác mà bạn yêu cầu.
Polyfill không được sử dụng độc quyền vì chức năng tốt hơn và hiệu suất tốt hơn. Các triển khai native của API có thể làm nhiều hơn và nhanh hơn so với polyfills. Ví dụ, polyfill của Object.create chỉ chứa các chức năng có thể thực hiện trong một triển khai không native của Object.create.
Polyfill cũng được sử dụng để giải quyết các vấn đề nơi các trình duyệt thực hiện cùng một tính năng theo các cách khác nhau. Polyfill sử dụng các tính năng không chuẩn trong một trình duyệt cụ thể để cung cấp cho JavaScript một cách tiếp cận tuân thủ chuẩn để truy cập vào tính năng đó.
Polyfill có thể được coi như một vật liệu lấp đầy các khoảng trống và nứt để làm mịn bất kỳ khuyết điểm nào. Nó cung cấp các chức năng và tính năng mà nhà phát triển mong đợi trình duyệt cung cấp theo mặc định.
Polyfill và các tính năng của nó: Polyfill không giới hạn chỉ được thực hiện bằng JavaScript, và "fill" sẽ lấp đầy khoảng trống trong trình duyệt nơi công nghệ cần thiết.
Tại sao, việc để nguyên phạm vi toàn cục của một trang web và không can thiệp vào nó lại là một ý tưởng tốt?
Việc giữ nguyên phạm vi toàn cục của một trang web và không can thiệp vào nó được coi là một ý tưởng tốt vì nhiều lý do:
Tóm lại, việc tránh can thiệp vào phạm vi toàn cục của một trang web giúp tránh xung đột tên biến, làm cho mã nguồn dễ đọc và lý giải hơn, giảm rủi ro về bảo mật, kiểm soát thay đổi dễ dàng hơn, cải thiện hiệu suất, tăng tính tái sử dụng mã và làm cho việc kiểm thử và gỡ lỗi trở nên dễ dàng hơn.
Giải thích sự khác biệt về cách sử dụng foo giữa function foo() {} và var foo = function() {} là gì?
Sự khác biệt chính giữa function foo() {} (Function Declaration) và var foo = function() {} (Function Expression) liên quan đến hoisting và cách chúng được tải trong JavaScript.
function foo() {}) được hoisted, nghĩa là nó được đưa lên đầu phạm vi (scope) trước khi bất kỳ mã nào được thực thi. Điều này cho phép bạn gọi hàm trước khi nó được định nghĩa trong mã nguồn. Ngược lại, Function Expression (var foo = function() {}) không được hoisted. Bạn không thể gọi hàm này trước khi nó được định nghĩa vì biến foo chỉ được hoisted như một biến chưa được khởi tạo, không phải là hàm.Tóm lại, sự khác biệt giữa hai cách khai báo hàm này chủ yếu liên quan đến hoisting và thời điểm đánh giá của chúng trong quá trình thực thi mã JavaScript.
So sánh việc sử dụng Async/Await và Generators để đạt được cùng một chức năng trong javascript?
Trong JavaScript, cả Async/Await và Generators đều là các công cụ giúp xử lý các tác vụ không đồng bộ, nhưng chúng có cách tiếp cận và cú pháp khác nhau.
Async/Await:
Async/Await là một cách tiếp cận mới và đơn giản hơn để xử lý các tác vụ không đồng bộ, dựa trên Promises.async sẽ luôn trả về một Promise. Khi bạn gọi một hàm async, bạn có thể sử dụng await bên trong hàm đó để "chờ" một Promise được giải quyết hoặc bị từ chối, trước khi tiếp tục thực thi các dòng code tiếp theo.Async/Await làm cho code bất đồng bộ trông giống như đồng bộ và dễ đọc, dễ bảo trì hơn.Generators:
Generators là các hàm có thể tạm dừng và tiếp tục lại, cho phép chúng ta tạo ra các chuỗi giá trị (hoặc các tác vụ không đồng bộ) theo cách linh hoạt hơn.Generator được định nghĩa bằng cách thêm dấu * sau từ khóa function và sử dụng từ khóa yield bên trong hàm để tạm dừng việc thực thi.Generators, chúng ta thường kết hợp chúng với Promises hoặc thư viện bổ trợ như co, tạo ra một cơ chế tương tự như Async/Await nhưng phức tạp hơn về cú pháp và cách tiếp cận.So sánh:
Async/Await cung cấp một cách tiếp cận đơn giản và trực quan hơn để xử lý các tác vụ không đồng bộ, làm cho code dễ đọc và dễ bảo trì hơn.Generators cung cấp sự linh hoạt cao hơn trong việc kiểm soát quá trình thực thi của hàm, nhưng cách tiếp cận này đòi hỏi phải hiểu rõ về cách thức hoạt động của Generators và Promises, cũng như cần thêm bước kết hợp với các thư viện bổ trợ để xử lý các tác vụ không đồng bộ một cách hiệu quả.Tóm lại, Async/Await và Generators đều có thể được sử dụng để đạt được cùng một chức năng trong việc xử lý các tác vụ không đồng bộ trong JavaScript, nhưng Async/Await thường được ưa chuộng hơn vì tính đơn giản và dễ đọc của nó
Giải thích về phép gán qua giá trị và phép gán qua tham chiếu trong Javascript?
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.
Giải thích Function.prototype.bind trong javascript?
Function.prototype.bind trong JavaScript là một phương thức cho phép bạn tạo ra một hàm mới từ một hàm hiện có, với một số điều chỉnh về ngữ cảnh this và các tham số truyền vào hàm. Khi bạn gọi phương thức bind trên một hàm, bạn có thể xác định giá trị của this cho hàm mới đó, cũng như truyền trước một số tham số mà hàm sẽ nhận khi nó được gọi.
Cú pháp của Function.prototype.bind là:
var boundFunction = originalFunction.bind(thisArg, arg1, arg2, ...);
Trong đó:
originalFunction là hàm bạn muốn "bind".thisArg là giá trị bạn muốn thiết lập cho this khi hàm được gọi.arg1, arg2, ... là các tham số bạn muốn truyền trước cho hàm.Hàm mới được tạo ra bởi bind sẽ có cùng cơ thể với hàm gốc, nhưng this bên trong hàm sẽ tham chiếu đến thisArg đã được cung cấp, và các tham số arg1, arg2, ... sẽ được đặt trước các tham số được truyền vào khi hàm mới được gọi.
Ví dụ:
var obj = {
x: 10,
getX: function() {
return this.x;
}
};
var unboundGetX = obj.getX;
console.log(unboundGetX()); // undefined hoặc giá trị của x trong global object, tùy thuộc vào strict mode
var boundGetX = unboundGetX.bind(obj);
console.log(boundGetX()); // 10
Trong ví dụ trên, unboundGetX là một tham chiếu đến hàm getX nhưng không giữ ngữ cảnh this của obj. Khi gọi unboundGetX(), this không tham chiếu đến obj nữa, do đó kết quả là undefined. Tuy nhiên, khi sử dụng bind để tạo boundGetX, this trong boundGetX được thiết lập để tham chiếu đến obj, và kết quả là 10 khi gọi hàm.
Function.prototype.bind là một công cụ hữu ích trong JavaScript, đặc biệt khi làm việc với các hàm callback và event handler, nơi mà việc kiểm soát ngữ cảnh this là rất quan trọng.
Toán tử typeof trong Javascript để làm gì?
Toán tử typeof trong JavaScript là một toán tử được sử dụng để kiểm tra kiểu dữ liệu của một giá trị hoặc biến. Khi áp dụng, typeof trả về một chuỗi mô tả kiểu dữ liệu của giá trị đó. Cú pháp của typeof có thể được viết theo hai cách: typeof operand hoặc typeof(operand), trong đó operand là giá trị hoặc biến cần kiểm tra kiểu dữ liệu
Toán tử typeof có thể được sử dụng để kiểm tra kiểu dữ liệu của các giá trị nguyên thủy như số, chuỗi, và boolean, cũng như các kiểu dữ liệu phức tạp như object và function. Dưới đây là một số ví dụ về cách sử dụng typeof:
console.log(typeof true); // 'boolean'
console.log(typeof 3000); // 'number'
console.log(typeof 'foobar'); // 'string'
console.log(typeof [1, 2, 'foobar']); // 'object'
console.log(typeof {a: 'foobar'}); // 'object'
console.log(typeof function(){}); // 'function'
typeof trả về "object" cho cả mảng và object, làm cho việc phân biệt giữa mảng và object trở nên khó khăntypeof null trả về "object", một điểm gây nhầm lẫn cho nhiều người vì null thực sự không phải là một object. Điều này là do một lỗi lịch sử trong JavaScript mà không được sửa chữa để đảm bảo tính tương thích ngượctypeof trả về "function" cho các hàm, bao gồm cả các constructors Class trong JavaScripttypeof có thể được sử dụng để kiểm tra sự tồn tại của một biến hoặc một đối tượng trước khi sử dụng nó, giúp tránh lỗi khi tham chiếu đến một giá trị không tồn tạiToán tử typeof là một công cụ hữu ích trong JavaScript để kiểm tra kiểu dữ liệu của các giá trị và biến. Tuy nhiên, do một số hạn chế và điểm gây nhầm lẫn, lập trình viên cần lưu ý khi sử dụng nó, đặc biệt là khi làm việc với null và phân biệt giữa mảng và object.
Sự khác biệt giữa .call và .apply là gì?
Trả lời câu hỏi: Sự khác biệt chính giữa .call và .apply trong JavaScript nằm ở cách chúng truyền đối số vào hàm. Cả hai phương thức này đều cho phép bạn gọi một hàm với một giá trị this cụ thể và một danh sách các đối số.
.call gọi một hàm với một giá trị this được chỉ định và các đối số được truyền vào một cách riêng lẻ. Các đối số được liệt kê theo dạng phân cách bằng dấu phẩy. Đối số đầu tiên là giá trị this, nó đề cập đến đối tượng hiện tại hay còn gọi là đối tượng gọi hàm..apply cũng gọi một hàm với một giá trị this được chỉ định nhưng khác biệt là nó nhận các đối số dưới dạng một mảng duy nhất. Điều này hữu ích khi bạn muốn sử dụng một mảng thay vì một danh sách đối số.Ví dụ, nếu bạn có một hàm myFunction và bạn muốn gọi nó với giá trị this là someObject và các đối số là arg1, arg2, arg3, bạn sẽ sử dụng .call và .apply như sau:
myFunction.call(someObject, arg1, arg2, arg3);
myFunction.apply(someObject, [arg1, arg2, arg3]);
Trong cả hai trường hợp, someObject sẽ được sử dụng như là giá trị this bên trong hàm myFunction. Tuy nhiên, với .call, các đối số được liệt kê riêng lẻ, trong khi với .apply, các đối số được truyền dưới dạng một mảng.
Tóm lại, sự khác biệt giữa .call và .apply là cách chúng truyền đối số vào hàm: .call nhận đối số dưới dạng danh sách, còn .apply nhận đối số dưới dạng một mảng.
Sự khác biệt giữa sự kiện tải trang document load event và sự kiện DOMContentLoaded event là gì?
Sự kiện DOMContentLoaded được kích hoạt khi tài liệu HTML đã được tải và phân tích cú pháp hoàn toàn, không chờ đợi các tài nguyên khác như stylesheets, hình ảnh và subframes hoàn thành việc tải. Điều này có nghĩa là sự kiện DOMContentLoaded xảy ra ngay sau khi cấu trúc DOM của trang web đã sẵn sàng, cho phép các mã JavaScript thao tác với DOM ngay lập tức mà không cần đợi tất cả các tài nguyên khác được tải.
Trong khi đó, sự kiện load được kích hoạt khi toàn bộ trang đã tải xong, bao gồm tất cả các tài nguyên phụ thuộc như stylesheets, scripts, iframes và hình ảnh. Sự kiện load xảy ra sau khi tất cả nội dung của trang và tài nguyên liên quan đã được tải và hiển thị hoàn chỉnh, thích hợp cho các tác vụ cần truy cập đến các tài nguyên này, chẳng hạn như thực hiện các thao tác liên quan đến kích thước hình ảnh hoặc khởi tạo các hoạt ảnh.
Tóm lại, sự kiện DOMContentLoaded thích hợp cho việc thực thi các mã JavaScript cần tương tác với DOM ngay khi nó sẵn sàng, trong khi sự kiện load thích hợp cho các tác vụ cần đợi cho đến khi toàn bộ trang và tất cả các tài nguyên liên quan đã được tải hoàn toàn.
Một số ưu/nhược điểm của việc viết code JavaScript bằng ngôn ngữ biên dịch sang JavaScript là gì?
Việc sử dụng các ngôn ngữ lập trình biên dịch sang JavaScript như TypeScript, CoffeeScript, Elm, ClojureScript, và PureScript mang lại một số ưu và nhược điểm như sau:
Ưu điểm:
Nhược điểm:
Cách đơn giản để loại bỏ các phần tử trùng lặp từ một mảng sử dụng ES6 là gì?
Một cách đơn giản để loại bỏ các phần tử trùng lặp từ một mảng trong ES6 là sử dụng cấu trúc dữ liệu Set kết hợp với spread operator. Set là một cấu trúc dữ liệu trong ES6 cho phép lưu trữ các giá trị duy nhất, không cho phép các giá trị trùng lặp. Bạn có thể tạo một Set mới từ mảng ban đầu, sau đó sử dụng spread operator để chuyển đổi Set trở lại thành một mảng mới chỉ chứa các giá trị duy nhất. Dưới đây là cách thực hiện:
const duplicates = [1, 2, 3, 4, 4, 1];
const uniques = [...new Set(duplicates)];
console.log(uniques); // [1, 2, 3, 4]
Trong đoạn mã trên, new Set(duplicates) tạo ra một Set từ mảng duplicates, loại bỏ các giá trị trùng lặp. Sau đó, ... (spread operator) được sử dụng để chuyển đổi Set thành một mảng mới chứa các giá trị duy nhất từ Set.
Bạn có thể đưa ra một ví dụ về hàm curry và giải thích tại sao cú pháp này lại có ích?
Ví dụ về một hàm curry trong JavaScript có thể như sau:
function sum(a) {
return (b) => {
return (c) => {
return a + b + c;
};
};
}
const addOne = sum(1);
const addOneAndTwo = addOne(2);
const result = addOneAndTwo(3); // Kết quả là 6
Trong ví dụ trên, hàm sum được curry hóa để nhận từng đối số một lần. Đầu tiên, nó nhận đối số a, sau đó trả về một hàm mới nhận đối số b, và cuối cùng là hàm nhận đối số c. Khi tất cả các đối số đã được cung cấp, nó sẽ thực hiện phép cộng và trả về kết quả.
Lợi ích của cú pháp này là:
Tuy nhiên, cũng cần lưu ý rằng curry hóa có thể làm tăng độ phức tạp của mã nguồn và sử dụng nhiều bộ nhớ hơn, đặc biệt nếu không được sử dụng một cách cẩn thận.
Prototype trong Javascript là gì?
Trong JavaScript, prototype là một cơ chế mà thông qua đó các đối tượng JavaScript kế thừa các tính năng từ một đối tượng khác. JavaScript là một ngôn ngữ lập trình dựa trên prototype, không dựa trên lớp (class-based), nghĩa là thay vì định nghĩa các lớp và tạo ra các thể hiện của lớp, JavaScript sử dụng các đối tượng prototype để tạo ra các đối tượng mới bằng cách kế thừa các tính năng từ các đối tượng khác.
Mỗi đối tượng JavaScript có một thuộc tính nội bộ gọi là [[Prototype]], có thể được tham chiếu thông qua thuộc tính __proto__ (trong môi trường hiện đại, việc sử dụng Object.getPrototypeOf() được khuyến khích hơn là trực tiếp sử dụng __proto__). Thuộc tính này là một tham chiếu đến một đối tượng khác, từ đó đối tượng hiện tại kế thừa các thuộc tính và phương thức.
Khi bạn cố gắng truy cập một thuộc tính hoặc phương thức của một đối tượng, JavaScript sẽ trước tiên tìm kiếm thuộc tính hoặc phương thức đó trên chính đối tượng đó. Nếu không tìm thấy, JavaScript sẽ tiếp tục tìm kiếm trên prototype của đối tượng, và quá trình này sẽ tiếp tục cho đến khi tìm thấy thuộc tính/phương thức hoặc cho đến khi đạt đến cuối chuỗi prototype mà không tìm thấy (đối tượng prototype cuối cùng thường là Object.prototype, mà từ đó không còn prototype nào khác để tìm kiếm).
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log("Hello, my name is " + this.name);
};
const alice = new Person("Alice");
alice.greet(); // In ra: "Hello, my name is Alice"
Trong ví dụ trên, Person.prototype là đối tượng mà tất cả các thể hiện của Person sẽ kế thừa các thuộc tính và phương thức từ. Khi alice.greet() được gọi, JavaScript tìm thấy phương thức greet trên Person.prototype và thực thi nó.
Sử dụng prototype trong JavaScript mang lại một số lợi ích:
Tóm lại, prototype là một phần cốt lõi của JavaScript, cho phép kế thừa và tái sử dụng mã một cách hiệu quả.
Triển khai Prototype Design Pattern trong Javascript như thế nào?
Prototype Design Pattern trong JavaScript là một kỹ thuật lập trình cho phép tạo ra các đối tượng mới dựa trên một đối tượng mẫu (prototype) đã tồn tại. Mẫu này là một đối tượng cụ thể, và các đối tượng mới được tạo ra sẽ kế thừa các thuộc tính và phương thức từ nó. Điều này giúp tiết kiệm bộ nhớ và tài nguyên bởi vì không cần phải tạo ra một bản sao hoàn chỉnh của mẫu cho mỗi đối tượng mới.
Trong JavaScript, mọi đối tượng đều có một thuộc tính nội bộ gọi là [[Prototype]] (hoặc __proto__ trong một số trình duyệt). Thuộc tính này là một tham chiếu đến một đối tượng khác, và nó được sử dụng để kế thừa các thuộc tính và phương thức. Khi bạn cố gắng truy cập một thuộc tính không tồn tại trên đối tượng hiện tại, JavaScript sẽ tự động tìm kiếm thuộc tính đó trên [[Prototype]].
// Định nghĩa một đối tượng mẫu
const carPrototype = {
start() {
console.log(`The ${this.make} ${this.model} car has started.`);
}
};
// Hàm tạo đối tượng Car mới
function Car(make, model) {
const car = Object.create(carPrototype); // Tạo đối tượng mới từ prototype
car.make = make;
car.model = model;
return car;
}
// Tạo một đối tượng Car mới
const myCar = Car('Toyota', 'Corolla');
myCar.start(); // Output: The Toyota Corolla car has started.
Trong ví dụ trên, carPrototype là một đối tượng mẫu có phương thức start(). Hàm Car tạo ra một đối tượng mới dựa trên carPrototype và gán giá trị cho các thuộc tính make và model. Khi gọi myCar.start(), phương thức start() được kế thừa từ carPrototype.
Prototype Design Pattern là một phần cốt lõi của JavaScript, cho phép kế thừa và tái sử dụng mã một cách hiệu quả. Nó cung cấp một cách để tạo ra các đối tượng mới mà không cần định nghĩa lại các phương thức và thuộc tính, giúp tiết kiệm tài nguyên và tối ưu hóa hiệu suất ứng dụng.
Triển khai Command Pattern trong Javascript như thế nào?
Command Pattern là một mẫu thiết kế hành vi (behavioral design pattern) trong lập trình hướng đối tượng, nó cho phép bạn đóng gói các yêu cầu dưới dạng một đối tượng, từ đó cho phép người dùng tham chiếu đến các yêu cầu này, xếp hàng hoặc ghi nhật ký chúng, và hỗ trợ các hoạt động hoàn tác.
Để triển khai Command Pattern trong JavaScript, bạn cần tạo ra các đối tượng command với một phương thức execute chính và một hoặc nhiều đối tượng receiver mà command sẽ thao tác trên đó. Bạn cũng có thể cần một Invoker để quản lý và gọi các command.
Đầu tiên, bạn cần một interface cho các command với ít nhất một phương thức execute.
class Command {
execute() {
throw new Error('This method must be overwritten!');
}
}
Sau đó, tạo các lớp concrete command thực thi interface command và triển khai phương thức execute.
class TurnOnCommand extends Command {
constructor(receiver) {
super();
this.receiver = receiver;
}
execute() {
this.receiver.turnOn();
}
}
class TurnOffCommand extends Command {
constructor(receiver) {
super();
this.receiver = receiver;
}
execute() {
this.receiver.turnOff();
}
}
Tạo một hoặc nhiều receiver chứa logic thực sự để thực hiện các hành động.
class Light {
turnOn() {
console.log('The light is on');
}
turnOff() {
console.log('The light is off');
}
}
Invoker sẽ gọi command mà không cần biết chi tiết về cách thức thực hiện.
class RemoteControl {
submit(command) {
command.execute();
}
}
Cuối cùng, bạn có thể sử dụng Command Pattern để thực thi các hành động.
const light = new Light();
const turnOnLight = new TurnOnCommand(light);
const turnOffLight = new TurnOffCommand(light);
const remote = new RemoteControl();
remote.submit(turnOnLight); // Output: The light is on
remote.submit(turnOffLight); // Output: The light is off
Command Pattern trong JavaScript giúp tách biệt phần logic yêu cầu thực thi từ phần logic thực sự thực hiện hành động. Điều này không chỉ giúp mã nguồn dễ bảo trì và mở rộng hơn mà còn hỗ trợ thêm các tính năng như hoàn tác và ghi nhật ký một cách dễ dàng.
Closure trong javascript là gì, cho ví dụ?
Closure trong JavaScript là một khái niệm quan trọng, cho phép một hàm truy cập các biến từ phạm vi bên ngoài của nó, ngay cả sau khi phạm vi đó đã kết thúc. Cụ thể, closure là một hàm có khả năng "nhớ" và truy cập các biến và tham số của hàm bên ngoài mà nó được tạo ra, ngay cả khi hàm bên ngoài đó đã thực thi xong
function createCounter() {
let count = 0;
return function() {
count++;
console.log(count);
};
}
let counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
Trong ví dụ trên, hàm createCounter trả về một hàm con, là một closure, có thể truy cập và thay đổi biến count của hàm createCounter. Mỗi lần gọi counter(), biến count sẽ tăng lên một, và giá trị mới của count được in ra màn hình. Điều này cho thấy closure đã "nhớ" và duy trì trạng thái của biến count sau mỗi lần gọi
Closure là một công cụ mạnh mẽ trong JavaScript, giúp lập trình viên quản lý và bảo vệ dữ liệu hiệu quả, đồng thời tạo ra các ứng dụng web động và phức tạp. Hiểu và sử dụng thành thạo closure sẽ mở ra nhiều khả năng mới trong lập trình JavaScript.
Triển khai Constructor Design Pattern trong Javascript như thế nào?
Constructor Design Pattern trong JavaScript là một kỹ thuật lập trình được sử dụng để tạo ra các đối tượng với các thuộc tính và phương thức cụ thể. Mẫu thiết kế này sử dụng hàm tạo (constructor function) hoặc lớp (class) để khởi tạo đối tượng.
Để triển khai Constructor Pattern, bạn cần tạo một hàm tạo hoặc một lớp với các thuộc tính và phương thức mà bạn muốn đối tượng của mình có.
Trong JavaScript cổ điển (ES5 và trước đó), bạn sẽ sử dụng hàm tạo:
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.start = function() {
console.log(this.make + ' ' + this.model + ' engine started.');
};
}
// Tạo một đối tượng Car mới
var myCar = new Car('Toyota', 'Corolla', 2020);
myCar.start(); // Output: Toyota Corolla engine started.
class (ES6+)Trong JavaScript hiện đại (ES6 trở lên), bạn có thể sử dụng từ khóa class để tạo một lớp:
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
start() {
console.log(`${this.make} ${this.model} engine started.`);
}
}
// Tạo một đối tượng Car mới
const myCar = new Car('Toyota', 'Corolla', 2020);
myCar.start(); // Output: Toyota Corolla engine started.
Constructor Design Pattern là một phương pháp cơ bản và mạnh mẽ trong JavaScript để tạo ra các đối tượng. Nó cho phép bạn định nghĩa cách một đối tượng được tạo và cấu trúc của nó, giúp mã nguồn trở nên dễ đọc, dễ bảo trì và tái sử dụng.
Triển khai Singleton Design Pattern trong Javascript như thế nào?
Singleton Design Pattern là một mẫu thiết kế phần mềm được sử dụng để đảm bảo rằng một lớp chỉ có một thể hiện duy nhất và cung cấp một điểm truy cập toàn cục đến thể hiện đó. Trong JavaScript, Singleton Pattern thường được sử dụng để quản lý các tài nguyên chia sẻ, như kết nối cơ sở dữ liệu hoặc cấu hình ứng dụng.
Để triển khai Singleton Pattern trong JavaScript, bạn có thể sử dụng một số cách khác nhau. Dưới đây là một ví dụ sử dụng một hàm tự gọi (IIFE - Immediately Invoked Function Expression) để tạo ra một Singleton:
var Singleton = (function() {
var instance;
function createInstance() {
var object = new Object("I am the instance");
return object;
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// Sử dụng Singleton
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // Output: true
Trong ví dụ trên, Singleton là một IIFE trả về một đối tượng với một phương thức getInstance. Phương thức này kiểm tra xem một thể hiện của lớp đã được tạo hay chưa. Nếu chưa, nó sẽ tạo một thể hiện mới; nếu có, nó sẽ trả về thể hiện hiện tại. Điều này đảm bảo rằng chỉ có một thể hiện duy nhất của lớp được tạo ra.
Singleton Design Pattern là một công cụ hữu ích trong JavaScript để đảm bảo rằng chỉ có một thể hiện duy nhất của một lớp được tạo ra và cung cấp một điểm truy cập toàn cục đến thể hiện đó. Mẫu thiết kế này giúp quản lý tài nguyên và trạng thái ứng dụng một cách hiệu quả.
Những lợi ích của WeakMap trong Javascript là gì?
WeakMap là một cấu trúc dữ liệu trong ECMAScript 6 (ES6) giống như Map, nhưng với một số điểm khác biệt quan trọng giúp nó có những công dụng đặc biệt trong một số trường hợp sử dụng. Dưới đây là những công dụng chính của WeakMap:
WeakMap giữ các key dưới dạng yếu, nghĩa là nếu không còn tham chiếu nào khác đến một key, thì key đó cùng với giá trị của nó có thể được thu hồi bộ nhớ tự động. Điều này giúp quản lý bộ nhớ hiệu quả, đặc biệt trong các ứng dụng lớn và phức tạp, giảm thiểu rủi ro rò rỉ bộ nhớ.
WeakMap thường được sử dụng để lưu trữ dữ liệu riêng tư của đối tượng mà không làm ô nhiễm không gian tên toàn cục hoặc đối tượng mà bạn đang làm việc với. Bởi vì các key trong WeakMap không thể được liệt kê, điều này giúp giữ cho dữ liệu trở nên "yếu" và không thể truy cập từ bên ngoài, tăng cường tính bảo mật và đóng gói.
WeakMap có thể được sử dụng như một cơ chế "điều kiện gác" (guard) cho các đối tượng, cho phép bạn kiểm tra xem một đối tượng có đáp ứng một số điều kiện hoặc có trong một tập hợp nhất định mà không cần giữ một tham chiếu mạnh đến đối tượng đó. Điều này hữu ích trong việc quản lý các tài nguyên hoặc thực thể mà bạn không muốn ngăn chặn việc thu hồi bộ nhớ.
WeakMap cho phép bạn lưu trữ dữ liệu liên kết với một đối tượng mà không cần phải sửa đổi đối tượng đó. Điều này rất hữu ích khi bạn cần thêm dữ liệu hoặc trạng thái vào các đối tượng mà bạn không kiểm soát, chẳng hạn như các đối tượng từ thư viện bên thứ ba.
Trong một số trường hợp, việc sử dụng WeakMap có thể giúp tối ưu hóa hiệu suất bằng cách giảm thiểu việc giữ các tham chiếu không cần thiết đến các đối tượng, từ đó giúp bộ thu gom rác (garbage collector) hoạt động hiệu quả hơn.
WeakMap trong ES6 mang lại một số lợi ích quan trọng liên quan đến quản lý bộ nhớ, bảo mật, và kiến trúc ứng dụng. Sự khác biệt chính của nó so với Map là khả năng giữ các key dưới dạng yếu, giúp giảm thiểu rủi ro rò rỉ bộ nhớ và tạo điều kiện cho việc lưu trữ dữ liệu riêng tư hoặc liên kết với đối tượng một cách hiệu quả.
Trong JavaScript hàm map có thể lặp qua các thuộc tính của một đối tượng không?
Trong JavaScript, không có hàm map được tích hợp sẵn để trực tiếp lặp qua các thuộc tính của một đối tượng như khi bạn lặp qua các phần tử của một mảng. Tuy nhiên, bạn có thể đạt được chức năng tương tự bằng cách sử dụng một sự kết hợp của các phương thức khác và kỹ thuật xử lý đối tượng.
Có hai cách chính để lặp qua các thuộc tính của một đối tượng và áp dụng một hàm lên chúng, tạo ra một kết quả tương tự như khi sử dụng map với mảng:
Sử dụng Object.entries() kết hợp với map(): Bạn có thể chuyển đổi đối tượng thành một mảng các cặp [key, value] bằng cách sử dụng Object.entries(), sau đó áp dụng map() lên mảng đó. Cuối cùng, bạn có thể chuyển mảng kết quả trở lại thành một đối tượng mới nếu cần.
Ví dụ:
const obj = { key1: 'value1', key2: 'value2', key3: 'value3' };
const result = Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, value.toUpperCase()])
);
console.log(result); // { key1: 'VALUE1', key2: 'VALUE2', key3: 'VALUE3' }
Sử dụng vòng lặp for...in hoặc các phương thức Object.keys()/Object.values() kết hợp với forEach(): Bạn cũng có thể lặp qua các khóa (keys) hoặc giá trị (values) của đối tượng và áp dụng một hàm lên chúng. Điều này không trực tiếp tạo ra một mảng mới như map(), nhưng bạn có thể tự tạo một đối tượng mới từ kết quả.
Ví dụ sử dụng Object.keys() và forEach():
const obj = { key1: 'value1', key2: 'value2', key3: 'value3' };
const result = {};
Object.keys(obj).forEach(key => {
result[key] = obj[key].toUpperCase();
});
console.log(result); // { key1: 'VALUE1', key2: 'VALUE2', key3: 'VALUE3' }
Mặc dù JavaScript không cung cấp một hàm map dành riêng cho đối tượng, nhưng bằng cách sử dụng các phương thức và kỹ thuật trên, bạn có thể dễ dàng lặp qua và biến đổi các thuộc tính của đối tượng một cách linh hoạt.
Temporal Dead Zone trong Javascript là gì?
Temporal Dead Zone (TDZ) là một khái niệm trong JavaScript, đặc biệt là trong phiên bản ES6, liên quan đến việc sử dụng các từ khóa let và const để khai báo biến. TDZ mô tả trạng thái mà trong đó các biến không thể truy cập được, mặc dù chúng đã nằm trong phạm vi (scope) nhưng chưa được khai báo hoàn chỉnh
let và const tồn tại trong TDZ từ đầu phạm vi chứa chúng cho đến khi chúng được khai báo hoàn toàn. Trong thời gian này, mọi cố gắng truy cập biến sẽ dẫn đến lỗi ReferenceErrorconsole.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 5;
Trong ví dụ trên, biến a đang nằm trong TDZ tại thời điểm console.log(a) được thực thi, do đó dẫn đến ReferenceError.
varBiến được khai báo bằng var không nằm trong TDZ. Thay vào đó, chúng được "hoisted" (nâng lên đầu phạm vi chứa chúng) và khởi tạo với giá trị undefined, cho phép truy cập trước khi khai báo mà không gây ra lỗi
TDZ là một phần quan trọng của cơ chế hoạt động của let và const trong JavaScript ES6, giúp ngăn chặn các lỗi tiềm ẩn do truy cập biến trước khi chúng được khởi tạo. Hiểu rõ về TDZ giúp lập trình viên viết mã nguồn chính xác và dễ bảo trì hơn.
Prototype Inheritance trong JavaScript là gì?
Prototype Inheritance trong JavaScript là một cơ chế cho phép một đối tượng kế thừa các thuộc tính và phương thức từ một đối tượng khác. Trong JavaScript, mọi đối tượng đều có một thuộc tính nội bộ gọi là [[Prototype]], thường được truy cập thông qua __proto__ hoặc các phương thức Object.getPrototypeOf() và Object.setPrototypeOf().
Khi bạn truy cập một thuộc tính hoặc phương thức của một đối tượng, JavaScript sẽ tìm kiếm thuộc tính hoặc phương thức đó trên chính đối tượng đó. Nếu không tìm thấy, JavaScript sẽ tìm kiếm trên prototype của đối tượng, và quá trình này sẽ tiếp tục lên chuỗi prototype cho đến khi tìm thấy hoặc đến cuối chuỗi.
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log('Hello, my name is ' + this.name);
};
var alice = new Person('Alice');
alice.greet(); // "Hello, my name is Alice"
Trong ví dụ trên, alice là một thể hiện của Person. greet không phải là một phương thức trực tiếp của alice, nhưng vì nó được định nghĩa trên Person.prototype, alice có thể truy cập nó thông qua chuỗi prototype.
Tóm lại, Prototype Inheritance là một phần cốt lõi của JavaScript, cung cấp khả năng kế thừa và tái sử dụng mã một cách hiệu quả. Hiểu rõ về prototype là quan trọng để viết mã JavaScript hiệu quả và bảo trì dễ dàng.
Giải thích về Hoisting trong Javascript?
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;
Currying trong Javascript là gì?
Currying trong JavaScript là một kỹ thuật chuyển đổi một hàm có nhiều đối số thành một chuỗi các hàm, mỗi hàm chỉ nhận một đối số. Kỹ thuật này được sử dụng để tạo ra một phiên bản mới của hàm ban đầu, cho phép một số đối số của hàm được cố định trước khi hàm được gọi.
Currying hoạt động bằng cách tạo ra một chuỗi các hàm, mỗi hàm nhận một đối số và trả về một hàm mới cho đến khi tất cả các đối số đã được cung cấp. Cuối cùng, kết quả của hàm gốc sẽ được tính toán với tất cả các đối số đã được cung cấp.
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5);
console.log(add5(3)); // 8
Trong ví dụ trên, hàm add được "curried" để tạo ra một hàm mới add5, hàm này đã cố định giá trị của đối số đầu tiên là 5. Khi gọi add5 với một đối số, nó sẽ thêm đối số đó vào 5.
Tóm lại, currying là một kỹ thuật mạnh mẽ trong JavaScript, giúp tạo ra các hàm linh hoạt và tái sử dụng mã. Tuy nhiên, cần cân nhắc khi sử dụng để tránh làm tăng độ phức tạp không cần thiết.
Khi nào bạn sẽ sử dụng import * as X from 'X'?
Bạn sẽ sử dụng cú pháp import * as X from 'X' trong các trường hợp sau:
import * as X giúp bạn duy trì một namespace rõ ràng cho tất cả các thành phần được nhập từ module 'X', giúp bạn dễ dàng xác định nguồn gốc của chúng khi đọc hoặc gỡ lỗi mã nguồn.import * as X có thể giúp bạn dễ dàng quản lý và sử dụng các thành phần này mà không cần phải nhập từng thành phần một cách riêng lẻ.Tuy nhiên, cần lưu ý rằng việc sử dụng import * as X có thể làm cho mã nguồn trở nên khó đọc hơn nếu bạn nhập quá nhiều thành phần và không sử dụng chúng. Ngoài ra, việc sử dụng cú pháp này cũng có thể làm tăng khả năng xung đột tên nếu các module khác nhau có các thành phần với cùng tên.
Thuật ngữ transpiling trong Javascript là gì?
Transpiling trong JavaScript là quá trình chuyển đổi mã từ một ngôn ngữ hoặc phiên bản ngôn ngữ này sang một ngôn ngữ hoặc phiên bản khác mà vẫn giữ nguyên hoặc tương đương về chức năng. Trong bối cảnh của JavaScript, transpiling thường được sử dụng để chuyển đổi mã JavaScript được viết bằng các phiên bản mới hơn của ngôn ngữ (ví dụ: ES6/ES2015 trở lên) sang một phiên bản tương thích với các trình duyệt hoặc môi trường thực thi cũ hơn.
Transpilers, như Babel hoặc TypeScript, đọc mã nguồn được viết bằng một phiên bản hoặc ngôn ngữ nhất định và chuyển đổi nó thành mã nguồn tương đương hoặc tương tự trong một phiên bản hoặc ngôn ngữ khác. Quá trình này thường bao gồm việc chuyển đổi cú pháp mới thành cú pháp cũ hơn mà các trình duyệt hoặc môi trường thực thi hiện tại có thể hiểu và thực thi.
Một ví dụ điển hình của transpiling là sử dụng Babel để chuyển đổi mã JavaScript ES6+ thành mã ES5, giúp mã có thể chạy trên các trình duyệt cũ không hỗ trợ ES6+:
// Mã ES6+
const greet = (name) => console.log(`Hello, ${name}!`);
// Sau khi transpiling, mã trở thành ES5
var greet = function(name) {
return console.log("Hello, " + name + "!");
};
Tóm lại, transpiling là một công cụ quan trọng trong phát triển web hiện đại, giúp tận dụng các tính năng mới của JavaScript và các ngôn ngữ khác mà vẫn đảm bảo mã có thể chạy trên một phạm vi rộng lớn các môi trường thực thi.
Mô tả module design pattern trong JavaScript?
module design pattern trong JavaScript là một mẫu thiết kế được sử dụng để cải thiện khả năng bảo trì và tái sử dụng mã bằng cách tạo ra quyền truy cập công cộng và riêng tư. Mẫu thiết kế này giúp bảo vệ giá trị bên trong một module khỏi bị truy cập từ các phạm vi khác, được gọi là đóng gói (encapsulation).
Module trong JavaScript giúp chia nhỏ một file lớn thành nhiều phần nhỏ hơn, có thể tái sử dụng. Các giá trị bên trong module mặc định được giữ kín, không thể bị sửa đổi từ bên ngoài, trừ khi chúng được xuất khẩu rõ ràng bằng từ khóa export. Điều này giúp bảo vệ các giá trị khỏi bị rò rỉ vào phạm vi toàn cục hoặc xảy ra xung đột tên.
Mẫu thiết kế module cung cấp một cách để có cả phần tử công cộng và riêng tư với từ khóa export, giúp mã nguồn dễ bảo trì và tái sử dụng hơn. Mẫu thiết kế module tương tự như một biểu thức hàm được gọi ngay lập tức (IIFE - immediately invoked functional expression), nhưng một module luôn trả về một đối tượng thay vì một hàm.
Một ví dụ về mẫu thiết kế module là việc tạo ra một module với các biến và hàm riêng tư, sau đó trả về một đối tượng công cộng mà chỉ bao gồm các biến và phương thức mà chúng ta muốn tiết lộ. Các biến hoặc phương thức không được trả về trong đối tượng này sẽ không thể truy cập từ bên ngoài module, giúp giữ các chi tiết triển khai ẩn đi và chỉ cung cấp một API công cộng cho các phần khác của ứng dụng sử dụng.
Mẫu thiết kế module cũng giúp giảm thiểu khả năng xung đột tên hàm với các hàm khác được định nghĩa trong các script khác trên trang, do các hàm và biến được bảo vệ trong phạm vi của module.
Tóm lại, mẫu thiết kế module trong JavaScript giúp tổ chức và cấu trúc mã nguồn một cách tốt hơn, tạo ra các đơn vị mã độc lập giúp dễ dàng bảo trì và mở rộng, đồng thời giữ cho mã nguồn sạch sẽ và dễ quản lý.
Higher-Order Function trong Javascript là gì?
Higher-Order Function trong JavaScript là một hàm có khả năng nhận một hoặc nhiều hàm khác làm đối số hoặc trả về một hàm như là kết quả của nó. Điều này cho phép các chương trình có thể tạo ra các hàm tùy chỉnh dựa trên các hàm khác, tăng cường khả năng tái sử dụng và modular hóa mã.
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, console.log); // In ra 0, 1, 2
Trong ví dụ trên, repeat là một higher-order function vì nó nhận console.log (một hàm) làm đối số.
function greaterThan(n) {
return m => m > n;
}
let greaterThan10 = greaterThan(10);
console.log(greaterThan10(11)); // true
Ở đây, greaterThan là một higher-order function vì nó trả về một hàm mới, hàm này kiểm tra giá trị đầu vào có lớn hơn n hay không.
Tóm lại, higher-order functions là một công cụ mạnh mẽ trong JavaScript, giúp tạo ra các chương trình linh hoạt, tái sử dụng mã và hỗ trợ lập trình hàm.
Làm thế nào để ngăn chặn Callback Hell mà không sử dụng promises, async hoặc generators?
Để ngăn chặn Callback Hell mà không sử dụng promises, async hoặc generators, bạn có thể áp dụng một số kỹ thuật sau:
async: Mặc dù không phải là promises, async hoặc generators, nhưng module async cung cấp các hàm giúp quản lý luồng xử lý bất đồng bộ mà không cần lồng ghép callback. Ví dụ, async.waterfall hoặc async.series cho phép bạn thực hiện các tác vụ tuần tự mà không cần lồng ghép.Những kỹ thuật này giúp bạn quản lý mã nguồn bất đồng bộ một cách hiệu quả hơn, giảm thiểu sự phức tạp và tăng cường khả năng bảo trì và mở rộng mã nguồn.
JavaScript truyền tham số theo kiểu tham chiếu (pass by references) hay theo kiểu tham trị (pass by values)?
JavaScript truyền tham số theo kiểu tham trị (pass by value). Tuy nhiên, khi chúng ta truyền đối tượng (bao gồm cả mảng và hàm) vào một hàm, thực chất chúng ta truyền địa chỉ tham chiếu của đối tượng đó như một giá trị. Điều này có nghĩa là bất kỳ thay đổi nào đối với thuộc tính của đối tượng trong hàm sẽ ảnh hưởng đến đối tượng gốc, vì cả hai đều tham chiếu đến cùng một vị trí trong bộ nhớ.
Các kiểu dữ liệu nguyên thủy như số (number), chuỗi (string), boolean, null, undefined và symbol được truyền theo giá trị. Khi bạn gán một biến có kiểu dữ liệu nguyên thủy cho một biến khác, JavaScript lưu trữ giá trị trực tiếp và bất kỳ thay đổi nào đối với một biến không ảnh hưởng đến biến kia.
Mặt khác, các kiểu dữ liệu tham chiếu như đối tượng (object), mảng (array) và hàm (function) khi được gán từ một biến này sang biến khác, JavaScript không lưu trữ giá trị thực sự mà là một tham chiếu đến giá trị đó. Bất kỳ thay đổi nào thông qua tham chiếu này sẽ ảnh hưởng đến giá trị gốc vì chúng cùng tham chiếu đến cùng một nơi trong bộ nhớ.
Ví dụ, khi bạn truyền một biến kiểu nguyên thủy vào một hàm, một bản sao của giá trị được tạo ra và sử dụng trong hàm. Bất kỳ thay đổi nào đối với giá trị này trong hàm không ảnh hưởng đến biến gốc. Tuy nhiên, khi bạn truyền một đối tượng vào một hàm, tham chiếu đến đối tượng được truyền đi. Bất kỳ thay đổi nào đối với đối tượng trong hàm sẽ phản ánh trên đối tượng gốc.