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

问题描述 投票:0回答:1

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

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

然后拨打:

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

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

javascript extension-methods
1个回答
201
投票

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

最接近的类似的事情是修改所有字符串对象的原型对象:

String.prototype
。将函数作为方法添加到原型中,并在函数中使用
this
来访问调用它的字符串。 (不要使用箭头函数,因为您希望
this
由调用者控制,因此您不想关闭它。)一般来说,最佳实践是修改内置函数的原型库代码中的对象意味着与您无法控制的其他代码组合。 (在您控制应用程序中包含哪些其他代码的应用程序中执行此操作是可以的。)

如果您修改内置函数的原型,那么(到目前为止)最好使用Object.defineProperty

(ES5+,所以基本上任何现代JavaScript环境,而不是IE8 或更早版本)。为了匹配其他字符串方法的可枚举性、可写性和可配置性,它看起来像这样:
Object.defineProperty(String.prototype, "SayHi", { value: function SayHi() { return "Hi " + this + "!"; }, writable: true, configurable: true, });

enumerable默认为

false
。)

如果您需要支持过时的环境,那么对于

String.prototype

,具体而言,您可能可以通过创建可枚举属性来避免:

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

这不是一个好主意,但你可能会侥幸逃脱。 
切勿

使用 Array.prototype

Object.prototype
这样做;在这些属性上创建可枚举属性是一件坏事™。
详情:

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

通过对象的 构造函数

(例如,
    new Foo
  • 创建一个以 Foo.prototype 作为原型的对象)
    通过 ES5 (2009) 中添加的 
    Object.create
    函数
  • 通过
    Object.setPrototypeOf
    函数 (ES2015+) [或已弃用的
  • __proto__
  • setter(ES2015+,可选,仅存在于[直接或间接]继承于
    Object.prototype
    的对象上),或
    在为原语创建对象时由 JavaScript 引擎执行,因为您要调用它的方法(有时称为“提升”)
    
    
  • 因此,在您的示例中,由于
  • firstName
  • 是一个字符串基元,因此每当您调用它的方法时,它就会被提升为
String

实例,并且

String
实例的原型是
String.prototype
。因此,向
String.prototype
添加一个引用您的
SayHi
函数的属性,可以使该函数在所有
String
实例上可用(并且有效地在字符串基元上,因为它们得到提升)。
示例:

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


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

(正如
    DougR
  • 在评论中指出的那样)

    C# 的扩展方法可以在 null 引用上调用。如果您有 string

     扩展方法,则此代码:
    string s = null; s.YourExtensionMethod();
    有效(除非 

    YourExtensionMethod
     在收到 
    null

    作为实例参数时抛出)。 JavaScript 则不然。

    null
    是它自己的类型,对
    null
    的任何属性访问都会引发错误。 (即使没有,也没有原型可以扩展 Null 类型。)
    
    

    (正如
  • ChrisW
  • 在评论中指出的那样)

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

    它们
    加载。) 1 IE8 确实有 Object.defineProperty,但它只适用于 DOM 对象,不适用于 JavaScript 对象。 String.prototype

    是一个 JavaScript 对象。

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

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

© www.soinside.com 2019 - 2024. All rights reserved.