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

Mat*_*att 73 javascript extension-methods

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

public static string SayHi(this Object name)
{
    return "Hi " + name + "!";
}
Run Code Online (Sandbox Code Playgroud)

然后叫:

string firstName = "Bob";
string hi = firstName.SayHi();
Run Code Online (Sandbox Code Playgroud)

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

T.J*_*der 128

JavaScript没有C#扩展方法的精确模拟.JavaScript和C#是完全不同的语言.

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

如果你确实修改了内置的原型,那么最好(到目前为止)通过使用(ES5 +,基本上任何现代JavaScript环境,而不是IE8¹或更早版本)来创建一个不可枚举的属性Object.defineProperty.为了匹配其他字符串方法的可枚举性,可写性和可配置性,它看起来像这样:

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

(为默认enumerablefalse).

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

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

这不是一个好主意,但你可能会侥幸逃脱.永远不要用Array.prototypeObject.prototype; 在那些上创建可枚举的属性是Bad Thing™.

细节:

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

  • 通过对象的构造函数(例如,new Foo创建一个对象Foo.prototype作为其原型)
  • 通过Object.createES5(2009)中添加的功能
  • 通过__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());
Run Code Online (Sandbox Code Playgroud)

这个和C#扩展方法之间有一些关键的区别:

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

    string s = null;
    s.YourExtensionMethod();
    
    Run Code Online (Sandbox Code Playgroud)

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

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


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