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

Sự khác biệt giữa null, undefined hoặc undeclared là gì?


Các biến undeclared (chưa được khai báo) được tạo khi bạn gán một giá trị cho một identifier chưa được tạo trước đó bằng cách sử dụng var, let hoặc const. Các biến undeclared sẽ được xác định trên toàn bộ, bên ngoài của phạm vi hiện tại (current scope). Trong strict mode, một ReferenceError sẽ được ném ra khi bạn cố gắng gán tới một biến undeclared. Tránh chúng bằng mọi giá! Để kiểm tra chúng, hãy bọc chúng trong một khối try / catch.

function foo() {
  x = 1; // Throws a ReferenceError in strict mode
}

foo();
console.log(x); // 1

Một biến là undefined là một biến đã được khai báo, nhưng không được gán giá trị. Nếu một hàm không trả về bất kỳ giá trị nào được gán cho một biến, thì biến đó cũng có giá trị undefined. Để kiểm tra nó, hãy so sánh bằng cách sử dụng toán tử Strict Equality (===) hoặc typeof. Lưu ý rằng bạn không nên sử dụng toán tử Abstract Equality (==) để kiểm tra, vì nó cũng sẽ trả về true nếu giá trị là null.

var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === "undefined"); // true

console.log(foo == null); // true. Wrong, don't use this to check!

function bar() {}
var baz = bar();
console.log(baz); // undefined

Một biến là null nếu được gán cho một giá trị null. Nó đại diện cho không giá trị (no-value) và khác với undefined là nó đã được gán một cách rõ ràng. Để kiểm tra null, chỉ cần so sánh bằng cách sử dụng toán tử Strict Equality(===). Lưu ý rằng giống như ở trên, bạn không nên sử dụng toán tử Abstract equality(==) để kiểm tra, vì nó cũng sẽ trả về true nếu giá trị là undefined.

var foo = null;
console.log(foo === null); // true
console.log(typeof foo === "object"); // true

console.log(foo == undefined); // true. Wrong, don't use this to check!

Theo thói quen cá nhân, tôi không bao giờ để các biến của mình là undeclared hoặc unassigned. Tôi sẽ gán null cho chúng một cách rõ ràng sau khi khai báo nếu tôi chưa có ý định sử dụng nó. Nếu bạn sử dụng linter (ví dụ ESLint) trong quy trình làm việc của mình, nó sẽ thường kiểm tra rằng bạn đang không tham chiếu tới các biến undeclared.

Làm sao để kiểm tra xem một chuỗi có phải là chuỗi đẳng hình (isomorphic) hay không?


Để hai chuỗi là đẳng hình, tất cả các lần xuất hiện của một ký tự trong chuỗi A có thể được thay thế bằng một ký tự khác để được chuỗi B. Thứ tự của các ký tự phải được giữ nguyên. Phải có ánh xạ 1-1 cho từng ký tự của chuỗi A với mọi ký tự của chuỗi B.

  • paper và title sẽ trả về true.
  • egg và sad sẽ trở lại false.
  • dgg và add sẽ trả về true.
isIsomorphic("egg", "add"); // true
isIsomorphic("paper", "title"); // true
isIsomorphic("kick", "side"); // false

function isIsomorphic(firstString, secondString) {
  // Check if the same lenght. If not, they cannot be isomorphic
  if (firstString.length !== secondString.length) return false;
  var letterMap = {};
  for (var i = 0; i < firstString.length; i++) {
    var letterA = firstString[i],
      letterB = secondString[i];
    // If the letter does not exist, create a map and map it to the value
    // of the second letter
    if (letterMap[letterA] === undefined) {
      letterMap[letterA] = letterB;
    } else if (letterMap[letterA] !== letterB) {
      // Eles if letterA already exists in the map, but it does not map to
      // letterB, that means that A is mapping to more than one letter.
      return false;
    }
  }
  // If after iterating through and conditions are satisfied, return true.
  // They are isomorphic
  return true;
}

Nêu một số trường hợp KHÔNG nên sử dụng Arrow Functions trong ES6?


Các arrow functions KHÔNG nên được sử dụng:

  • Khi chúng ta muốn function hoisting - vì các Arrow Functions là ẩn danh.
  • Khi chúng ta muốn sử dụng this / arguments trong một hàm - vì các Arrow Functions không có this / arguments của riêng chúng, chúng phụ thuộc vào ngữ cảnh bên ngoài của chúng.
  • Khi chúng ta muốn sử dụng hàm được đặt tên (named function) - vì các Arrow Functions là ẩn danh.
  • Khi chúng ta muốn sử dụng hàm như một phương thức khởi tạo - vì các Arrow Functions không có chức năng này.
  • Khi chúng ta muốn thêm một thuộc tính là một hàm vào trong object literal và sử dụng đối tượng trong đó - vì chúng ta không thể truy cập vào điều này (mà phải là chính đối tượng).

Singleton Design Pattern trong Javascript như thế nào?


Đây là một design pattern vô cùng nổi tiếng, chúng ta sử dụng singleton pattern để hạn chế khởi tạo đối tượng, giảm bớt được khai báo đối tượng dư thừa, chỉ khởi tạo một lần duy nhất và có thể truy cập toàn cục. Đây sẽ là một pattern vô cùng hữu ích cho các trường hợp bạn phải xử lý 1 tác vụ ở nhiều nơi, có thể hạn chế được số lần khai báo đối tượng không cần thiết. Cùng thử xem ví dụ:

const utils = (() => {
  let instance;
  function initialize() {
    return {
      sum: function () {
        let nums = Array.prototype.slice.call(arguments);
        return nums.reduce((numb, total) => numb + total, 0);
      },
    };
  }
  return {
    getInstance: function () {
      // Nếu đối tượng này chưa được khởi tạo
      if (!instance) {
        // Khởi tạo lần đầu tiên
        instance = new initialize();
      }
      // Không khởi tạo nữa, chỉ trả về đối tượng đã khởi tạo
      return instance;
    },
  };
})();

const firstU = utils.getInstance(); // Cùng lấy 1 instance
const secondU = utils.getInstance(); // Cùng lấy 1 instance

console.log(firstU === secondU); // Trả về true là đúng vì cùng thuộc 1 instance duy nhất

console.log(firstU.sum(1, 2, 3, 4, 5)); // 15 // working

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

function getInfo(member, year) {
  member.name = "Lydia";
  year = "1998";
}

const person = { name: "Sarah" };
const birthYear = "1997";

getInfo(person, birthYear);

console.log(person, birthYear);
  • A: { name: "Lydia" }, "1997"
  • B: { name: "Sarah" }, "1998"
  • C: { name: "Lydia" }, "1998"
  • D: { name: "Sarah" }, "1997"

Đáp án: A

Đối số sẽ được đưa vào hàm dạng tham trị, trừ phi nó là object, khi đó nó sẽ được đưa vào hàm dạng tham chiếu. birthYear là dạng giá trị, vì nó là string chứ không phải object. Khi chúng ta đưa vào dạng giá trị, một bản sao của giá trị đó sẽ được tạo ra (xem thêm câu 46).

birthYear trỏ đến giá trị là "1997". Đối số year cũng sẽ rỏ đến giá trị "1997", nhưng giá trị này chỉ là một bản sao của giá trị mà birthYear trỏ tới mà thôi, hai giá trị đó hoàn toàn khác nhau. Do đó khi ta thay đổi giá trị year bằng "1998", chúng ta chỉ thay đổi giá trị của year mà thôi. birthYear sẽ vẫn giữ giá trị là "1997".

person là một object. Biến member có một tham chiếu tới cùng object mà person trỏ tới. Khi chúng ta thay đổi một thuộc tính của object mà member trỏ tới, giá trị của person cũng sẽ tự động thay đổi theo, vì chúng có chung tham chiếu. name của person khi này sẽ có giá trị mới là "Lydia".

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

function greeting() {
  throw "Hello world!";
}
function sayHi() {
try {
const data = greeting();
console.log("It worked!", data);
} catch (e) {
console.log("Oh no an error!", e);
}
}
sayHi();
  • A: "It worked! Hello world!"
  • B: "Oh no an error: undefined
  • C: SyntaxError: can only throw Error objects
  • D: "Oh no an error: Hello world!

Đáp án: D

Với lệnh throw, chúng ta có thể tạo ra các errors tùy ý. Với câu lệnh đó, chúng ta có thể throw các exception. Một exeption có thể là một chuỗi, một số, một boolean hoặc một object. Trong trường hợp này thì nó là chuỗi 'Hello world'.

Với lệnh catch chúng ta có thể xử lý những exeption được throw ra khi thực hiện try. Một exeption đã được throw ra: chuỗi 'Hello world'. e chính là chuỗi đó và chúng ta sẽ in ra. Kết quả là 'Oh an error: Hello world'.

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

function Car() {
  this.make = "Lamborghini";
  return { make: "Maserati" };
}

const myCar = new Car();
console.log(myCar.make);
  • A: "Lamborghini"
  • B: "Maserati"
  • C: ReferenceError
  • D: TypeError

Đáp án: B

Khi chúng ta trả về một thuộc tính, giá trị của thuộc tính bằng với giá trị đã được trả về bởi lệnh return, chứ không phải giá trị được set trong constructor. Chúng ta trả về giá trị là "Maserati", do đó myCar.make sẽ là "Maserati".

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

(() => {
  let x = (y = 10);
})();

console.log(typeof x);
console.log(typeof y);
  • A: "undefined", "number"
  • B: "number", "number"
  • C: "object", "number"
  • D: "number", "undefined"

Đáp án: A

let x = y = 10; chính là cách viết ngắn gọn của:

y = 10;
let x = y;

Khi ta set y bằng 10, thực tế chúng ta đã sử dụng biến global y (window nếu là trên browser, global nếu là môi trường Node).Trên browser, window.y sẽ là 10.

Sau đó, chúng ta khai báo giá trị của x với giá trị của y, tức 10. Tuy nhiên khi ta khai báo với từ khóa let biến x sẽ chỉ tồn tại trong block scoped; hay trong trường hợp này là hàm thực hiện ngay lập tức (immediately-invoked function - IIFE). Khi ta sử dụng phép toán typeof, x hoàn toàn chưa được định nghĩa: vì x lúc này nằm bên ngoài block nó được định nghĩa lúc trước. Nghĩa là xundefined. 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ả đoạn code sau là gì?

class Dog {
  constructor(name) {
    this.name = name;
  }
}

Dog.prototype.bark = function () {
  console.log(`Woof I am ${this.name}`);
};

const pet = new Dog("Mara");

pet.bark();

delete Dog.prototype.bark;

pet.bark();
  • A: "Woof I am Mara", TypeError
  • B: "Woof I am Mara","Woof I am Mara"
  • C: "Woof I am Mara", undefined
  • D: TypeError, TypeError

Đáp án: A

Chúng ta có thể xóa các thuộc tính khỏe object bằng từ khóa delete, kể cả với prototype. Khi chúng ta xóa một thuộc tính trên prototype, nó sẽ bị vô hiệu hóa hoàn toàn trong chuỗi prototype. Trong trường hợp này, hàm bark sẽ bị vô hiệu hóa ngay sau khi chúng ta thực hiện hàm xóa delete Dog.prototype.bark, tất nhiên ta vẫn có thể truy cập vào nó nhưng giá trị sẽ là undefined.

Khi chúng ta chạy một thứ không phải là hàm, nó sẽ bắn ra một TypeError. Trong trường hợp này là TypeError: pet.bark is not a function, vì bản thân thuộc tính pet.barkundefined.

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

const set = new Set([1, 1, 2, 3, 4]);

console.log(set);
  • A: [1, 1, 2, 3, 4]
  • B: [1, 2, 3, 4]
  • C: {1, 1, 2, 3, 4}
  • D: {1, 2, 3, 4}

Đáp án: D

Set là một tập hơp các giá trị không trùng nhau.

Chúng ta đưa đầu vào là một mảng [1, 1, 2, 3, 4] với giá trị 1 bị trùng. Giá trị trùng đó sẽ bị loại bỏ. Kết quả là {1, 2, 3, 4}.

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

// counter.js
let counter = 10;
export default counter;
// index.js
import myCounter from "./counter";

myCounter += 1;

console.log(myCounter);
  • A: 10
  • B: 11
  • C: Error
  • D: NaN

Đáp án: C

Một module khi được import sẽ là read-only: chúng ta sẽ không thể chỉnh sửa module đó, chỉ có bản thân module đó có thể chỉnh sửa giá trị của nó mà thôi.

Khi ta thay đổi giá trị cuả myCounter, nó sẽ throw ra một lỗi: myCounterread-only và không thể thay đổi.

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

const name = "Lydia";
age = 21;

console.log(delete name); console.log(delete age);

  • A: false, true
  • B: "Lydia", 21
  • C: true, true
  • D: undefined, undefined

Đáp án: A

Phép toán delete sẽ trả về một giá trị boolean: true nếu xóa thành công, false nếu thất bại. Tuy nhiên, nếu biến được khai báo với các từ khóa var, const hay let thì nó sẽ không thể bị xóa bởi phép toán delete.

Biến name được khai báo với từ khóa const, nên nó sẽ không thể bị xóa và trả về false. Khi ta set age bằng 21, thực tế là ta đang sử dụng biến global age. Ta có thể xóa sử dụng phép toán delete, khi này delete age trả về true.

```
Avatar Techmely Team
VIẾT BỞI

Techmely Team