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

Giải thích về "this" trong Javascript?


Từ khoá this trong javascript tham chiếu đến một đối tượng có thuộc tính là một hàm.

Giá trị của this phụ thuộc vào đối tượng đang gọi hàm.

Giả sử ta có code sau:

function doSomething() {
  console.log(this);
}

doSomething();

Như vậy theo định nghĩa, this tham chiếu đến một đối tượng có hàm là thuộc tính. Vậy trong đoạn code trên hàm là thuộc tính của đối tượng nào?

Vì hàm được gọi từ ngữ cảnh tổng thể, nên hàm sẽ là thuộc tính của đối tượng toàn cục. Do đó, nếu ta chạy đoạn code trên trình duyệt kết quả sẽ là window object.

Ví dụ 2:

var obj = {
  name: "vivek",
  getName: function () {
    console.log(this.name);
  },
};

obj.getName();

Trong đoạn code này, hàm getName là thuộc tính của obj. Do đó, this sẽ tham chiếu đến đối tượng obj, và output sẽ là "vivek".

Ví dụ 3:

var obj = {
  name: "vivek",
  getName: function () {
    console.log(this.name);
  },
};

var getName = obj.getName;

var obj2 = { name: "akshay", getName };
obj2.getName();

Output sẽ là "akshay". Mặc dù hàm getName được khai báo trong đối tượng obj, nhưng ở thời điểm gọi thì getName() lại là thuộc tính của obj2, do đó "this" sẽ tham chiếu đến obj2.

Cách ngớ ngẩn để hiểu this là, bất cứ khi nào hàm được gọi, hãy kiểm tra đối tượng trước dấu chấm. Giá trị của this sẽ luôn là đối tượng trước dấu chấm.

Nếu không có đối tượng nào như ở ví dụ 1, giá trị sẽ là đối tượng toàn cục.

Ví dụ 4:

var obj1 = {
  address: "Mumbai,India",
  getAddress: function () {
    console.log(this.address);
  },
};

var getAddress = obj1.getAddress;
var obj2 = { name: "akshay" };
obj2.getAddress();

Kết quả sẽ là lỗi, vì từ khóa this tham chiếu đến đối tượng obj2, nhưng obj2 không có thuộc tính “address” ‘, do đó hàm getAddress sẽ xảy ra lỗi.

Currying function trong Javascript là gì?


Currying là khi bạn chia nhỏ một hàm có nhiều đối số thành một chuỗi các hàm có một phần đối số. Đây là một ví dụ trong JavaScript:

function add(a, b) {
  return a + b;
}
add(3, 4); // returns 7

Đây là một hàm nhận hai đối số, ab và trả về tổng của chúng. Bây giờ chúng ta sẽ dùng curry cho hàm này:

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

Trong đại số học, việc xử lý các hàm có nhiều đối số (hoặc tương đương với một đối số là N-tuple) hơi không phù hợp. Vì vậy, làm thế nào để bạn đối phó với một cái gì đó bạn muốn diễn đạt một cách tự nhiên, chẳng hạn như, f(x, y)?. Vâng, bạn coi điều đó tương đương với f(x)(y) - f(x), gọi nó là g, là một hàm, và bạn áp dụng hàm đó cho y.

Nói cách khác, bạn chỉ có các hàm nhận một đối số - nhưng một số hàm trong số đó trả về các hàm khác (Cũng nhận một đối số).

Ví dụ bạn có một hàm để tính giá trị discount, giảm ngay 10% cho khách hàng thân thiết.

function discount(price, discount) {
  return price * discount;
}
// Giảm ngay 50 đồng khi khách hàng đã tiêu 500 đồng.
const price = discount(500, 0.1); // $50
// $500  - $50 = $450

Khách hàng tiêu tiền điên cuồng, chúng ta liên tục gọi hàm

const price = discount(1500, 0.1); // $150
// $1,500 - $150 = $1,350
const price = discount(2000, 0.1); // $200
// $2,000 - $200 = $1,800
const price = discount(50, 0.1); // $5
// $50 - $5 = $45
const price = discount(5000, 0.1); // $500
// $5,000 - $500 = $4,500
const price = discount(300, 0.1); // $30
// $300 - $30 = $270

Chúng ta có thể đưa vào giá trị discount ở lần đầu tiên, đến các lần gọi tiếp theo, chúng ta ko cần truyền giá trị 10% này nữa

function discount(discount) {
  return price => {
    return price * discount;
  };
}
const tenPercentDiscount = discount(0.1);
tenPercentDiscount(500); // $50
const twentyPercentDiscount = discount(0.2);
twentyPercentDiscount(500); // 100
// $500 - $100 = $400
twentyPercentDiscount(5000); // 1000
// $5,000 - $1,000 = $4,000
twentyPercentDiscount(1000000); // 200000
// $1,000,000 - $200,000 = $600,000

Nói một cách ngắn gọn, khi cần truyền vào 1 argument ít thay đổi, cố định trong đa số các trường hợp, nghĩ đến currying.

Sự khác nhau giữa hàm tạo của ES6 class và hàm tạo của ES5 function là gì?


Trước tiên, hãy xem 2 ví dụ dưới đây:

// ES5 Function Constructor
function Person(name) {
  this.name = name;
}

// ES6 Class
class Person {
  constructor(name) {
    this.name = name;
  }
}

Đối với các hàm tạo đơn giản, chúng trông khá giống nhau. Sự khác biệt chính trong hàm tạo là khi sử dụng kế thừa. Nếu chúng ta muốn tạo một lớp Student là lớp con của lớp Person và thêm 1 trường studentId, đây là những gì chúng ta phải làm ngoài những điều trên.

// ES5 Function Constructor
function Student(name, studentId) {
  // Call constructor of superclass to initialize superclass-derived members.
  Person.call(this, name);
  // Initialize subclass's own members.
  this.studentId = studentId;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

// ES6 Class
class Student extends Person {
  constructor(name, studentId) {
    super(name);
    this.studentId = studentId;
  }
}

Sử dụng kế thừa trong ES5 dài dòng hơn nhiều và phiên bản ES6 dễ hiểu và dễ nhớ hơn.

Tại sao chúng ta nên sử dụng ES6 classes?


  • Cú pháp đơn giản và ít lỗi hơn.
  • Việc thiết lập cấu trúc phân cấp kế thừa bằng cú pháp mới dễ dàng hơn nhiều (và một lần nữa, ít bị lỗi hơn).
  • class bảo vệ bạn khỏi lỗi phổ biến là không thể sử dụng new với hàm tạo (bằng cách hàm tạo thảy ra một exception nếu đây không phải là object hợp lệ cho hàm tạo).
  • Việc gọi phiên bản nguyên mẫu gốc của một method đơn giản hơn nhiều so với cú pháp cũ (super.method () thay vì ParentConstructor.prototype.method.call(this) hoặc Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)).

Xem xét đoạn code dưới đây:

// **ES5**
var Person = function (first, last) {
  if (!(this instanceof Person)) {
    throw new Error("Person is a constructor function, use new with it");
  }
  this.first = first;
  this.last = last;
};

Person.prototype.personMethod = function () {
  return (
    "Result from personMethod: this.first = " +
    this.first +
    ", this.last = " +
    this.last
  );
};

var Employee = function (first, last, position) {
  if (!(this instanceof Employee)) {
    throw new Error("Employee is a constructor function, use new with it");
  }
  Person.call(this, first, last);
  this.position = position;
};

Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.personMethod = function () {
  var result = Person.prototype.personMethod.call(this);
  return result + ", this.position = " + this.position;
};
Employee.prototype.employeeMethod = function () {
  // ...
};

Và tương tự như trên:

// **\*ES2015+**
class Person {
  constructor(first, last) {
    this.first = first;
    this.last = last;
  }
  personMethod() {
    // ...
  }
}

class Employee extends Person {
  constructor(first, last, position) {
    super(first, last);
    this.position = position;
  }
  employeeMethod() {
    // ...
  }
}

So sánh sự khác nhau giữa Object.freeze() và const?


constObject.freeze là hai thứ hoàn toàn khác nhau:

So sánh sự khác nhau giữa Object.freeze() và const

  • const áp dụng cho các ràng buộc biến. Nó tạo ra một ràng buộc bất biến, tức là bạn không thể gán một giá trị mới cho ràng buộc đó.
const person = {
  name: "Leonardo",
};
let animal = {
  species: "snake",
};
person = animal; // ERROR "person" is read-only
  • Object.freeze hoạt động trên các giá trị và cụ thể hơn là các giá trị đối tượng. Nó làm cho một đối tượng trở nên bất biến, tức là bạn không thể thay đổi các thuộc tính của nó.
let person = {
  name: "Leonardo",
};
let animal = {
  species: "snake",
};
Object.freeze(person);
person.name = "Lima"; // TypeError: Cannot assign to read only property 'name' of object
console.log(person);

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

const box = { x: 10, y: 20 };
Object.freeze(box);
const shape = box;
shape.x = 100;
console.log(shape);
  • A: { x: 100, y: 20 }

  • B: { x: 10, y: 20 }

  • C: { x: 100 }

  • D: ReferenceError


    Đáp án: B

Object.freeze khiến cho chúng ta không thể thêm vào, xóa đi hay thay đổi bất kì thuộc tính nào của object (trừ phi giá trị của thuộc tính lại chính là một object khác).

Khi chúng ta tạo ra biến shape và set cho nó giá trị bằng với một object đã được đóng băng là box, thì shape cũng sẽ trỏ tới một object đã được đóng băng. Ta có thể check một object có đang bị đóng băng hay không bằng Object.isFrozen. Trong trường hợp này, Object.isFrozen(shape) trả về true, vì shape đang trỏ tới một object bị đóng băng.

Do đó, cộng với việc x không phải là object, ta sẽ không thể thay đổi giá trị của x. x sẽ vẫn là 10, và { x: 10, y: 20 } được ghi ra.

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

const list = [1 + 2, 1 * 2, 1 / 2];
console.log(list);
  • A: ["1 + 2", "1 * 2", "1 / 2"]
  • B: ["12", 2, 0.5]
  • C: [3, 2, 0.5]
  • D: [1, 1, 1]

Đáp án: C

Mảng có thể nhận bất cứ giá trị nào. Số, chuỗi, objects, mảng khác, null, boolean, undefined, và nhiều dạng biểu thức nữa như ngày tháng, hàm, và các tính toán.

Giá trị của phần tử chính là giá trị trả về. 1 + 2 trả về 3, 1 * 2 trả về 2, và 1 / 2 trả về 0.5.

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

const person = { name: "Lydia" };
Object.defineProperty(person, "age", { value: 21 });
console.log(person);
console.log(Object.keys(person));
  • A: { name: "Lydia", age: 21 }, ["name", "age"]
  • B: { name: "Lydia", age: 21 }, ["name"]
  • C: { name: "Lydia"}, ["name", "age"]
  • D: { name: "Lydia"}, ["age"]

Đáp án: B

Với phương thức defineProperty, chúng ta có thể thêm các thuộc tính mới, cũng như sửa các thuộc tính sẵn có của object. Khi chúng ta thêm thuộc tính vào object bằng defineProperty, chúng sẽ mặc định là thuộc tính not enumerable. Phương thức Object.keys sẽ trả về tất cả các thuộc tính enumerable của object, trong trường hợp này thì chỉ có "name" mà thôi.

Thêm nữa, các thuộc tính được thêm bởi defineProperty là mặc định không thể thay đổi được. Tất nhiên ta có thể override các điều đó bằng các thuộc tính như writable, configurableenumerable. Tức là defineProperty là một cách rất mềm dẻo để tạo ra và điều chỉnh thuộc tính của object.

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

const settings = {
  username: "lydiahallie",
  level: 19,
  health: 90,
};

const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);
  • A: "{"level":19, "health":90}"
  • B: "{"username": "lydiahallie"}"
  • C: "["level", "health"]"
  • D: "{"username": "lydiahallie", "level":19, "health":90}"

Đáp án: A

Đối số thứ hai của JSON.stringifyreplacer. Replacer Có thể là một hàm hoặc một mảng, nó sẽ quy định xem giá trị nào sẽ được chuỗi hóa ra sao.

Nếu replacer là một mảng, chỉ có các thuộc tính có tên trong mảng được convert thành chuỗi JSON. Trong trường hợp này, chỉ có các thuộc tính "level""health" được đưa vào, "username" bị loại bỏ. data giờ sẽ là "{"level":19, "health":90}".

Nếu replacer là function, hàm này sẽ được gọi trên từng thuộc tính của object được chuỗi hóa. Giá trị trả về sẽ là giá trị được đưa vào chuỗi JSON. Nếu trả về undefined, thuộc tính này sẽ bị loại bỏ khỏi chuỗi.

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

let num = 10;

const increaseNumber = () => num++;
const increasePassedNumber = number => number++;

const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);

console.log(num1);
console.log(num2);
  • A: 10, 10
  • B: 10, 11
  • C: 11, 11
  • D: 11, 12

Đáp án: A

Phép toán ++ sẽ trả về trước giá trị của toán hạng, sau đó tăng giá trị của toán hạng lên. Giá trị của num110, vì increaseNumber sẽ trả về giá trị của num, đang là 10, và sau đó mới tăng giá trị của num lên.

num2 cũng là 10, vì chúng ta đưa num1 vào increasePassedNumber. number bằng 10(tức giá trị của num1). Cũng giống như trên, phép toán ++ sẽ trả về trước giá trị của toán hạng, sau đó tăng giá trị của toán hạng lên. Giá trị của number10, do đó num2 cũng sẽ là 10.

Avatar Techmely Team
VIẾT BỞI

Techmely Team