Typecript "this" bên trong một phương thức lớp


81

Tôi biết điều này có lẽ rất cơ bản, nhưng tôi đang gặp khó khăn trong việc quấn lấy nó.

class Main
{
     constructor()
     {
         requestAnimationFrame(this.update);  //fine    
     }

     update(): void
     {
         requestAnimationFrame(this.update);  //error, because this is window
     }

}

Có vẻ như tôi cần một proxy, vì vậy hãy sử dụng Jquery

class Main
{
     constructor()
     {
         this.updateProxy = $.proxy(this.update, this);
         requestAnimationFrame(this.updateProxy);  //fine    
     }

     updateProxy: () => void
     update(): void
     {
         requestAnimationFrame(this.updateProxy);  //fine
     }

}

Nhưng đến từ nền tảng Actionscript 3, tôi không thực sự chắc chắn điều gì đang xảy ra ở đây. Xin lỗi, tôi không chắc nơi Javascript bắt đầu và TypeScript kết thúc.

updateProxy: () => void

Và tôi cũng không tin rằng mình đang làm đúng. Điều cuối cùng tôi muốn là hầu hết lớp của tôi có hàm aa () cần được truy cập aProxy()vì tôi cảm thấy mình đang viết cùng một thứ hai lần? Nó có bình thường không?


Tôi thấy tài liệu này rất hữu ích github.com/Microsoft/TypeScript/wiki/…
rainversion_3

Câu trả lời:


117

Nếu bạn muốn thisnắm bắt, cách thực hiện của TypeScript là thông qua các hàm mũi tên. Để trích dẫn Anders:

Các thishàm trong mũi tên có phạm vi từ vựng

Đây là cách tôi muốn sử dụng điều này để có lợi cho mình:

class test{
    // Use arrow functions
    func1=(arg:string)=>{
            return arg+" yeah" + this.prop;
    }
    func2=(arg:number)=>{
            return arg+10 + this.prop;
    }       

    // some property on this
    prop = 10;      
}

Xem cái này trong Sân chơi TypeScript

Bạn có thể thấy rằng trong JavaScript đã tạo thisđược ghi lại bên ngoài lệnh gọi hàm:

var _this = this;
this.prop = 10;
this.func1 = function (arg) {
    return arg + " yeah" + _this.prop;
};

vì vậy thisgiá trị bên trong lời gọi hàm (có thể là window) sẽ không được sử dụng.

Để tìm hiểu thêm: “Hiểu về thisTypeScript” (4:05) - YouTube


2
Điều này thì không cần thiết. Những gì bạn đang đề xuất là JavaScript thành ngữ, nhưng TypeScript làm cho điều này không cần thiết.
Tatiana Racheva

1
@TatianaRacheva sử dụng các hàm mũi tên trong ngữ cảnh của các thành viên trong lớp không được phép trước TS 0.9.1 (và câu trả lời này đã có trước đó). Câu trả lời được cập nhật để cú pháp mới :)
basarat

18
BẠN PHẢI XEM VIDEO - RẤT HỮU ÍCH. CHỈ 5 PHÚT
Simon_Weaver

1
Cảm ơn bạn, @basarat. Bóng đèn tiếp tục tìm ngữ cảnh cho điều này ngay khi tôi thấy bạn sử dụng chức năng mũi tên nửa chừng qua video của mình. Tôi đánh giá cao bạn.
Simon

1
@AaronLS trên sân chơi TypeScript để tạo, var _this = this;bạn phải chọn ES5; kể từ ES2015 - các chức năng bên trong - thisđược sử dụng thay cho_this
Stefano Spinucci.

18

Nếu bạn viết các phương thức của mình như thế này, 'điều này' sẽ được xử lý theo cách bạn mong đợi.

class Main
{
    constructor()
    {
        requestAnimationFrame(() => this.update());
    }

    update(): void
    {
        requestAnimationFrame(() => this.update());
    }
}

Một tùy chọn khác sẽ là liên kết 'this' với lệnh gọi hàm:

class Main
{
    constructor()
    {
        requestAnimationFrame(this.update.bind(this));
    }

    update(): void
    {
        requestAnimationFrame(this.update.bind(this));
    }
}

2
Theo kinh nghiệm của tôi, chức năng cập nhật được định nghĩa tốt hơn như thế này: update = () => {...}
Shaun Rowan

Tôi đã sử dụng rất nhiều kiểu chữ và đã thay đổi qua lại nhiều lần. hiện tại, tôi chỉ bao gồm dấu ngoặc nhọn nếu nó không chỉ là một cuộc gọi phương thức đơn giản. cũng như khi sử dụng stylescript + linq (thật tuyệt vời), định dạng đẹp hơn. ví dụ: Enumerable.From (arr) .Where (o => o.id == 123);
joelnet

Tôi nghĩ nếu bạn nhìn vào javascript được tạo ra, bạn sẽ thấy một sự khác biệt đáng kể. Nó không phải là một vấn đề của hương vị. update = () => {} sẽ tạo phạm vi từ vựng thông qua biên dịch "var _this = this", cú pháp của bạn thì không.
Shaun Rowan

1
Bạn có thể cần phải nâng cấp thư viện TypeScript của mình vì nó hoàn toàn bao gồm ngữ cảnh "_this". Sử dụng "() => code ()" hoặc () => {return code (); .}" Ý chí đầu ra 100% mã javascript giống hệt Dưới đây là kết quả: i.imgur.com/I5J12GE.png Bạn cũng có thể xem cho chính mình bằng cách dán mã vào. Typescriptlang.org/Playground
joelnet

rõ ràng bind (này) có thể là xấu vì nó thua loại an toàn trên args chức năng gốc
robert vua

6

Xem trang 72 của thông số kỹ thuật ngôn ngữ phiên bản https://github.com/Microsoft/TypeScript/blob/master/doc/TypeScript%20Language%20Specification.pdf?raw=true

Biểu thức hàm mũi tên

Trong ví dụ

class Messenger {
 message = "Hello World";
 start() {
 setTimeout(() => alert(this.message), 3000);
 }
};
var messenger = new Messenger();
messenger.start();

việc sử dụng biểu thức hàm mũi tên khiến cho lệnh gọi lại giống như phương thức 'start' xung quanh. Viết callback dưới dạng một biểu thức hàm tiêu chuẩn, cần phải sắp xếp thủ công quyền truy cập vào vùng xung quanh này, ví dụ: bằng cách sao chép nó vào một biến cục bộ:

Đây là Javascript được tạo thực tế:

class Messenger {
 message = "Hello World";
 start() {
 var _this = this;
 setTimeout(function() { alert(_this.message); }, 3000);
 }
};

Liên kết bị hỏng một cách đáng tiếc
Jimmy Kane

1
@JimmyKane Tôi đã cập nhật liên kết. Kỳ lạ là tài liệu này đã hơn một năm và vẫn được trang chủ của họ tham chiếu nhưng nội dung quan trọng mà tôi đưa vào câu trả lời.
Simon_Weaver

4

Vấn đề nảy sinh khi bạn chuyển một hàm dưới dạng gọi lại. Vào thời điểm lệnh gọi lại đã thực thi, giá trị của "this" có thể đã thay đổi thành Cửa sổ, điều khiển gọi lệnh gọi lại hoặc một cái gì đó khác.

Đảm bảo rằng bạn luôn sử dụng biểu thức lambda tại thời điểm bạn chuyển một tham chiếu đến hàm được gọi lại. Ví dụ

public addFile(file) {
  this.files.push(file);
}
//Not like this
someObject.doSomething(addFile);
//but instead, like this
someObject.doSomething( (file) => addFile(file) );

Điều này biên dịch thành một cái gì đó giống như

this.addFile(file) {
  this.files.push(file);
}
var _this = this;
someObject.doSomething(_this.addFile);

Vì hàm addFile đang được gọi trên một tham chiếu đối tượng cụ thể (_this), nó không sử dụng "this" của invoker mà thay vào đó là giá trị của _this.


Khi bạn nói nó biên dịch thành cái gì, bạn đang hiển thị cái nào? (Ví dụ mũi tên hay đối tượng chỉ truyền đối tượng phương thức?)
Vaccano

Con lambda. Chỉ cần tạo TS bằng mã đó và xem nó biên dịch những gì.
Peter Morris

2

Tóm lại, từ khóa this luôn có tham chiếu đến đối tượng được gọi là hàm.

Trong Javascript, vì các hàm chỉ là các biến nên bạn có thể chuyển chúng xung quanh.

Thí dụ:

var x = {
   localvar: 5, 
   test: function(){
      alert(this.localvar);
   }
};

x.test() // outputs 5

var y;
y.somemethod = x.test; // assign the function test from x to the 'property' somemethod on y
y.test();              // outputs undefined, this now points to y and y has no localvar

y.localvar = "super dooper string";
y.test();              // outputs super dooper string

Khi bạn làm như sau với jQuery:

$.proxy(this.update, this);

Những gì bạn đang làm đang ghi đè bối cảnh đó. Hậu trường jQuery sẽ cung cấp cho bạn điều này:

$.proxy = function(fnc, scope){
  return function(){
     return fnc.apply(scope);  // apply is a method on a function that calls that function with a given this value
  }
};

2

Buổi tiệc rất muộn, nhưng tôi nghĩ rằng điều rất quan trọng đối với những vị khách tương lai của câu hỏi này là phải xem xét những điều sau:

Các câu trả lời khác, bao gồm cả câu được chấp nhận, thiếu một điểm quan trọng:

myFunction() { ... }

myFunction = () => { ... }

không được điều tương tự "với ngoại lệ là ảnh chụp sau this".

Cú pháp đầu tiên tạo một phương thức trên nguyên mẫu, trong khi cú pháp thứ hai tạo một thuộc tính trên đối tượng whos value là một hàm (điều đó cũng xảy ra để nắm bắt this). Bạn có thể thấy rõ điều này trong JavaScript đã chuyển đổi.

Để hoàn thành:

myFunction = function() { ... }

sẽ giống như cú pháp thứ hai, nhưng không có lệnh bắt.

Vì vậy, sử dụng cú pháp mũi tên trong hầu hết các trường hợp sẽ khắc phục được vấn đề liên kết với đối tượng của bạn, nhưng nó không giống nhau và có nhiều trường hợp bạn muốn có một hàm thích hợp trên nguyên mẫu thay vì một thuộc tính.

Trong những trường hợp này, sử dụng proxy hoặc .bind()thực sự giải pháp chính xác. (Mặc dù có thể đọc được.)

Đọc thêm ở đây (không chủ yếu về TypeScript, mà là các nguyên tắc):

https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1

https://ponyfoo.com/articles/binding-methods-to-class-instance-objects


0

Làm thế nào về làm theo cách này? Khai báo một biến toàn cục kiểu "myClass" và khởi tạo nó trong hàm tạo của lớp:

var _self: myClass;

class myClass {
    classScopeVar: string = "hello";

    constructor() {
        _self = this;
    }

    alerter() {
        setTimeout(function () {
            alert(_self.classScopeVar)
        }, 500);
    }
}

var classInstance = new myClass();
classInstance.alerter();

Lưu ý: Điều quan trọng là KHÔNG sử dụng "self" vì nó đã có nghĩa đặc biệt rồi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.