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

Có thể reset một generator ES6 về state ban đầu của nó không?


Khi một generator bước sang trạng thái "completed", nó sẽ không bao giờ rời khỏi nó và execution context liên quan của nó sẽ không bao giờ được tiếp tục.

Bất kỳ trạng thái thực thi nào được liên kết với generator cũng có thể bị loại bỏ tại thời điểm này.

Javascript là ngôn ngữ pass-by-reference hay pass-by-value không?


Nó luôn pass-by-value, nhưng đối với các đối tượng, giá trị của biến đối tượng đó là một tham chiếu (reference). Do đó, khi bạn truyền một đối tượng và thay đổi các thành viên của nó, những thay đổi đó vẫn tồn tại bên ngoài hàm. Điều này làm cho nó giống như đi qua tham chiếu. Nhưng nếu bạn thực sự thay đổi giá trị của biến đối tượng, bạn sẽ thấy rằng thay đổi không tồn tại, chứng tỏ rằng nó thực sự chuyển qua giá trị. Ví dụ:

function changeStuff(a, b, c) {
  a = a * 10;
  b.item = "changed";
  c = { item: "changed" };
}
var num = 10;
var obj1 = { item: "unchanged" };
var obj2 = { item: "unchanged" };

changeStuff(num, obj1, obj2);

console.log(num);
console.log(obj1.item);
console.log(obj2.item);

Kết quả:

10;
changed;
unchanged;

Trong JavaScript, tại sao toán tử this không nhất quán?


Điều quan trọng nhất cần hiểu là một function object không có giá trị "this" cố định - giá trị của this thay đổi tùy thuộc vào cách hàm được gọi. Chúng ta nói rằng một hàm được gọi với một giá trị this cụ thể nào đó - giá trị this được xác định tại thời điểm gọi, không phải thời gian định nghĩa.

  • Nếu hàm được gọi là "raw" function (ví dụ: chỉ thực hiện someFunc()), thì this sẽ là Global object (window trong trình duyệt) (hoặc undefined nếu hàm chạy ở strict mode).
  • Nếu nó được gọi như một phương thức trong một đối tượng, thì this sẽ là đối tượng đang gọi.
  • Nếu bạn gọi một hàm bằng lệnh call hoặc apply, thì thisđược chỉ định làm đối số đầu tiên củacallhoặcapply`.
  • Nếu nó được gọi như là một Event listener, thì this sẽ là target element của sự kiện đó.
  • Nếu nó được gọi là một phương thức khởi tạo với new, this sẽ là một đối tượng mới được tạo có prototype được đặt thành prototype property của hàm khởi tạo.
  • Nếu hàm là kết quả của một hành động bind, thì hàm sẽ luôn và mãi mãi có this được gán thành đối số đầu tiên của lệnh bind đó. (Đây là ngoại lệ duy nhất của quy tắc "một function object không có giá trị this cố định" - các hàm được tạo bởi bind thực sự có this không thay đổi.)

Cho một ví dụ về curry và tại sao cú pháp này mang lại lợi ích?


Currying là một pattern trong đó một hàm có nhiều hơn một tham số được chia thành nhiều hàm, khi được gọi nối tiếp, sẽ tích lũy tất cả các tham số được yêu cầu cùng một lúc.

Kỹ thuật này có thể hữu ích để làm cho mã được viết theo kiểu functional dễ đọc và soạn thảo hơn. Điều quan trọng cần lưu ý là để một hàm được xử lý, nó cần bắt đầu như một hàm, sau đó được chia nhỏ thành một chuỗi các hàm mà mỗi hàm chấp nhận một tham số.

function curry(fn) {
  if (fn.length === 0) {
    return fn;
  }
  function _curried(depth, args) {
    return function (newArgument) {
      if (depth - 1 === 0) {
        return fn(...args, newArgument);
      }
      return _curried(depth - 1, [...args, newArgument]);
    };
  }
  return _curried(fn.length, []);
}

function add(a, b) {
  return a + b;
}

var curriedAdd = curry(add);
var addFive = curriedAdd(5);

var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]

Command Pattern trong Javascript như thế nào?


Mục đích của Command Pattern là đóng gói các hành động dưới dạng đối tượng. Cho phép phân tách hệ thống các đối tượng nhận yêu cầu với các đối tượng thực thi yêu cầu, có khả năng ghép nối. Trong đó, các yêu cầu được gọi là sự kiện và các mã để thực thi yêu cầu được gọi là trình xử lý sự kiện.

Nào bây giờ hãy tưởng tượng bạn nhận được yêu cầu xây dựng hệ thống thanh toán cho một cửa hàng thương mại điện tử. Tuỳ thuộc vào loại phương thức thanh toán, bạn sẽ chọn cho mình một quy trình cụ thể.

Ví dụ

if (paymentMethod === "creditcard") {
  // Xử lý thanh toán
}

Một số phương thức thanh toán chỉ cần một bước duy nhất, một số khác thì không. Dựa vào ví dụ trên, chúng ta sẽ thử xây dựng quy trình thanh toán.

Command Pattern là một giải pháp tốt để áp dụng trong ví dụ này. Cụ thể về ví dụ, hệ thống xử lý của bạn sẽ không biết nhiều thông tin về việc xử lý từng phương thức thanh toán như thế nào. Các yêu cầu xử lý thanh toán và nơi xử lý thanh toán sẽ tách biệt và được ghép nối lại từ hệ thống xử lý.

// Phần lõi điều hướng xử lý
function Command(operation) {
  this.operation = operation;
}

Command.prototype.execute = function () {
  this.operation.execute();
};

// Hàm phương thức thanh toán
function ProccessPaypalPayment() {
  return {
    execute: function () {
      console.log("Payment with Paypal");
    },
  };
}

// Hàm phương thức thanh toán
function ProccessCreditCardPayment() {
  return {
    execute: function () {
      console.log("Payment with Credit Card");
    },
  };
}

// Hàm xử lý thanh toán nhận yêu cầu và điều hướng xử lý thực thi
function PaymentHandler() {
  let paymentCommand;
  return {
    setPaymentCommand(command) {
      this.paymentCommand = command;
    },
    executeCommand() {
      this.paymentCommand.execute();
    },
  };
}

function main() {
  let systemPayment = new PaymentHandler();
  systemPayment.setPaymentCommand(new Command(new ProccessPaypalPayment()));
  systemPayment.executeCommand();
}

main();

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

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const member = new Person("Lydia", "Hallie");
Person.getFullName = function () {
  return `${this.firstName} ${this.lastName}`;
};

console.log(member.getFullName());
  • A: TypeError
  • B: SyntaxError
  • C: Lydia Hallie
  • D: undefined undefined

Đáp án: A

Chúng ta không thể add thêm một thuộc tính cho một constructor giống như một object thông thường. Nếu bạn muốn add thêm thuộc tính nào đó cho tất cả các object một lần, bạn phải dùng prototype. Trong trường hợp này cũng vậy.

Person.prototype.getFullName = function () {
  return `${this.firstName} ${this.lastName}`;
};

khi này member.getFullName() sẽ hoạt động. Tại sao nên làm vậy? Hãy thử thêm chúng trực tiếp vào constructor xem sao. Không phải mọi instance Person đều cần phương thức này. Nó sẽ dẫn tới việc lãng phí rất nhiều bộ nhớ, khi chúng đều phải lưu trữ thuộc tính này cho mỗi instance. Thay vì thế, nếu ta chỉ thêm chúng vào prototype, ta sẽ chỉ tốn bộ nhớ một lần mà thôi, và mọi object khác đều có thể truy cập đến nó!

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

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const lydia = new Person("Lydia", "Hallie");
const sarah = Person("Sarah", "Smith");

console.log(lydia);
console.log(sarah);
  • A: Person {firstName: "Lydia", lastName: "Hallie"}undefined
  • B: Person {firstName: "Lydia", lastName: "Hallie"}Person {firstName: "Sarah", lastName: "Smith"}
  • C: Person {firstName: "Lydia", lastName: "Hallie"}{}
  • D:Person {firstName: "Lydia", lastName: "Hallie"}ReferenceError

Đáp án: A

Với sarah, chúng ta khai báo mà không có từ khóa new. Khi sử dụng new, nó sẽ trỏ đến một object mới mà ta vừa tạo ra. Tuy nhiên nếu ta không dùng new thì nó sẽ trỏ tới global object!

Chúng ta cho rằng this.firstName"Sarah"this.lastName"Smith". Tuy nhiên sự thực là chúng ta đã định nghĩa global.firstName = 'Sarah'global.lastName = 'Smith'. Bản thân biến sarah vẫn là undefined.

3 giai đoạn của event propagation là gì?

  • A: Target > Capturing > Bubbling
  • B: Bubbling > Target > Capturing
  • C: Target > Bubbling > Capturing
  • D: Capturing > Target > Bubbling

Đáp án: D

Trong capturing phase, event được truyền từ các phần tử cha cho tới phần tử target. Sau khi tới được phần tử target thì bubbling sẽ bắt đầu.

Tất cả các object đều có prototypes.

  • A: đúng
  • B: sai

Đáp án: B

Tất cả các object đều có prototypes, ngoại trừ base object. Object base có thể truy cập đến vài methods và properties, ví dụ như .toString. Đó là lý do tại sao chúng ta có thể sử dụng được các built-in methods trong JavaScript! Tất cả các phương thức đó đều có trong prototype. Mặc dù JavaScript không thể tìm thấy chúng trong object một cách trực tiếp, nó sẽ được truyền xuống thông qua prototype chain và xuống tới object, tại đây chúng ta có thể truy cập được nó.

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

function sum(a, b) {
  return a + b;
}

sum(1, "2");
  • A: NaN
  • B: TypeError
  • C: "12"
  • D: 3

Đáp án: C

JavaScript là một ngôn ngữ dynamically typed: chúng ta không khai báo kiểu dữ liệu khi khai báo biến. Giá trị có thể bị tự động convert sang một kiểu dữ liệu khác mà ta không hề hay biết, điều này được gọi là implicit type coercion. Coercion có nghĩa là convert từ kiểu này sang kiểu khác.

Trong ví dụ này, JavaScript sẽ convert số 1 sang dạng string. Mỗi khi ta cộng một số (1) với một string ('2'), số sẽ luôn được xem như là một string. Kết quả sẽ là một phép nối chuỗi giống như "Hello" + "World", vậy nên "1" + "2" sẽ trả về là "12".

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

let number = 0;
console.log(number++);
console.log(++number);
console.log(number);
  • A: 1 1 2
  • B: 1 2 2
  • C: 0 2 2
  • D: 0 1 2

Đáp án: C

Khi phép toán ++ nằm ở đằng sau (postfix):

  1. Trả về giá trị (trả về 0)
  2. Tăng giá trị lên (number giờ là 1)

Khi phép toán ++ nằm ở đằng trước (prefix):

  1. Tăng giá trị lên (number giờ là 2)
  2. Trả về giá trị (trả về 2)

Vậy kết quả là 0 2 2.

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

function getPersonInfo(one, two, three) {
  console.log(one);
  console.log(two);
  console.log(three);
}

const person = "Lydia";
const age = 21;

getPersonInfo`${person} is ${age} years old`;
  • A: "Lydia" 21 ["", " is ", " years old"]
  • B: ["", " is ", " years old"] "Lydia" 21
  • C: "Lydia" ["", " is ", " years old"] 21

Đáp án: B

Nếu bạn dùng tagged template literals, giá trị của đối số đầu tiên luôn luôn là một mảng các string. Những đối số còn lại sẽ lấy giá trị từ biểu thức đưa vào!

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

function checkAge(data) {
  if (data === { age: 18 }) {
    console.log("You are an adult!");
  } else if (data == { age: 18 }) {
    console.log("You are still an adult.");
  } else {
    console.log(`Hmm.. You don't have an age I guess`);
  }
}

checkAge({ age: 18 });
  • A: You are an adult!
  • B: You are still an adult.
  • C: Hmm.. You don't have an age I guess

Đáp án: C

Khi test sự bằng nhau, các kiểu dữ liệu cơ bản sẽ so sánh giá trị của chúng, còn object thì so sánh tham chiếu. JavaScript sẽ kiểm tra xem các object đó có trỏ đến những vùng nhớ giống nhau hay không.

Hai object chúng ta đang so sánh không có được điều đó: object đối số tham chiếu đến một vùng nhớ khác với object chúng ta dùng để kiểm tra sự bằng nhau.

Đó là lý do tại sao cả { age: 18 } === { age: 18 }{ age: 18 } == { age: 18 } đều trả về false.

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

function getAge(...args) {
  console.log(typeof args);
}

getAge(21);
  • A: "number"
  • B: "array"
  • C: "object"
  • D: "NaN"

Đáp án: C

Spread operator (...args.) sẽ trả về một mảng các đối số. Mảng thực chất là một object, vậy nên typeof args sẽ trả về "object".

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

function getAge() {
  "use strict";
  age = 21;
  console.log(age);
}

getAge();
  • A: 21
  • B: undefined
  • C: ReferenceError
  • D: TypeError

Đáp án: C

Với "use strict", chúng ta sẽ đảm bảo được rằng ta sẽ không bao giờ khai báo biến global một cách vô ý. Tại đây chúng ta chưa khai báo biến age, và khi dùng "use strict", nó sẽ throw ra một reference error. Nếu như không dùng "use strict", nó sẽ vẫn hoạt động, vì thuộc tính age sẽ được thêm vào global object.

Avatar Techmely Team
VIẾT BỞI

Techmely Team