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

Giải thích về Scope và Scope Chain trong Javascript?


Scope trong JS, xác định khả năng truy cập của các biến, hàm ở các phần khác nhau trong một đoạn code.

Nói chung, Scope cho biết phạm vi mà biến và hàm của ta có thể hay không thể truy cập. Có 3 loại scope trong JS:

  • Global Scope
  • Local hay Function Scope
  • Block Scope

Global Scope

Các biến hoặc hàm được khai báo trong namespace global đều có global scope, có nghĩa là tất cả các biến và hàm có global scope có thể được truy cập từ bất kỳ đâu bên trong code.

var globalVariable = "Hello world";

function sendMessage() {
  return globalVariable; // can access globalVariable since it's written in global space
}

function sendMessage2() {
  return sendMessage(); // Can access sendMessage function since it's written in global space
}

sendMessage2(); // Returns “Hello world”

Function Scope

Bất kỳ biến hoặc hàm nào được khai báo bên trong một hàm đều có function scope, có nghĩa là tất cả các biến và hàm được khai báo bên trong một hàm, có thể được truy cập từ bất cứ đâu bên trong hàm chứ không phải bên ngoài nó.

function awesomeFunction() {
  var a = 2;

  var multiplyBy2 = function () {
    console.log(a * 2); // Can access variable "a" since a and multiplyBy2 both are written inside the same function
  };
}
console.log(a); // Throws reference error since a is written in local scope and cannot be accessed outside

multiplyBy2(); // Throws reference error since multiplyBy2 is written in local scope

Block Scope

Block Scope liên quan đến các biến được khai báo bằng letconst. Các biến được khai báo với var không có block scope.

Block scope cho chúng ta biết rằng bất kỳ biến nào được khai báo bên trong một khối {}, chỉ có thể được truy cập bên trong khối đó và không thể được truy cập bên ngoài khối đó.

{
  let x = 45;
}

console.log(x); // Gives reference error since x cannot be accessed outside of the block

for (let i = 0; i < 2; i++) {
  // do something
}

console.log(i); // Gives reference error since i cannot be accessed outside of the for loop block

Scope Chain

JavaScript Engine cũng sử dụng scope để tìm biến. Ví dụ:

var y = 24;

function favFunction() {
  var x = 667;
  var anotherFavFunction = function () {
    console.log(x); // Does not find x inside anotherFavFunction, so looks for variable inside favFunction, outputs 667
  };

  var yetAnotherFavFunction = function () {
    console.log(y); // Does not find y inside yetAnotherFavFunction, so looks for variable inside favFunction and does not find it, so looks for variable in global scope, finds it and outputs 24
  };

  anotherFavFunction();
  yetAnotherFavFunction();
}

favFunction();

Như bạn có thể thấy trong đoạn code trên, nếu javascript engine không tìm thấy biến trong function scope, nó sẽ cố gắng kiểm tra biến ở phạm vi bên ngoài. Nếu biến không tồn tại trong phạm vi bên ngoài, nó sẽ cố gắng tìm biến trong global scope.

Nếu biến cũng không được tìm thấy trong không gian chung, thì lỗi tham chiếu sẽ được đưa ra.

Từ khóa "new" trong JavaScript là gì?


  • Nó tạo ra một đối tượng mới. Loại đối tượng này chỉ đơn giản là object.
  • Nó đặt thuộc tính internal, inaccessible, prototype (tức là proto) của đối tượng mới này trở thành đối tượng prototype, external, accessible của hàm khởi tạo (constructor function), mọi function object đều tự động có một thuộc tính prototype.
  • Nó làm cho biến this trỏ tới đối tượng mới được tạo.
  • Nó thực thi hàm khởi tạo, sử dụng đối tượng mới được tạo bất cứ khi nào điều này được đề cập.
  • Nó trả về đối tượng mới được tạo, trừ khi hàm khởi tạo trả về một tham chiếu đối tượng non-null. Trong trường hợp này, tham chiếu đối tượng đó được trả về thay thế.
function New(func) {
  var res = {};
  if (func.prototype !== null) {
    res.__proto__ = func.prototype;
  }
  var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
  if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
    return ret;
  }
  return res;
}

Lấy ví dụ về destructuring một object hoặc một array trong ES6?


Destructuring là một biểu thức có sẵn trong ES6 cho phép một cách ngắn gọn và thuận tiện để trích xuất các giá trị của Object hoặc Array và đặt chúng vào các biến riêng biệt.

Array destructuring

// Variable assignment.
const foo = ["one", "two", "three"];
const [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
// Swapping variables
let a = 1;
let b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

Object destructuring

// Variable assignment.
const o = { p: 42, q: true };
const { p, q } = o;
console.log(p); // 42
console.log(q); // true

Sự khác nhau giữa Anonymous function và Named function là gì?


Ví dụ:

var foo = function () {
  // anonymous function assigned to variable foo
  // ..
};
var x = function bar() {
  // named function (bar) assigned to variable x
  // ..
};
foo(); // actual function execution
x();

So sánh sự khác nhau của .forEach() và .map()?


Để hiểu sự khác biệt giữa hai hàm, chúng ta hãy xem mỗi hàm làm gì.

forEach() trong Javascript

  • Lặp qua các phần tử trong một mảng.
  • Thực hiện một lệnh gọi lại cho mỗi phần tử.
  • Không trả về giá trị.
const a = [1, 2, 3];
const doubled = a.forEach((num, index) => {
  // Do something with num and/or index.
});
// doubled = undefined

map() trong Javascript

  • Lặp qua các phần tử trong một mảng.
  • Ánh xạ mỗi phần tử thành một phần tử mới bằng cách gọi hàm trên mỗi phần tử, kết quả là tạo ra một mảng mới.
const a = [1, 2, 3];
const doubled = a.map(num => {
  return num * 2;
});
// doubled = [2, 4, 6]

Sự khác biệt chính giữa forEach()map() là map trả về một mảng mới. Nếu bạn cần kết quả, nhưng không muốn thay đổi mảng ban đầu, map là lựa chọn rõ ràng. Nếu bạn chỉ cần lặp lại một mảng, forEach() là một lựa chọn tốt.

Giải thích Prototype Inheritance trong JavaScript là gì?


Trong một ngôn ngữ thực hiện kế thừa cổ điển như Java, C # hoặc C ++, bạn bắt đầu bằng cách tạo một class - một bản thiết kế cho các đối tượng của bạn - và sau đó bạn có thể tạo các đối tượng mới từ class đó hoặc bạn có thể mở rộng class, xác định một class mới để tăng cường class ban đầu.

Trong JavaScript, trước tiên bạn tạo một đối tượng (không có khái niệm về class trong Javascript), sau đó bạn có thể tăng cường đối tượng của riêng mình hoặc tạo các đối tượng mới từ nó. Mọi đối tượng trong Javascript đều có một prototype. Hệ thống kế thừa của JavaScript là nguyên mẫu và không dựa trên class. Khi một thông báo đến một đối tượng, JavaScript sẽ cố gắng tìm một thuộc tính trong đối tượng đó trước, nếu không thể tìm thấy thì thông báo sẽ được gửi đến prototype của đối tượng, v.v. Hành vi đó được gọi là prototype chain (chuỗi nguyên mẫu) hoặc prototype inheritance.

Hàm tạo (constructor) là cách được sử dụng nhiều nhất trong JavaScript để tạo prototype chain. Khi chúng ta sử dụng new, JavaScript đưa một tham chiếu ngầm đến đối tượng mới đang được tạo dưới dạng từ khóa this. Nó cũng trả về tham chiếu này một cách ngầm định ở cuối hàm.

function Foo() {
  this.kind = "foo";
}
var foo = new Foo();
foo.kind; //=> foo

Thuật ngữ Transpiling là gì?


Không có cách nào để polyfill các cú pháp mới đã được thêm vào ngôn ngữ. Vì vậy, lựa chọn tốt hơn là sử dụng một công cụ chuyển đổi mã mới hơn của bạn thành các mã tương đương cũ hơn. Quá trình này thường được gọi là transpiling, một thuật ngữ để chuyển đổi + biên dịch (transforming + compiling).

Thông thường, bạn chèn transpiler (trình chuyển đổi) vào quy trình phát triển của mình, tương tự như trình ghép mã (linter) hoặc trình thu nhỏ (minifier) của bạn. Có khá nhiều bộ chuyển đổi tuyệt vời cho bạn lựa chọn:

  • Babel: Chuyển ES6+ thành ES5
  • Traceur: Chuyển ES6, ES7 và hơn thế nữa sang ES5

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

function sayHi() {
  console.log(name);
  console.log(age);
  var name = "Lydia";
  let age = 21;
}

sayHi();
  • A: Lydiaundefined
  • B: LydiaReferenceError
  • C: ReferenceError21
  • D: undefinedReferenceError

Đáp án: D

Trong 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 sau là gì?

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1);
}

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1);
}
  • A: 0 1 2 and 0 1 2
  • B: 0 1 2 and 3 3 3
  • C: 3 3 3 and 0 1 2

Đáp án: C

Bở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ì?

const shape = {
  radius: 10,
  diameter() {
    return this.radius * 2;
  },
  perimeter: () => 2 * Math.PI * this.radius,
};
shape.diameter();
shape.perimeter();
  • A: 20 and 62.83185307179586
  • B: 20 and NaN
  • C: 20 and 63
  • D: NaN and 63

Đáp án: B

Chú ý 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.

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

+true;
!"Lydia";
  • A: 1 and false
  • B: false and NaN
  • C: false and false

Đáp án: A

Phép toán cộng + sẽ convert một toán hạng sang dạng number. true1, 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.

Cái nào đúng?

const bird = {
  size: "small",
};

const mouse = {
  name: "Mickey",
  small: true,
};
  • A: mouse.bird.size không hợp lệ
  • B: mouse[bird.size] không hợp lệ
  • C: mouse[bird["size"]] không hợp lệ
  • D: Tất cả đều hợp lệ

Đáp án: A

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"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.birdundefined, 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.

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

let c = { greeting: "Hey!" };
let d;

d = c;
c.greeting = "Hello";
console.log(d.greeting);
  • A: Hello
  • B: Hey
  • C: undefined
  • D: ReferenceError
  • E: TypeError

Đáp án: A

Trong 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 sau là gì?

let a = 3;
let b = new Number(3);
let c = 3;

console.log(a == b);
console.log(a === b);
console.log(b === c);
  • A: true false true
  • B: false false true
  • C: true false false
  • D: false true true

Đáp án: C

new 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ị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.

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

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");
  • A: orange
  • B: purple
  • C: green
  • D: TypeError

Đáp án: D

Hà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ì?

let greeting;
greetign = {}; // Lỗi đánh máy!
console.log(greetign);
  • A: {}
  • B: ReferenceError: greetign is not defined
  • C: undefined

Đáp án: A

Nó sẽ log ra object greetign, bởi vì chúng ta vừa khởi tạo một global object! Khi chúng ta đánh máy nhầm greeting thành greetign, trình thông dịch của JS sẽ coi nó như là global.greetign = {} (hay window.greetign = {} nếu chạy trên browser).

Để tránh điều này chúng ta có thể sử dụng "use strict". Nó sẽ đảm bảo rẳng các biến đều phải được khai báo trước khi sử dụng.

Điều gì sẽ xảy ra khi chúng ta làm thế này?

function bark() {
  console.log("Woof!");
}

bark.animal = "dog";
  • A: Hoàn toàn không có vấn đề gì!
  • B: SyntaxError. Bạn không thể thêm thuộc tính theo cách này.
  • C: undefined
  • D: ReferenceError

Đáp án: A

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

Avatar Techmely Team
VIẾT BỞI

Techmely Team