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

Prototype là gì?


Tất cả các đối tượng javascript đều kế thừa các thuộc tính từ một prototype.

Ví dụ:

Đối tượng Date kế thừa các thuộc tính từ prototype Date.

Đối tượng Math kế thừa các thuộc tính từ prototype Math.

Đối tượng Array kế thừa các thuộc tính từ prototype Array.

Trên đầu chuỗi là Object.prototype. Mọi prototype đều kế thừa các thuộc tính và phương thức từ Object.prototype.

Prototype là một bản thiết kế của một đối tượng. Prototype cho phép chúng ta sử dụng các thuộc tính và phương thức trên một đối tượng ngay cả khi các thuộc tính và phương thức không tồn tại trên đối tượng hiện tại.

prototype trong javascript

Ví dụ:

var arr = [];
arr.push(2);

console.log(arr); // Outputs [2]

Trong đoạn code trên, có thể thấy ta chưa xác định bất kỳ thuộc tính hoặc phương thức nào được gọi là push trên mảng arr nhưng javascript engine không đưa ra lỗi.

Lý do là việc sử dụng các prototype. Như đã thảo luận trước đây, các đối tượng Array kế thừa các thuộc tính từ prototype Array.

Javascript engine thấy rằng phương thức push không tồn tại trên đối tượng mảng hiện tại, do đó nó tìm kiếm phương thức push bên trong prototype Array và nó tìm thấy phương thức.

Bất cứ khi nào thuộc tính hoặc phương thức không được tìm thấy trên đối tượng hiện tại, javascript engine sẽ luôn tìm kiếm trong prototype của nó và nếu nó vẫn không tồn tại, nó sẽ tìm bên trong prototype của prototype, v.v.

Hoisting trong JavaScript là gì?


Hoisting là hành động của trình thông dịch JavaScript sẽ di chuyển tất cả các khai báo biến và hàm lên top của phạm vi hiện tại (current scope). Có hai loại hoisting:

  • variable hoisting - ít phổ biến.
  • function hoisting - phổ biến hơn

Bất cứ khi nào một var (hoặc khai báo hàm) xuất hiện bên trong một phạm vi, thì khai báo đó được coi là thuộc về toàn bộ phạm vi và có thể truy cập ở mọi nơi trong phạm vi đó.

var a = 2;
foo(); // works because `foo()`

// declaration is "hoisted"
function foo() {
  a = 3;
  console.log(a); // 3
  var a; // declaration is "hoisted"
  // to the top of `foo()`
}

console.log(a); // 2

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


Prototype Pattern là một object-based. Nó tạo ra một phiên bản mới của đối tượng dựa trên đối tượng nguyên mẫu. Mục tiêu chính là tạo ra một đối tượng sử dụng làm làm bản thiết kế cho mỗi đối tượng được tạo sau đó.

Nếu bạn cảm thấy khởi tạo một đối tượng mới trực tiếp quá phức tạp và không hiệu quả, trong trường hợp này, sử dụng Prototype Pattern là một cách ý tưởng không tồi.

Bạn có thể triển khai Prototype Pattern theo các như sau:

// Khởi tạo đối tượng
function Person(name, age) {
  this.name = name;
  this.age = age;

  this.showName = () => console.log(this.name);
}

// Clone prototype của đối tượng
function PersonPrototype(prototype) {
  const _prototype = prototype;

  this.clone = () => {
    let person = new Person();
    person.name = _prototype.name;
    person.age = _prototype.age;

    return person;
  };
}

const person = new Person("Codestus.com", 20);
const prototypeOfPerson = new PersonPrototype(person);
const tester = prototypeOfPerson.clone();

tester.showName(); // "Codestus.com"

Ngoài ra, bạn cũng có thể sử dụng khái niệm kế thừa prototype đã tích hợp sẵn trong đối tượng Object.

const person = {
  name: "Codestus.com",
  age: 20,
};

const personA = Object.assign({}, person);

Làm thế nào bạn có thể sử dụng code qua lại giữa các file?


Điều này phụ thuộc vào môi trường JavaScript.

Trên máy khách (trình duyệt web), miễn là các biến / hàm được khai báo trong phạm vi toàn cục (window), tất cả các tập lệnh đều có thể tham chiếu đến chúng. Ngoài ra, hãy áp dụng Asynchronous Module Definition (AMD) thông qua RequestJS để có cách tiếp cận module hơn.

Trên máy chủ (Node.js), cách phổ biến là sử dụng CommonJS. Mỗi file được xem như một module và nó có thể export các biến và hàm bằng cách gắn chúng vào đối tượng module.exports.

ES2015 (ES6) định nghĩa một cú pháp module với mục đích thay thế cả AMD và CommonJS, như vậy chúng sẽ được hỗ trợ trong cả trong môi trường trình duyệt và Node.

Những công dụng của WeakMap trong ES6 là gì?


WeakMap chỉ có trong ES6 trở lên. WeakMap cung cấp một cách thức để mở rộng các đối tượng từ bên ngoài mà không can thiệp vào việc thu gom rác (garbage collection). Bất cứ khi nào bạn muốn mở rộng một đối tượng nhưng không thể vì nó bị bịt kín - hoặc từ một nguồn bên ngoài - thì một WeakMap có thể được áp dụng.

WeakMap là một tập hợp các cặp khóa và giá trị, trong đó khóa phải là một đối tượng.

var map = new WeakMap();
var pavloHero = {
  first: "Pavlo",
  last: "Hero",
};
var gabrielFranco = {
  first: "Gabriel",
  last: "Franco",
};
map.set(pavloHero, "This is Hero");
map.set(gabrielFranco, "This is Franco");
console.log(map.get(pavloHero)); //This is Hero

Khía cạnh thú vị của WeakMap là nó giữ một tham chiếu yếu (weak reference) đến khóa bên trong map. Tham chiếu yếu có nghĩa là nếu đối tượng bị phá hủy, bộ thu gom rác (garbage collector) sẽ xóa toàn bộ entry khỏi WeakMap, do đó sẽ giải phóng bộ nhớ.

Giải thích sự khác nhau giữa hai cách sử dụng dưới đây

function Person() {}
var person = Person();
var person = new Person();

Câu hỏi này khá mơ hồ. Dự đoán tốt nhất của tôi về ý định của nó là nó đang hỏi về các hàm tạo trong JavaScript. Về mặt kỹ thuật, function Person() {} chỉ là một khai báo hàm bình thường. Quy ước là sử dụng PascalCase cho các hàm được sử dụng làm hàm tạo.

var person = Person() gọi Person dưới dạng một hàm chứ không phải là một hàm tạo. Gọi như vậy là một sai lầm phổ biến nếu hàm được dự định sử dụng như một phương thức khởi tạo. Thông thường, hàm tạo không trả về bất kỳ thứ gì, do đó việc gọi hàm tạo giống như một hàm bình thường sẽ trả về undefined và được gán cho biến dùng để làm instance.

var person = new Person() tạo một instance của đối tượng Person bằng toán tử new, toán tử này kế thừa từ Person.prototype. Một giải pháp thay thế là sử dụng Object.create, ví dụ: Object.create(Person.prototype).

function Person(name) {
  this.name = name;
}
var person = Person("John");
console.log(person); // undefined
console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined

var person = new Person("John");
console.log(person); // Person { name: "John" }
console.log(person.name); // "john"

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

const one = false || {} || null;
const two = null || false || "";
const three = [] || 0 || true;
console.log(one, two, three);
  • A: false null []

  • B: null "" true

  • C: {} "" []

  • D: null null true


Đáp án: C

Với phép toán ||, ta sẽ trả về giá trị truethy đầu tiên. Nếu tất cả đều là falsy, giá trị cuối cùng sẽ được trả về.

(false || {} || null): object rỗng {} là một giá trị truthy. Nó là giá trị truethy đầu tiên và duy nhất nên sẽ được trả về. Do đó one sẽ là {}.

(null || false || ""): Tất cả toán hạng đều là falsy. Có nghĩa là toán hạng cuối cùng "" sẽ được trả về. Do đó two sẽ là "".

([] || 0 || ""): mảng rỗng [] là một giá trị truthy. Nó là giá trị truthy đầu tiên nên sẽ được trả về. Do đó three sẽ là [].

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

const myPromise = () => Promise.resolve("I have resolved!");
function firstFunction() {
myPromise().then((res) => console.log(res));
console.log("second");
}
async function secondFunction() {
console.log(await myPromise());
console.log("second");
}
firstFunction();
secondFunction();
  • A: I have resolved!, secondI have resolved!, second

  • B: second, I have resolved!second, I have resolved!

  • C: I have resolved!, secondsecond, I have resolved!

  • D: second, I have resolved!I have resolved!, second


Đáp án: D

Có thể tưởng tượng đơn giản cách promise thực thi như sau: bây giờ tôi sẽ để tạm nó sang một bên vì nó tính toán mất thời gian. Chỉ khi nào nó được hoàn thành (resolved) hay bị hủy bỏ (rejected) hay khi call stack trở nên rỗng thì tôi sẽ lấy giá trị trả về ra.

Dù chúng ta có thể sử dụng giá trị thu được bằng cú pháp .then, hoặc sử dụng cặp cú pháp await/async, nhưng, cách chúng hoạt động là khác nhau.

Trong firstFunction, chúng ta đưa promise qua một bên chờ cho nó tính toán xong, và vẫn tiếp tục chạy những code tiếp sau đó, theo đó console.log('second') sẽ được chạy. Sau đó promise được hoàn thành trả về giá trị I have resolved, giá trị này sẽ được log ra khi call stack trở nên rỗng.

Với từ khóa await trong secondFunction, ta đã tạm dừng một hàm bất đồng bộ cho tới khi chúng trả về giá trị, sau đó ta mới đi tiếp đến các câu lệnh tiếp theo.

Do đó nó sẽ chờ cho tới khi myPromise được hoàn thành và trả về giá trị I have resolved, sau đó chúng ta sẽ chạy tiếp câu lệnh tiếp theo in ra second.

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

const set = new Set();
set.add(1);
set.add("Lydia");
set.add({ name: "Lydia" });
for (let item of set) {
  console.log(item + 2);
}
  • A: 3, NaN, NaN
  • B: 3, 7, NaN
  • C: 3, Lydia2, [object Object]2
  • D: "12", Lydia2, [object Object]2

Đáp án: C

Phép toán + không chỉ dùng để cộng các số, mà nó còn dùng để nối chuỗi nữa. Mỗi khi Javascript engine gặp một giá trị trong phép toán không phải dạng số, nó sẽ chuyển các số trong phép toán đó sang dạng chuỗi.

Phép toán đầu tiên item là một số 1, nên 1 + 2 trả về 3.

Ở phép toán thứ hai, item là một chuỗi "Lydia". trong khi đó 2 là một số, nên 2 sẽ bị chuyển sang dạng chuỗi, sau khi nối vào ta có chuỗi "Lydia2".

Ở phép toán thứ ba, { name: "Lydia" } là một object. Tuy nhiên dù có là object hay gì đi nữa thì nó cũng sẽ bị chuyển sang dạng chuỗi. Đối với object thì khi chuyển sang dạng chuỗi nó sẽ trở thành "[object Object]". "[object Object]" nối với "2" trở thành "[object Object]2".

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

Promise.resolve(5);
  • A: 5
  • B: Promise {<pending>: 5}
  • C: Promise {<fulfilled>: 5}
  • D: Error

Đáp án: C

Ta có thể truyền vào giá trị bất kì cho Promise.resolve, dù có là promise hay không promise. Bản thân nó sẽ là một hàm trả về một promise với giá trị đã được resolved.

Trong trường hợp này ta đưa vào giá trị 5. Nó sẽ trả về một resolved promise với giá trị 5.

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

function compareMembers(person1, person2 = person) {
  if (person1 !== person2) {
    console.log("Not the same!");
  } else {
    console.log("They are the same!");
  }
}
const person = { name: "Lydia" };
compareMembers(person);
  • A: Not the same!
  • B: They are the same!
  • C: ReferenceError
  • D: SyntaxError

Đáp án: B

Object sẽ được truyền vào hàm theo reference. Khi chúng ta nói so sánh strict equal (===), nghĩa là ta đang so sánh các reference của chúng.

Ta set giá trị mặc định của person2 là object person, và đưa object person vào làm giá trị cho đối số person1.

Điều đó có nghĩa là chúng cùng trỏ đến một object trong bộ nhớ, do đó chúng bằng nhau, và They are the same! được in ra.

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

const colorConfig = {
red: true,
blue: false,
green: true,
black: true,
yellow: false,
};
const colors = ["pink", "red", "blue"];
console.log(colorConfig.colors[1]);
  • A: true

  • B: false

  • C: undefined

  • D: TypeError


Đáp án: D

Trong Javascript ta có hai cách để truy cập thuộc tính của một object: sử dụng ngoặc vuông [], hoặc sử dụng chấm .. Trong trương hợp này chúng ta sử dụng chấm (colorConfig.colors) thay cho ngoặc vuông (colorConfig["colors"]).

Với cách sử dụng chấm, Javascript sẽ tìm kiếm một thuộc tính có tên chính xác như tên ta đưa vào. Trong trường hợp này nó là thuộc tính colors trong object colorConfig Tuy nhiên trong object này không có thuộc tính nào tên là colors, nên nó sẽ trả về undefined. Sau đó chúng ta cố truy cậ vào thuộc tính 1 của nó bằng cách gọi [1]. Chúng ta không thể làm như vậy trên giá trị undefined, nên nó sẽ trả về TypeError: Cannot read property '1' of undefined.

Javascript thông dịch theo câu lệnh. Khi ta sử dụng ngoặc vuông, Nnó sẽ tìm mở ngoặc đầu tiên [ và tiếp tục cho tới khi gặp đóng ngoặc tương ứng ]. Chỉ khi đó nó mới đánh giá câu lệnh. Nếu chúng ta sử dụng cú pháp colorConfig[colors[1]], nó sẽ trả về giá trị của thuộc tính red trong object colorConfig.

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

console.log("❤️" === "❤️");
  • A: true

  • B: false


Đáp án: A

Về cơ bản, emoji vẫn là các ký tự unicode mà thôi. Mã unicode cho hình trái tim là "U+2764 U+FE0F". Chúng luôn luôn là một, nên phép toán đơn giản trả về true.

Phép toán nào sau đây làm thay đổi mảng gốc?

const emojis = ["✨", "🥑", "😍"];
emojis.map((x) => x + "✨");
emojis.filter((x) => x !== "🥑");
emojis.find((x) => x !== "🥑");
emojis.reduce((acc, cur) => acc + "✨");
emojis.slice(1, 2, "✨");
emojis.splice(1, 2, "✨");
  • A: All of them

  • B: map reduce slice splice

  • C: map slice splice

  • D: splice


Đáp án: D

Với splice, ta thay đổi mảng gốc bằng cách thêm sửa xóa các phần tử. Trong trường hợp này ta xóa 2 phần tử kể từ index 1 (ta xóa '🥑''😍') và thêm vào ✨ emoji.

map, filterslice trả về một mảng mới, find trả về một phần tử, và reduce trả về giá trị tích lũy.

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

const food = ["🍕", "🍫", "🥑", "🍔"];
const info = { favoriteFood: food[0] };
info.favoriteFood = "🍝";
console.log(food);
  • A: ['🍕', '🍫', '🥑', '🍔']

  • B: ['🍝', '🍫', '🥑', '🍔']

  • C: ['🍝', '🍕', '🍫', '🥑', '🍔']

  • D: ReferenceError


Đáp án: A

Trong Javascript tất cả các kiểu cơ bản (mọi thứ không phải object) đều tương tác bằng giá trị. Chúng ta set giá trị của thuộc tính favoriteFood trong object info bằng ký tự bánh pizza, '🍕'. Chuỗi trong javascript là một kiểu cơ bản, nên nó cũng sẽ tương tác bằng giá trị.

Bản thân mảng food không hề thay đổi, do giá trị của favoriteFood chỉ là một bản copy của giá trị đầu tiên trong mảng mà thôi, và không hề trỏ tới reference của food[0]. Do đó khi ghi ra, giá trị của mảng vẫn là giá trị ban đầu, ['🍕', '🍫', '🥑', '🍔'].

Avatar Techmely Team
VIẾT BỞI

Techmely Team