如何用 JavaScript 编写扩展方法?

我需要用 JS 编写一些扩展方法。我知道怎么用 C # 做这件事。例如:

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

然后被叫做:

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

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

62671 次浏览

JavaScript doesn't have an exact analogue for C#'s extension methods. JavaScript and C# are quite different languages.

最接近的类似方法是修改所有字符串对象的原型对象: String.prototype。一般来说,最佳实践是 没有修改库代码中内置对象的原型,以便与您不能控制的其他代码组合在一起。(在应用程序中执行此操作可以控制应用程序中包含哪些其他代码。)

如果您使用 Object.defineProperty(ES5 + ,所以基本上是任何现代 JavaScript 环境,而不是 IE81或更早的版本)修改内置原型,那么最好(到目前为止)使用 Object.defineProperty(ES5 + ,所以基本上是任何现代 JavaScript 环境,而不是 IE81或更早的版本)将其设置为 无法计数属性。为了与其他字符串方法的可枚举性、可写性和可配置性相匹配,应该是这样的:

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

(enumerable的默认值是 false。)

If you needed to support obsolete environments, then for String.prototype, specifically, you could probably get away with creating an enumerable property:

// 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函数
  • 通过 Object.setPrototypeOf函数(ES2015 +)[或不推荐的 __proto__ setter (ES2015 + ,可选,并且只存在于[直接或间接]继承自 Object.prototype的对象上) ,或者
  • 在为原语创建对象时,由 JavaScript 引擎调用该对象,因为您正在调用该对象上的方法(这有时被称为“升级”)

So in your example, since firstName is a string primitive, it gets promoted to a String instance whenever you call a method on it, and that String instance's prototype is String.prototype. So adding a property to String.prototype that references your SayHi function makes that function available on all String instances (and effectively on string primitives, because they get promoted).

例如:

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


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

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

  • (正如 DougR在注释中指出的) C # 的扩展方法 可以在 null引用上调用:

      string s = null;
    s.YourExtensionMethod();
    

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

  • (As 克里斯 pointed out in a comment) C#'s extension methods aren't global. They're only accessible if the namespace they're defined in is used by the code using the extension method. (They're really syntactic sugar for static calls, which is why they work on null.) That isn't true in JavaScript: If you change the prototype of a built-in, that change is seen by 所有 code in the entire realm you do that in (a realm is the global environment and its associated intrinsic objects, etc.). So if you do this in a web page, 所有 code you load on that page sees the change. If you do this in a Node.js module, 所有 code loaded in the same realm as that module will see the change. In both cases, that's why you don't do this in library code. (Web workers and Node.js worker threads are loaded in their own realm, so they have a different global environment and different intrinsics than the main thread. But that realm is still shared with any modules they load.)


1 IE8确实有 Object.defineProperty,但它只能在 DOM 对象上工作,而不能在 JavaScript 对象上工作。

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

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