【问题标题】:How do I write an extension method in JavaScript?如何在 JavaScript 中编写扩展方法?
【发布时间】:2012-03-10 09:22:08
【问题描述】:

我需要在 JS 中编写一些扩展方法。我知道如何在 C# 中做到这一点。示例:

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

然后被调用:

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

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

【问题讨论】:

    标签: javascript extension-methods


    【解决方案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
    });
    

    enumerable 的默认值为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 Foo 创建一个以Foo.prototype 为原型的对象)
    • 由 ES5 (2009) 中添加的 Object.create 函数
    • 通过 __proto__ 访问器属性(ES2015+,仅在 Web 浏览器上,在标准化之前存在于某些环境中)或 Object.setPrototypeOf (ES2015+)
    • 在为基元创建对象时由 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# 的扩展方法can be called on null references。如果你有string 扩展方法,这段代码:

        string s = null;
        s.YourExtensionMethod();
      

    有效(除非YourExtensionMethod 在接收到null 作为实例参数时抛出)。 JavaScript 不是这样。 null 是它自己的类型,null 上的任何属性引用都会引发错误。 (即使没有,也没有可以为 Null 类型扩展的原型。)

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

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

    【讨论】:

    • @Grundy:谢谢,是的,String s = null; 部分的重点是使用s.YourExtensionMethod()!欣赏渔获。 :-)
    • 如果你在 Node.js 中执行此操作,我猜这会影响(添加新属性到)程序中的每个字符串——包括其他模块?如果两个模块都定义了“SayHi”属性,它们会发生冲突吗?
    • @ChrisW - 相当。 :-) 您可能会做的是创建一个 Array 子类 (class MyArray extends Array { singleOrDefault() { ... }}) 但这意味着如果 originalArray 是一个无聊的旧数组,则在使用它之前您必须先执行 MyArray.from(originalArray)。还有类似 LINQ 的 JavaScript 库 (here's a roundup),同样涉及在执行操作之前从数组中创建一个 Linq 对象(嗯,LINQ to JavaScript 做到了,我没有使用过其他的 [也没有使用过那个)多年])。 (顺便说一句,更新了答案,谢谢。)
    • 今天我才知道可以安全地在空实例上调用 C# 扩展方法。谢谢!
    • @Daniel - 是的,它非常适合空检查参数
    【解决方案2】:

    使用Global augmentation

    declare global {
        interface DOMRect {
            contains(x:number,y:number): boolean;
        }
    }
    /* Adds contain method to class DOMRect */
    DOMRect.prototype.contains = function (x:number, y:number) {                    
        if (x >= this.left && x <= this.right &&
            y >= this.top && y <= this.bottom) {
            return true
        }
        return false
    };
    

    【讨论】:

    • 这是否适用于纯 / 香草 javascript,还是我必须弄清楚如何使用打字稿?我在问,因为您链接到 typescriptlang.org 而不是说 ES6 文档或 MDN
    猜你喜欢
    • 1970-01-01
    • 2018-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-15
    • 2014-01-23
    相关资源
    最近更新 更多