Bộ câu hỏi phỏng vấn Javascript phần 4

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.

Hoisting trong Javascript

Đ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;

Cách để lặp qua các thuộc tính đối tượng trong Javascript?


Cách thứ nhất: sử dụng for...in, tuy nhiên, điều này cũng sẽ lặp lại qua các thuộc tính kế thừa của nó và chúng ta có thể thêm một lệnh kiểm tra obj.hasOwnProperty(property) trước khi sử dụng nó.

for (var property in obj) {
  console.log(property);
}

Cách thứ hai: sử dụng Object.keys() là một phương thức tĩnh sẽ liệt kê tất cả các thuộc tính có thể liệt kê của đối tượng mà bạn truyền nó vào.

Object.keys(obj).forEach(function (property) {
  ...
})

Cách thứ ba: sử dụng Object.getOwnPropertyNames() là một phương thức tĩnh sẽ liệt kê tất cả các thuộc tính có thể liệt kê và không thể liệt kê của đối tượng mà bạn truyền nó.

Object.getOwnPropertyNames(obj).forEach(function (property) {
  ...
})

Cách thứ tư: sử dụng Object.entries() để tạo một mảng với tất cả các thuộc tính có thể liệt kê của nó và lặp qua.

Object.entries(items).map((item) => {
  ...
});
Object.entries(items).forEach((item) => {
  ...
});
for (const item of Object.entries(items)) {
  ...
}

Cách lặp các phần tử của mảng trong Javascript?


Cách thứ nhất: sử dụng for, cạm bẫy phổ biến ở đây là var nằm trong phạm vi hàm chứ không phải phạm vi khối và hầu hết bạn muốn biến i thuộc phạm vi khối. ES2015 giới thiệu let có phạm vi khối và bạn nên sử dụng nó để thay thế. Vì - vậy, điều này trở thành

for (let i = 0; i <arr.length; i ++) {
  ...for (let i = 0; i <arr.length; i ++)
}

Cách thứ 2: sử dụng forEach, cấu trúc này đôi khi có thể thuận tiện hơn vì bạn không phải sử dụng index nếu tất cả những gì bạn cần là các phần tử mảng. Ngoài ra còn có các phương thức everysome sẽ cho phép bạn kết thúc - sớm quá trình lặp.

arr.forEach(function (el, index) {
  ...
}).

Tuy vậy, vòng lặp for cho phép linh hoạt hơn, chẳng hạn như kết thúc sớm vòng lặp bằng cách sử dụng break hoặc tăng vòng lặp nhiều hơn.

Higher-Order Function trong Javascript là gì?


Một higher-order function là một hàm nhận một hoặc nhiều hàm làm đối số, hàm này sử dụng để hoạt động trên một số dữ liệu hoặc trả về một hàm khác. Các higher-order functions có nghĩa là trừu tượng hóa một số hoạt động được thực hiện lặp đi lặp lại.

Ví dụ cổ điển về điều này là map, nhận một mảng và một hàm làm đối số, sau đó map sử dụng hàm này để biến đổi từng item trong mảng, trả về một mảng mới với dữ liệu đã biến đổi. Các ví dụ phổ biến khác trong JavaScript là forEach, filterreduce...

Một higher-order function không nhất thiết chỉ được thao tác với các mảng mà còn có nhiều trường hợp sử dụng để trả về một hàm từ một hàm khác, ví dụ như Function.prototype.bind trong JavaScript.

Ví dụ 1: chúng ta có một mảng các số và chúng ta muốn tạo một mảng mới gấp đôi mỗi giá trị của mảng đầu tiên. Hãy để xem làm thế nào chúng ta có thể giải quyết vấn đề có và không có Higher-Order Functions.

Không Higher-order function

const arr1 = [1, 2, 3];
const arr2 = [];
for (let i = 0; i < arr1.length; i++) {
  arr2.push(arr1[i] * 2);
}
// prints [ 2, 4, 6 ]
console.log(arr2);

Có Higher-order function

const arr1 = [1, 2, 3];
const arr2 = arr1.map(function (item) {
  return item * 2;
});
console.log(arr2);

Chúng ta cũng có thể làm code ngắn hắn bằng cách sử dụng cú pháp arrow function:

const arr1 = [1, 2, 3];
const arr2 = arr1.map(item => item * 2);
console.log(arr2);

Thuộc tính NaN trong JavaScript là gì?


Thuộc tính NaN biểu diễn một giá trị Not-a-Number. Nó biểu thị một giá trị không phải là số.

typeof của NaN trả về Number. Muốn kiểm tra một giá trị có phải NaN không, có thể dùng hàm isNaN().

Ví dụ:

isNaN("Hello"); // Returns true
isNaN(345); // Returns false
isNaN("1"); // Returns false, since '1' is converted to Number type which results in 0 ( a number)
isNaN(true); // Returns false, since true converted to Number type results in 1 ( a number)
isNaN(false); // Returns false
isNaN(undefined); // Returns true

IIFE là gì trong JavaScript?


Immediately Invoked Function (IIFE) là một hàm được chạy ngay sau khi nó được định nghĩa.

Cú pháp của IIFE:

(function () {
  // Do something;
})();

Để hiểu về IIFE, trước hết cần hiểu về hai dấu ngoặc đơn được thêm vào để tạo IIFE.

Dấu ngoặc đơn đầu tiên:

(function () {
  //Do something;
});

Khi thực thi code javascript, bất cứ khi nào trình biên dịch bắt gặp từ "function" nó cũng sẽ cho rằng ta đang khai báo một function. Do đó, nếu ta không dùng dấu ngoặc đơn, trình biên dịch sẽ báo lỗi vì nó nghĩa ta đang khái báo một function và theo cú pháp thì các function buộc phải có tên.

Để tránh lỗi ta thêm dấu ngoặc đơn vào để trình biên dịch biết đây không phải là khai báo function mà là một biểu thức function.

Dấu ngoặc đơn thứ hai:

(function () {
  //Do something;
})();

Từ định nghĩa IIFE, ta biết rằng code sẽ chạy ngay sai khi khai báo. Một function chỉ chạy khi nó được gọi. Nếu ta không gọi, thì chúng ta chỉ nhận về được khai báo hàm.

Do đó để gọi function ta sử dụng dấu ngoặc đơn thứ hai.

Bạn nghĩ gì về AMD (Asynchronous Module Definition) và CommonJS?


Mặc dù JavaScript không hỗ trợ các module, nhưng cộng đồng các developer đã cố gắng tìm ra cách để làm việc này. Sau một thời gian phát triển, thì hiện nay có một số phương thức module hóa như sau:

  • The Module pattern
  • CommonJS
  • Asynchronous Module Definition (AMD)

Bạn nghĩ gì về AMD (Asynchronous Module Definition) và CommonJS

Trong những phương án trên, module pattern không yêu cầu bất cứ một công cụ hay thư viện nào, nó có thể hoạt động ở mọi môi trường JavaScript. CommonJS hướng tới mục tiêu là JavaScript chạy ở server-side. AMD chính là phương thức rất phổ biến với những ứng dụng Web, và nó cũng là phương thức mà RequireJS sử dụng.

Cả hai đều là cách để triển khai một module system, vốn không có trong JavaScript cho đến khi ES2015 ra đời. CommonJS là đồng bộ trong khi AMD là bất đồng bộ. CommonJS được thiết kế với sự phát triển phía máy chủ trong khi AMD, hỗ trợ tính năng tải các modules một cách bất đồng bộ, dành cho trình duyệt nhiều hơn.

Cú pháp AMD khá dài dòng và CommonJS gần với kiểu bạn viết câu lệnh import trong các ngôn ngữ khác. Hầu hết thời gian, tôi thấy AMD không cần thiết, bởi vì nếu bạn đưa tất cả JavaScript của mình vào một tệp gói được nối, bạn sẽ không được hưởng lợi từ các thuộc tính tải bất đồng bộ. Ngoài ra, cú pháp CommonJS gần với phong cách viết module của Node hơn và có ít chi phí chuyển đổi ngữ cảnh hơn khi chuyển đổi giữa phát triển JavaScript phía máy khách và phía máy chủ.

Thật vui vì với các modules ES2015, có hỗ trợ cả tải đồng bộ và bất đồng bộ, cuối cùng chúng ta có thể chỉ cần bám vào một cách tiếp cận. Mặc dù nó chưa được triển khai hoàn toàn trong các trình duyệt và trong Node, nhưng chúng ta luôn có thể sử dụng các bộ chuyển mã để chuyển đổi mã của mình.

Hạn chế của phương thức private trong JavaScript là gì?


Một trong những hạn chế của phương thức private trong JavaScript là chúng rất tốn kém bộ nhớ vì một bản sao mới của phương thức sẽ được tạo cho mỗi trường hợp.

var Employee = function (name, company, salary) {
  this.name = name || ""; //Public attribute default value is null
  this.company = company || ""; //Public attribute default value is null
  this.salary = salary || 5000; //Public attribute default value is null

  // Private method
  var increaseSalary = function () {
    this.salary = this.salary + 1000;
  };

  // Public method
  this.dispalyIncreasedSalary = function () {
    increaseSalary();
    console.log(this.salary);
  };
};

// Create Employee class object
var emp1 = new Employee("John", "Pluto", 3000);
// Create Employee class object
var emp2 = new Employee("Merry", "Pluto", 2000);
// Create Employee class object
var emp3 = new Employee("Ren", "Pluto", 2500);

Ở đây mỗi biến emp1, emp2, emp3 đều có bản sao riêng của phương thức private increaseSalary. Vì vậy, không nên sử dụng phương thức private trừ khi nó cần thiết.

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:

  • Domain (tên miền)
  • Protocol (giao thức)
  • Port (cổng kết nối)

Nói đơn giản thì request sẽ được coi là hợp lệ chỉ khi nó thỏa mãn 3 tiêu chí ở trên (cùng domain,cùng protocol và cùng port)

Giải thích chính sách same-origin trong JavaScript

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) :

Sự khác biệt giữa các Host objects và Native objects là gì?


  • Native objects là một phần của ngôn ngữ JavaScript được xác định bởi đặc tả ECMAScript, chẳng hạn như String, Math, RegExp, Object, Function, ...
  • Host objects được cung cấp bởi môi trường runtime (trình duyệt hoặc Node), chẳng hạn như window, XMLHTTPRequest,...

Kết quả đoạn code sau là gì?

console.log(String.raw`Hello\nworld`);
  • A: Hello world!
  • B: Hello
         world
  • C: Hello\nworld
  • D: Hello\n
         world

Đáp án: C

String.raw trả về chuỗi nguyên bản, các ký tự (\n, \v, \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:\Documents\Projects\table.html`

Sẽ cho ta chuỗi là:

"C:DocumentsProjects able.html"

Với String.raw, nó sẽ trả về là:

C:\Documents\Projects\table.html

Do đó, trong trường hợp này Hello\nworld sẽ được ghi ra.

Kết quả đoạn code sau là gì?

async function getData() {
  return await Promise.resolve("I made it!");
}

const data = getData();
console.log(data);
  • A: "I made it!"
  • B: Promise {<resolved>: "I made it!"}
  • C: Promise {<pending>}
  • D: undefined

Đáp án: C

Một hàm async luôn luôn trả về một promise. await sẽ chờ cho tới khi promise đó được hoàn thành: một pending promise sẽ được trả về khi ta gọi getData() bằng cách gán nó cho biến data.

Nếu ta muốn truy cập giá trị đã hoàn thành của promise, trong trường hợp này là "I made it", ta có thể sử dụng hàm .then() ngay sau data như sau:

data.then(res => console.log(res))

Khi này nó sẽ ghi ra "I made it!"

Kết quả đoạn code sau là gì?

function addToList(item, list) {
  return list.push(item);
}

const result = addToList("apple", ["banana"]);
console.log(result);
  • A: ['apple', 'banana']
  • B: 2
  • C: true
  • D: undefined

Đáp án: B

Hàm .push() trả về độ dài của mảng mới! Trước đó, mảng chỉ hồm một phần tử là "banana" và có độ dài là 1. Sau khi thêm chuỗi "apple" vào mảng, mảng lúc này có hai chuỗi và có độ dài là 2. Do đó hàm addToList sẽ trả về 2.

Hàm push sẽ thay đổi chính bản thân mảng truyền vào. Do đó nếu chúng ta muốn trả về mảng thay vì chỉ trả về độ dài, chúng ta nên trả về trực tiếp mảng list sau khi đã thêm item vào đó.

Avatar Techmely Team
VIẾT BỞI

Techmely Team