如何在 JavaScript 中编写扩展方法?

2022-08-30 05:30:10

我需要在JS中编写一些扩展方法。我知道如何在C#中执行此操作。例:

public static string SayHi(this Object name)
{
    return "Hi " + name + "!";
}

然后由以下人员调用:

string firstName = "Bob";
string hi = firstName.SayHi();

我该如何在JavaScript中做这样的事情?


答案 1

JavaScript 没有 C# 扩展方法的确切类似物。JavaScript 和 C# 是完全不同的语言。

最接近的类似事情是修改所有字符串对象的原型对象:.通常,最佳做法是不要修改库代码中内置对象的原型,这些代码旨在与您无法控制的其他代码结合使用。(在控制应用程序中包含的其他代码的应用程序中执行此操作是可以的。String.prototype

如果您确实修改了内置的原型,最好(到目前为止)通过使用Object.defineProperty(ES5 +,因此基本上是任何现代JavaScript环境,而不是IE8¹或更早版本)使其成为不可枚举的属性。为了匹配其他字符串方法的可枚举性、可写性和可配置性,它如下所示:

Object.defineProperty(String.prototype, "SayHi", {
    value: function SayHi() {
        return "Hi " + this + "!";
    },
    writable: true,
    configurable: true,
});

可枚举的默认值为 false

如果您需要支持过时的环境,那么对于,具体来说,您可能会创建一个可枚举的属性:String.prototype

// Don't do this if you can use `Object.defineProperty`
String.prototype.SayHi = function SayHi() {
    return "Hi " + this + "!";
};

这不是一个好主意,但你可能会逃脱它。切勿使用 或 ;在这些属性上创建可枚举属性是一件™坏事。Array.prototypeObject.prototype

详:

JavaScript是一种原型语言。这意味着每个对象都由一个原型对象支持。在 JavaScript 中,该原型以以下四种方式之一进行分配:

  • 通过对象的构造函数(例如,创建一个对象作为其原型)new FooFoo.prototype
  • 通过 ES5 中添加的函数 (2009)Object.create
  • 通过函数 (ES2015+) [或已弃用的 setter (ES2015+,可选,并且仅存在于 [直接或间接] 继承自 的对象上),或者Object.setPrototypeOf__proto__Object.prototype
  • 在为基元创建对象时,通过JavaScript引擎,因为您正在对其调用方法(这有时称为“提升”)

因此,在您的示例中,由于 是一个字符串基元,因此每当您在其上调用方法时,它就会提升为实例,并且该实例的原型是 。因此,向函数的引用添加一个属性会使该函数在所有实例上都可用(并且有效地在字符串基元上可用,因为它们会被提升)。firstNameStringStringString.prototypeString.prototypeSayHiString

例:

Object.defineProperty(String.prototype, "SayHi", {
    value: function SayHi() {
        return "Hi " + this + "!";
    },
    writable: true,
    configurable: true
});

console.log("Charlie".SayHi());

此方法与 C# 扩展方法之间存在一些主要区别:

  • (正如DougR在评论中指出的那样)可以在引用上调用 C# 的扩展方法。如果您有扩展方法,则代码:string

      string s = null;
      s.YourExtensionMethod();
    

    工作(除非当它作为实例参数接收时抛出)。对于JavaScript来说,情况并非如此。 是它自己的类型,并且 上的任何属性访问都会引发错误。(即使它没有,也没有原型可以扩展到 Null 类型。YourExtensionMethodnullnullnull

  • (正如ChrisW在评论中指出的那样)C# 的扩展方法不是全局的。仅当使用扩展方法的代码使用在其中定义它们的命名空间时,才可以访问它们。(它们实际上是静态调用的语法糖,这就是它们工作的原因。在JavaScript中并非如此:如果你更改了内置的原型,那么在你执行此操作的整个领域中的所有代码都会看到该更改(领域是全局环境及其关联的内部对象等)。因此,如果您在网页中执行此操作,则您在该页面上加载的所有代码都会看到更改。如果在 Node.js 模块中执行此操作,则在与该模块相同的域中加载的所有代码都将看到更改。在这两种情况下,这就是为什么您不在库代码中执行此操作的原因。(Web workers 和 Node.js worker 线程都装入到它们自己的领域中,因此它们具有与主线程不同的全局环境和不同的内部函数。但是这个领域仍然与他们加载的任何模块共享。null


¹ IE8 确实有 ,但它只适用于 DOM 对象,而不适用于 JavaScript 对象。 是一个 JavaScript 对象。Object.definePropertyString.prototype


答案 2

每个对象都有一个父对象(原型),你可以通过将任何对象记录到控制台来证明这一点,你会看到一个原型对象,你可以展开原型对象来查看所有方法和属性(在浏览器中使用开发工具)。下面的示例将向 Array 原型添加一个将继承的新方法。

Array.prototype.hello = function() {
        console.log('hello')
}