5 quy tắc binding trong JavaScript

1983

Tác giả:TAPAS ADHIKARY

Khái niệm “this” trong JavaScript là một trong những khía cạnh khó hiểu nhất của ngôn ngữ này. Tuy nhiên lại là nhân tố quan trọng để viết code “advance”, nâng cao hơn. Trong JS, this cho phép:

  • Sử dụng lại function trong các ngữ cảnh khác nhau
  • Xác định tập trung vào object nào khi gọi method.

Nhắc tới this thì điều đầu tiên phải làm đó là biết gọi funtion nào, bởi sẽ không biết cái gì ở bên trong this cho tới khi function được gọi. Và các trường hợp của this có thể chia thành 5 khía cạnh binding khác nhau.

Binding là gì

Trong JavaScript thì Lexical Environment hay nói theo nghĩa đen là môi trường từ vựng, cũng như là môi trường chứa đựng code được viết ra theo mặt vật lý (physically) . Hãy xem ví dụ bên dưới, tên biến bên trong function sayName()lexically

function sayName() {
  let name = 'someName';
  console.log('The name is, ', name);
 }

Trong đó Execution Context thì liên quan đến code hiện thời đang chạt và những thứ giúp code hoạt động. Có thể có nhiều lexical environments có sẵn nhưng chỉ có một cái đang chạy lúc này, và được Execution Context quản lý.

Mỗi Execution Context lại chứa một Environment Record. Binding trong JavaScript có nghĩa là ghi lại identifier (variable và tên function) trong một Environment Record cụ thể.

Lưu ý: Binding giúp kết hợp identifier (variable và tên function) với this cho  một execution context.

Khúc này còn hơi rắc rối nhưng những phần sau sẽ giải thích rõ hơn.

Quy tắc 1: Implicit Binding hoạt động như thế nào?

Trong implicit binding thì cần phải xem đối tượng bên trái của hàm sử dụng dấu chấm (dot operater), từ đó xác định được this đang tham chiếu tới cái gì.

let user = {
    name: 'Tapas',
    address: 'freecodecamp',
    getName: function() {
        console.log(this.name);
    }
};

user.getName();

Ví dụ này thì this đang trỏ tới user object, bởi vì bên trái hàm dấu chấm là function getName (), ta thấy có user object, vì vậy this.name trong console sẽ hiển thị Tapas.

Một ví dụ khác

function decorateLogName(obj) {
      obj.logName = function() {
          console.log(this.name);
      }
  };

  let tom = {
      name: 'Tom',
      age: 7
  };

  let jerry = {
      name: 'jerry',
      age: 3
  };

  decorateLogName(tom);
  decorateLogName(jerry);

  tom.logName();
  jerry.logName();

Trong ví dụ này thì có 2 object, tomjerry. Và chúng được decorate (nâng cao) bằng cách đính kèm method logName().

Khi gọi hàm tom.logName()., object tom nằm bên trái dấu chấm cuả function logName(). Nên this sẽ trỏ tới object tom và nhận giá trị của tom (this.name tương đương với tom). Tương tự khi gọi hàm jerry.logName().

Quy tắc 2: Explicit Binding hoạt động như thế nào?

JavaScript tạo môi trường để thực hiện code mà chúng ta viết. Trong đó nó đảm nhiệm memory creation (dành cho variables, functions, objects) trong creation phase. Cuối cùng nó chãy code trong execution phase. Lúc này môi trường mới được Execution Context.

Có nhiều loại environment trong JavaScript application. Mỗi execution context thực hiện, chạy lệnh độc lập với nhau. Nhưng sẽ có lúc cần sử dụng thứ này trong execution context này trong cái context khác. Lúc này explicit binding sẽ pht huy công dụng.

Trong explicit binding, chúng ta có thể gọi function với object khi funtion đó nằm ngoài execution context của object đó.

Để thực hiện explicit biding thì có 3 method đó là call(), apply()bind().

Hàm call() hoạt động thế nào

Với phương thức call() thì context với function được gọi sẽ được chuyển tới called dưới dạng tham số (parameter).

let getName = function() {
     console.log(this.name);
 }
 
let user = {
   name: 'Tapas',
   address: 'Freecodecamp'  
 };

getName.call(user);

Ở đây, hàm call() sẽ được gọi trên function getName(). Function getName() nhận giá trị this.name. Nhưng this trong đây là gì? Lúc này sẽ được quyết định bởi giá trị đã được chuyển đến hàm call().

Với trường hợp này, this sẽ được bind đến user object bởi vì đã chuyển user như một tham số đến hàm call(). Vì vậy this.name sẽ nhận giá trị của thuộc tính name của user object, Tapas. 

Ví dụ trên mới chỉ chuyển một argument tới hàm call(), nhưng thực tế có thể chuyển nhiều argument như sau:

let getName = function(hobby1, hobby2) {
     console.log(this.name + ' likes ' + hobby1 + ' , ' + hobby2);
 }

let user = {
   name: 'Tapas',
   address: 'Bangalore'  
 };

let hobbies = ['Swimming', 'Blogging'];
 
getName.call(user, hobbies[0], hobbies[1]);

Argument đầu tiên đuợc chuyển tới call() là object context với function đã được gọi. Những thuộc tính khác chỉ có thể là giá trị được sử dụng. Ở đây mình đang chuyển Swimming và Blogging là 2 thuộc tính tới function getName().

Tuy nhiên sẽ có trường hợp cần pass từng argument một trong call() thì sao? Lúc này sẽ tới lượt của apply().

Hàm apply() hoạt động thế nào

Cách mà apply() thực thi lệnh cũng tương đồng với call() nhưng cho phép pass argument thuận tiện hơn.

let getName = function(hobby1, hobby2) {
     console.log(this.name + ' likes ' + hobby1 + ' , ' + hobby2);
 }
 
let user = {
   name: 'Tapas',
   address: 'Bangalore'  
 };

let hobbies = ['Swimming', 'Blogging'];
 
getName.apply(user, hobbies);

Lúc này ta có thể chuyển một mảng (array) chứa argument luôn, tiện hơn nhiều so với việc chuyển từng cái.

Tips: nếu bạn cần chuyển một giá trị argument hay argument mà không có giá trị thì hãy dùng call(). Còn nếu chuyển nhiều giá trị argument thì dùng apply().

Hàm bind() hoạt động thế nào

Method bind() cũng tương tự như call() nhưng có 1 điểm khác biệt nhỏ. Thay vì gọi function trực tiếp thì bind() trả về một hàm mới.

let getName = function(hobby1, hobby2) {
     console.log(this.name + ' likes ' + hobby1 + ' , ' + hobby2);
 }

let user = {
   name: 'Tapas',
   address: 'Bangalore'  
 };

let hobbies = ['Swimming', 'Blogging'];
let newFn = getName.bind(user, hobbies[0], hobbies[1]); 

newFn();

Ở đây getName.bind() không gọi function getName() trực tiếp, nó trả về function mới, newFn và chúng ta đang gọi hàm dưới dạng newFn().

Quy tắc 3: New Binding

Từ khóa new được dùng để tạo một object mới từ constructor function.

let Cartoon = function(name, animal) {
     this.name = name;
     this.animal = animal;
     this.log = function() {
         console.log(this.name +  ' is a ' + this.animal);
     }
 };

Bạn có thể tạo nhiều object với từ khóa new như sau:

 let tom = new Cartoon('Tom', 'Cat');
 let jerry = new Cartoon('Jerry', 'Mouse');

Với quy tắc new biding, khi một function được gọi với từ khóa new, thì this bên trong function sẽ tham chiếu tới cái object mới được lập.

let tom = new Cartoon('Tom', 'Cat');

Đây là function Cartoon được gọi với từ khóa new. Thì this sẽ tham chiếu tới object mới, tom.

Quy tắc 4: Global Object Binding

Đoạn code dưới đây có kết quả thế nào? this tham chiếu tới cái gì?

let sayName = function(name) {
    console.log(this.name);
};

window.name = 'Tapas';
sayName();

Nếu this không giải quyết được với một trong những quy tắc binding, implicit, explicit hay new, thì this lúc này tham chiếu tới object window(global).

Tuy nhiên quy tắc strict mode của JavaScript sẽ không cho phép default binding như sau:

"use strict";
function myFunction() {
  return this;
}

Trường hợp này thì thisundefined.

Quy tắc 5: HTML Event Element Biding

Trong HTML event handlers, this tham chiếu tới element HTML nào nhận event đó.

<button onclick="console.log(this)">Click Me!</button>

Đây là giá trị trả về console khi click vào button:

"<button onclick='console.log(this)'>Click Me!</button>"

Bạn có thể thay đổi style của this:

<button onclick="this.style.color='teal'">Click Me!</button>

Nhưng hãy cẩn thận khi call function trên button click và dùng this bên trong function đó.

<button onclick="changeColor()">Click Me!</button>

và JavaScript:

function changeColor() {
  this.style.color='teal';
}

Đoạn code trên sẽ không trả kết quả như ý được, bởi theo như rule số 4 thì this sẽ tham chiếu tới object global, và không có object style để set màu.

Tổng kết

  • Với implicit binding thì this trỏ tới object bên trái của hàm dấu chấm.
  • Với explicit binding, chúng ta có thể gọi function với object khi function đó nằm ngoài execution context của object, có thể dùng các hàm như call(), apply(), bind().
  • Khi một function được gọi với từ khóa new thì this bên trong dunction sẽ trỏ tới object mới được lập.
  • Khi this không được giải quyết với implicit, explicit hay new thì this trỏ tới object window(global). Với strict mode của JavaScript, this trở nên undefined.
  • Trong HTML event handler thì this trỏ tới HTML element nào nhận event đó.

Tham khảo bài viết gốc tại freeCodeCamp

Xem thêm việc làm Front-End Developer hấp dẫn tại TopDev

Có thể bạn quan tâm: