Web nâng cao (4) - JavaScript cho React (3)

1.1.1       Từ khóa this

Trong JavaScript, this là một từ khóa (keyword), được sử dụng để trỏ tới một đối tượng (object). Tuy nhiên, tùy theo tình huống sử dụng, từ khóa this sẽ trỏ tới đối tượng khác nhau. Ví dụ:

– Trong một phương thức của đối tượng, từ khóa this sẽ tham chiếu tới chính đối tượng chứa nó

– Ở vị trí tự do, từ khóa this sẽ tham chiếu tới đối tượng toàn cục (global object)

– Trong một hàm (hàm tự do, không thuộc đối tượng nào), từ khóa this sẽ tham chiếu tới đối tượng toàn cục

– Trong một hàm, ở chế độ strict mode, từ khóa this sẽ là undefined

– Trong một sự kiện (event), từ khóa this tham chiếu tới phần tử (element) tiếp nhận sự kiện

– Có thể sử dụng các phương thức bind(), apply() và call() để thay đổi đối tượng tham chiếu của từ khóa this

Để hiểu rõ hơn các tình huống sử dụng của từ khóa this, chúng ta sẽ cùng xem xét một số ví dụ.

Ví dụ 1, trong một phương thức của đối tượng, từ khóa this sẽ tham chiếu tới chính đối tượng chứa nó.

// định nghĩa đối tượng conNguoi

    const conNguoi = {

      ho : 'Nguyen',

      ten : 'Teo',

      hoVaTen : function() {

        return(this.ho + ' ' + this.ten);

      },

      test : function(){

        console.log(this);

      }

    };

    // xuất thử xem conNguoi có phải là đối tượng không?

    console.log(typeof conNguoi); //object

    // xuất thử xem this trong object conNguoi tham chiếu tới đối tượng nào

    console.log(conNguoi.test()); //conNguoi

Ví dụ 2, trong một phương thức của đối tượng, từ khóa this sẽ tham chiếu tới chính đối tượng chứa nó, trường hợp đối tượng lồng đối tượng.

 // định nghĩa đối tượng conNguoi

    const conNguoi = {

      ho : 'Nguyen',

      ten : 'Teo',

      test : function(){

        console.log(this);

      },

      sinhVien : {

        diem : 8,

        testSinhVien : function() {

          console.log(this);

        }

      }

    };

    // xuất từ khóa this trong đối tượng sinhVien

    console.log(conNguoi.sinhVien.testSinhVien()); // sinhVien

    // xuất từ khóa this trong đối tượng conNguoi

    console.log(conNguoi.test()); // conNguoi

Bạn để ý, từ khóa this sẽ thuộc về đối tượng gần nhất gọi tới nó. Ví dụ, trong dòng mã conNguoi.sinhVien.testSinhVien()thì đối tượng sinhVien gần hơn so với đối tượng conNguoi.

Ví dụ 3, ở vị trí tự do, từ khóa this sẽ tham chiếu tới đối tượng toàn cục (global object).

<script>

  console.log(this); // Window

</script>

Ví dụ 4, trong một hàm (tự do, không thuộc đối tượng nào), từ khóa this sẽ tham chiếu tới đối tượng toàn cục.

  function testFunction() {

    console.log(this);

  }

  testFunction(); // Window

Ví dụ 5, trong một hàm, ở chế độ strict mode, từ khóa this sẽ là undefined

  'use strict'

  function testFunction() {

    console.log(this);

  }

  testFunction(); // undefined

Ví dụ 6, trong một sự kiện (event), từ khóa this tham chiếu tới phần tử (element) tiếp nhận sự kiện.

<body>

  <button id="btn">Dang nhap</button>

<script>

  const btn = document.getElementById("btn");

  btn.onclick = function() {

    this.style.display = "none"; // làm biết mất nút Dang nhap

    console.log(this); // element button

    console.dir(this); // button object

  }

</script>

</body>

1.1.2       Phương thức bind()

Phương thức bind() là phương thức có sẵn của đối tượng function, nó được gọi là function prototype, do vậy chỉ có function mới gọi được phương thức này. Bạn có thể sử dụng lệnh console.dir(Function) để kiểm tra.

Bạn có thể sử dụng phương thức bind() để:

– Mượn phương thức của đối tượng khác

– Xác định tham số this của một phương thức

Mượn phương thức của đối tượng khác     

Phương thức bind() cho phép đối tượng này có thể mượn phương thức (hay mượn hàm – function borrowing) của đối tượng khác. Ví dụ, thông thường để định nghĩa hai đối tượng là conNguoi va sinhVien bạn sẽ làm như sau:

// định nghĩa đối tượng conNguoi

    const conNguoi = {

      ho : 'Nguyen',

      ten : 'Teo',

      hoVaTen : function() {

        return(this.ho + ' ' + this.ten);

      }

    };

    // định nghĩa đối tượng sinhVien

    const sinhVien = {

      ho : 'Le',

      ten : 'Ti',

      hoVaTen : function() {

        return(this.ho + ' ' + this.ten);

      }

    };

    console.log(conNguoi.hoVaTen());

    console.log(sinhVien.hoVaTen());

Với phương thức bind(), khi định nghĩa đối tượng sinhVien, bạn sẽ không phải viết lại phương thức hoVaTen, mà khi cần sử dụng, bạn sẽ mượn từ đối tượng conNguoi.

Lưu ý: phương thức bind() sẽ trả về một hàm mới, để bạn sử dụng về sau, chứ nó không gọi hàm để thực thi trực tiếp.

// định nghĩa đối tượng conNguoi

    const conNguoi = {

      ho : 'Nguyen',

      ten : 'Teo',

      hoVaTen : function() {

        return(this.ho + ' ' + this.ten);

      }

    };

    // định nghĩa đối tượng sinhVien

    const sinhVien = {

      ho : 'Le',

      ten : 'Ti'

    };

 

    let hoVaTenSinhVien = conNguoi.hoVaTen.bind(sinhVien);

    console.log(hoVaTenSinhVien());

    // hoặc  

    console.log(conNguoi.hoVaTen.bind(sinhVien)());

Ở đoạn mã let hoVaTenSinhVien = conNguoi.hoVaTen.bind(sinhVien); đối số sinhVien truyền vào cho phương thức bind() sẽ đóng vai trò là từ khóa this trong phương thức hoVaTen() của đối tượng conNguoi.

Xác định tham số this của phương thức

Từ khóa this được sử dụng để xác định đối tượng được tham chiếu tới trong các tình huống sử dụng cụ thể, hay nói cách khác là trong các tình huống khác nhau thì từ khóa this sẽ trỏ tới đối tượng khác nhau. Tình huống sử dụng ở đây được gọi là ngữ cảnh sử dụng (context). Khi lập trình, nếu không kiểm soát được từ khóa this đang trỏ tới đối tượng nào thì sẽ làm cho chương trình thực thi ngoài ý muốn, do vậy cần phải có cách nào đó để kiểm soát được từ khóa this, sử dụng phương thức bind() là một lựa chọn. Bạn cùng quan sát ví dụ sau.

Đoạn chương trình sau sẽ hiển thị họ tên sinh viên ra màn hình:

<body>

  <p id="ket-qua"></p>

<script>

 // định nghĩa đối tượng sinhVien

 sinhVien = {

            ho : 'Nguyen Van',

            ten : 'Teo',

            xuatHoTen : function() {

              let kq = document.getElementById("ket-qua");

              kq.innerHTML = this.ho + ' ' + this.ten;

            }

        };

  sinhVien.xuatHoTen();

 </script>

</body>

Chạy đoạn mã trên sẽ có kết quả như mong muốn. Tuy nhiên, khi chúng ta sử dụng phương thức xuatHoTen() như là một hàm callback (xuatHoTen() là tham số truyền vào, và được thực thi trong một hàm khác – hàm setTimeout) thì đoạn mã sẽ cho ra kết quả không như mong muốn như ví dụ sau,

sinhVien = {

            ho : 'Nguyen Van',

            ten : 'Teo',

            xuatHoTen : function() {

              let kq = document.getElementById("ket-qua");

              kq.innerHTML = this.ho + ' ' + this.ten;

            }

        };

  setTimeout(sinhVien.xuatHoTen, 3000);

Đoạn mã trên thay vì xuất Nguyen Van Teo thì lại xuất undefined ra màn hình. Nghĩa là từ khóa this đã không trỏ tới đối tượng sinhVien nữa, nên không lấy được họ tên sinh viên. Giải pháp là sẽ sử dụng phương thức bind() để kết buộc sinhVien.xuatHoTen() với đối tượng sinhVien, khi đó từ khóa this trong lệnh kq.innerHTML = this.ho + ' ' + this.ten; sẽ trỏ tới đối tượng sinhVien lúc thực thi.

sinhVien = {

            ho : 'Nguyen Van',

            ten : 'Teo',

            xuatHoTen : function() {

              let kq = document.getElementById("ket-qua");

              kq.innerHTML = this.ho + ' ' + this.ten;

            }

        };

  setTimeout(sinhVien.xuatHoTen.bind(sinhVien), 3000);

1.1.3       Từ khóa this và arrow function

Như bạn đã biết, hiện tượng bị mất ngữ cảnh của từ khóa this khi dùng nó trong hàm callback, như trong ví dụ dưới đây, mục đích là sẽ xuất danh sách các ngọn núi tại Đà Lạt sau một giây:

     const dalat = {

          mountains: ["langbiang", "voi", "bidoup", "pinhat"],

          print: function(delay = 1000) {

               setTimeout(function() {

                    console.log(this.mountains.join(''));

               }, delay);

          }

     };

     dalat.print();// Uncaught TypeError: Cannot read property 'join' of undefined

Tuy nhiên, chạy đoạn mã trên sẽ có lỗi, lý do là từ khóa this trỏ tới đối tượng Window, mà đối tượng Window không có thuộc tính mountains. Có thể khắc phục lỗi này bằng cách dùng phương thức bind() như sau:

const dalat = {

          mountains: ["langbiang", "voi", "bidoup", "pinhat"],

          print: function(delay = 1000) {

            console.log(this.mountains); // ['langbiang', 'voi', 'bidoup', 'pinhat']

               setTimeout(function(){

                    console.log(this.mountains); // undefined

                    console.log(this.mountains.join(', '));

               }, delay);

          }

     };

dalat.print();

Ở đoạn mã trên bạn thử xuất console.log(this.mountains); // ['langbiang', 'voi', 'bidoup', 'pinhat'] sẽ thấy từ khóa this vẫn tham chiếu tới mảng mountains được. Tuy nhiên, xuất this.mountains trong hàm setTimeout sẽ là undefined, do this bị mất ngữ cảnh.

Để lấy lại ngữ cảnh cho từ khóa this, bạn sẽ kết buộc hàm callback của setTimeout với đối tượng dalat. Để ý: kết buộc hàm callback của setTimeout chứ không phải kết buộc setTimeout với đối tượng dalat.

const dalat = {

          mountains: ["langbiang", "voi", "bidoup", "pinhat"],

          print: function(delay = 1000) {

            console.log(this.mountains); // ['langbiang', 'voi', 'bidoup', 'pinhat']

               setTimeout(function(){

                    console.log(this.mountains); // ['langbiang', 'voi', 'bidoup', 'pinhat']

                    console.log(this.mountains.join(', '));

               }.bind(dalat), delay);

          }

     };

dalat.print(); // langbiang, voi, bidoup, pinhat

Nếu không dùng phương thức bind() như ở trên, bạn có thể sử dụng cách đơn giản hơn để sửa lỗi kết buộc là chuyển hàm callback của setTimeout thành arrow function,

 const dalat = {

          mountains: ["langbiang", "voi", "bidoup", "pinhat"],

          print: function(delay = 1000) {

               setTimeout(() => {

                    console.log(this.mountains.join(', '));

               }, delay);

          }

     };

     dalat.print();// langbiang, voi, bidoup, pinhat

Tuy nhiên, nếu chuyển tiếp hàm print thành arrow function, thì từ khóa this lại trỏ tới đối tượng Window.

const dalat = {

          mountains: ["langbiang", "voi", "bidoup", "pinhat"],

          print: (delay = 1000) => {

               setTimeout(() => {

                    console.log(this.mountains.join(', '));

               }, delay);

          }

     };

     dalat.print();// Uncaught TypeError: Cannot read property 'join' of undefined

1.1.4       Xem và đọc thêm

– Dùng các từ khóa sau tìm kiếm trên mạng để đọc thêm: từ khóa this trong JavaScript, bind() method.

– This (w3schools.com): https://www.w3schools.com/js/js_this.asp

– This (mozilla): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this?retiredLocale=vi

– bind() method: https://www.w3schools.com/js/js_function_bind.asp

– Phương thức bind() – F8: https://www.youtube.com/watch?v=F5z6YoR8of0

https://www.youtube.com/watch?v=6j9b2_E34JM

– [1] Alex Banks, Eve Porcello, Learning React – Mordern Patterns for Developing React Apps, O’Reilly Media, 2020, p12 – p17 

1.1.5       Bài tập và thực hành

Bài tập 1. Viết lại các đoạn mã trong phần lý thuyết để chạy và kiểm tra lại kết quả.

1.1.6       Câu hỏi ôn tập

Câu 1. Quan sát đoạn mã JavaScript sau, lệnh console.log(this) sẽ xuất gì ra cửa sổ console?

<script>

        let x = this;

        console.log(this)   

</script>

A. Thông báo lỗi

B. this

C. x

D. window

Câu 2. Đoạn mã JavaScript sau xuất gì ra cửa sổ console?

function testFunction() {

    console.log(this);

  }

  testFunction();

A. window

B. testFunction

C. this

D. function

Câu 3. Trong JavaScript, bạn có thể sử dụng phương thức bind() để làm gì?

A. Mượn phương thức của đối tượng khác

B. Xác định tham số this của một phương thức

C. Xác định phạm vi của biến

D. Đáp án A và B

Câu 4. Trong JavaScript, phương thức setTimeout(function, milliseconds) dùng để làm gì?

A. Thực thi một đoạn mã sau một khoảng thời gian định trước

B. Là một phương thức của đối tượng window

C. Thiết lập thời gian cho máy tính

D. Đáp án A và B

Câu 5. The ______ method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

A. let

B. bind()

C. new

D. const

-----

Đáp án: 1 (D), 2 (A), 3 (D), 4 (D), 5 (B)

-----

Bài tiếp: Web nâng cao (5) - JavaScript cho React (4)