简体   繁体   English

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

[英]How do I write an extension method in JavaScript?

I need to write a few extension methods in JS.我需要在JS中编写一些扩展方法。 I know just how to do this in C#.我知道如何在 C# 中做到这一点。 Example:例子:

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

and then called by:然后通过以下方式调用:

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

How would I do something like this in JavaScript?我将如何在 JavaScript 中做这样的事情?

JavaScript doesn't have an exact analogue for C#'s extension methods. JavaScript 没有与 C# 的扩展方法完全类似的东西。 JavaScript and C# are quite different languages. JavaScript 和 C# 是完全不同的语言。

The nearest similar thing is to modify the prototype object of all string objects: String.prototype .最接近类似的就是修改所有字符串对象的原型对象: String.prototype In general, best practice is not to modify the prototypes of built-in objects in library code meant to be combined with other code you don't control.通常,最佳实践是不要修改库代码中旨在与您无法控制的其他代码组合的内置对象的原型。 (Doing it in an application where you control what other code is included in the application is okay.) (在您控制应用程序中包含哪些其他代码的应用程序中执行此操作是可以的。)

If you do modify the prototype of a built-in, it's best (by far) to make that a non-enumerable property by using Object.defineProperty (ES5+, so basically any modern JavaScript environment, and not IE8¹ or earlier).如果您确实修改了内置的原型,最好(到目前为止)通过使用Object.defineProperty (ES5+,所以基本上是任何现代 JavaScript 环境,而不是 IE8¹ 或更早版本)使其成为不可枚举的属性。 To match the enumerability, writability, and configurability of other string methods, it would look like this:为了匹配其他字符串方法的可枚举性、可写性和可配置性,它看起来像这样:

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

(The default for enumerable is false .) enumerable的默认值为false 。)

If you needed to support obsolete environments, then for String.prototype , specifically, you could probably get away with creating an enumerable property:如果您需要支持过时的环境,那么对于String.prototype ,特别是,您可能会创建一个可枚举的属性:

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

That's not a good idea, but you might get away with it.这不是一个好主意,但你可能会侥幸逃脱。 Never do that with Array.prototype or Object.prototype ;永远不要用Array.prototypeObject.prototype这样做; creating enumerable properties on those is a Bad Thing™.在这些上创建可枚举的属性是一件坏事™。

Details:细节:

JavaScript is a prototypical language. JavaScript 是一种原型语言。 That means that every object is backed by a prototype object .这意味着每个对象都由原型对象支持。 In JavaScript, that prototype is assigned in one of four ways:在 JavaScript 中,该原型以四种方式之一分配:

  • By the constructor function for the object (eg, new Foo creates an object with Foo.prototype as its prototype)通过对象的构造函数(例如, new Foo创建了一个以Foo.prototype为原型的对象)
  • By the Object.create function added in ES5 (2009)通过 ES5 (2009) 中添加的Object.create函数
  • By the __proto__ accessor property (ES2015+, only on web browsers, existed in some environments before it was standardized) or Object.setPrototypeOf (ES2015+)通过__proto__访问器属性(ES2015+,仅在 Web 浏览器上,在标准化之前存在于某些环境中)或Object.setPrototypeOf (ES2015+)
  • By the JavaScript engine when creating an object for a primitive because you're calling a method on it (this is sometimes called "promotion")在为原始对象创建对象时由 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 .因此,在您的示例中,由于firstName是字符串原语,因此每当您对其调用方法时,它都会被提升为String实例,并且该String实例的原型是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).因此,向String.prototype添加一个引用您的SayHi函数的属性,可以使该函数在所有String实例上可用(并且在字符串原语上有效,因为它们得到提升)。

Example:例子:

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

There are some key differences between this and C# extension methods:此方法与 C# 扩展方法之间存在一些主要区别:

  • (As DougR pointed out in a comment) C#'s extension methods can be called on null references . (正如DougR在评论中指出的那样) C# 的扩展方法可以在null引用上调用 If you have a string extension method, this code:如果您有string扩展方法,则此代码:

     string s = null; s.YourExtensionMethod();

    works.作品。 That isn't true with JavaScript; JavaScript 不是这样。 null is its own type, and any property reference on null throws an error. null是它自己的类型,任何对null属性引用都会引发错误。 (And even if it didn't, there's no prototype to extend for the Null type.) (即使没有,也没有原型可以扩展为 Null 类型。)

  • (As ChrisW pointed out in a comment) C#'s extension methods aren't global. (正如ChrisW在评论中指出的那样) C# 的扩展方法不是全局的。 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 all code in the entire realm you do that in (a realm is the global environment and its associated intrinsic objects, etc.). (它们确实是静态调用的语法糖,这就是它们在null工作的原因。)在 JavaScript 中并非如此:如果您更改内置函数的原型,则整个领域中的所有代码都会看到该更改你这样做(领域是全球环境及其相关的内在对象等)。 So if you do this in a web page, all code you load on that page sees the change.因此,如果您在网页中执行此操作,则您在该页面上加载的所有代码都会看到更改。 If you do this in a Node.js module, all code loaded in the same realm as that module will see the change.如果您在 Node.js 模块中执行此操作,则在与该模块相同的领域中加载的所有代码都将看到更改。 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.) (Web 工作线程和 Node.js 工作线程加载在它们自己的领域中,因此它们具有与主线程不同的全局环境和不同的内在函数。但该领域仍然与它们加载的任何模块共享。)


¹ IE8 does have Object.defineProperty , but it only works on DOM objects, not JavaScript objects. ¹ IE8 确实有Object.defineProperty ,但它只适用于 DOM 对象,而不适用于 JavaScript 对象。 String.prototype is a JavaScript object. String.prototype是一个 JavaScript 对象。

Using 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
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM