网站搜索

在 TypeScript 函数中显式指定 `this: `


我正在阅读 TypeScript 手册中的《在函数中声明 this》,我被这个例子弄糊涂了:

... 但是在很多情况下,你需要对对象 this 的表示有更多控制。JavaScript 规范规定你不能有一个名为 this 的参数,因此 TypeScript 使用这个语法空间来让你在函数体中声明 this 的类型。这种模式在回调式 API 中很常见,因为通常由另一个对象来控制何时调用你的函数。

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

我没有完全理解这一段的意思,特别是如何实现 filterUsers 方法才能让显式的 this: User 生效,所以我决定把这个例子弄成可运行状态:

type User = {
  id: number;
  admin: boolean;
}

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const users: User[] = [
    { id: 1, admin: true },
    { id: 2, admin: false },
  ];

const db:DB = {
  filterUsers(filter: (this: User) => boolean) {
    return users.filter(filter);
  }
};


const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

console.log(admins);

显然,这个例子行不通,尽管编译器没有报错。从生成的 JavaScript 更能看出——this 在 function (this: User) { return this.admin; } 内不会是 User 实例:

"use strict";
const users = [
    { id: 1, admin: true },
    { id: 2, admin: false },
];
const db = {
    filterUsers(filter) {
        return users.filter(filter);
    }
};
const admins = db.filterUsers(function () {
    return this.admin;
});
console.log(admins);

那么,正确实现 filterUsers 的方法是什么,以便显式的 this: User 的概念能够工作并且有用?

答案#1

我建议使用接受 User 对象作为参数的过滤函数,而不是依赖 this:

type User = {
  id: number;
  admin: boolean;
}

interface DB {
  filterUsers(filter: (user: User) => boolean): User[];
}

const users: User[] = [
  { id: 1, admin: true },
  { id: 2, admin: false },
];

const db: DB = {
  filterUsers(filter: (user: User) => boolean) {
    return users.filter(filter);
  }
};

const admins = db.filterUsers((user: User) => {
  return user.admin;
});

console.log(admins);

如果你想尝试将参数名称设置为这个,你也可以在 filterUsers 方法内使用 filter.call(user),或者像 @rozsazoltan 那样显式绑定对象。

这确保了在过滤函数内部的 this 指向当前的用户对象。

type User = {
  id: number;
  admin: boolean;
}

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const users: User[] = [
  { id: 1, admin: true },
  { id: 2, admin: false },
];

const db: DB = {
  filterUsers(filter: (this: User) => boolean) {
    return users.filter(user => filter.call(user));
  }
};

const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

console.log(admins);

答案#2

通过使用bind,你可以确保在执行过滤函数时 this 确实是正确的 User 实例。

type User = {
  id: number;
  admin: boolean;
}

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const users: User[] = [
  { id: 1, admin: true },
  { id: 2, admin: false },
];

const db: DB = {
  filterUsers(filter: (this: User) => boolean) {
    // need to manually bind the `this` context to each user
    return users.filter(user => filter.bind(user)());
  }
};

const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

console.log(admins);

看起来你似乎无法控制 this 的值,因为它的值是自动设置的,但实际上情况并非如此。

每个函数都有 .bind 方法,它返回一个新的函数,其 this 被绑定到一个值上。该函数的行为与调用 .bind 的原函数完全相同,只是 this 由你设置。无论该函数如何或何时被调用,this 始终指向传入的值。

来源:如何在回调中访问正确的this
Felix Kling