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

时间:2012-02-19 23:16:09

标签: javascript

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

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

然后通过以下方式调用:

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

我如何在JavaScript中执行此类操作?

2 个答案:

答案 0 :(得分:121)

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执行此操作;在那些上创建可枚举的属性是Bad Thing™。

详细说明:

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

  • 通过对象的构造函数(例如,new Foo创建一个以Foo.prototype为原型的对象)
  • 通过ES5(2009)
  • 中添加的Object.create功能
  • 通过__proto__访问者属性(ES2015 +,仅限于网络浏览器,在标准化之前存在于某些环境中)或Object.setPrototypeOf(ES2015 +)
  • 通过JavaScript引擎为基元创建对象,因为您正在调用它上面的方法(这有时称为“促销”)

因此,在您的示例中,由于firstName是一个字符串原语,只要您在其上调用方法,它就会被提升为String实例,并且String实例的原型是{{ 1}}。因此,向String.prototype添加一个引用您的String.prototype函数的属性会使该函数在所有SayHi实例上可用(并且对字符串原语有效,因为它们会被提升)。

示例:

String

这个和C#扩展方法之间存在一些关键差异:

  • (在评论中指出DougR C#的扩展方法can be called on null references。如果您使用Object.defineProperty(String.prototype, "SayHi", { value: function SayHi() { return "Hi " + this + "!"; }, writable: true, configurable: true }); console.log("Charlie".SayHi());扩展方法,则此代码为:

    string

    的工作原理。 JavaScript的情况并非如此; string s = null; s.YourExtensionMethod(); 是它自己的类型,null上的任何属性引用都会引发错误。 (即使它没有,也没有为Null类型扩展的原型。)

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


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

答案 1 :(得分:0)

使用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
};