我需要在JS中编写一些扩展方法。我知道如何在C#中做到这一点。例:
public static string SayHi(this Object name)
{
return "Hi " + name + "!";
}
然后由以下人员调用:
string firstName = "Bob";
string hi = firstName.SayHi();
我将如何在JavaScript中执行类似的操作?
我需要在JS中编写一些扩展方法。我知道如何在C#中做到这一点。例:
public static string SayHi(this Object name)
{
return "Hi " + name + "!";
}
然后由以下人员调用:
string firstName = "Bob";
string hi = firstName.SayHi();
我将如何在JavaScript中执行类似的操作?
Answers:
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
});
(默认enumerable
值为false
。)
如果您需要支持过时的环境,那么特别是对于String.prototype
,您可能可以创建一个可枚举的属性:
// Don't do this if you can use `Object.defineProperty`
String.prototype.SayHi = function SayHi() {
return "Hi " + this + "!";
};
这不是一个好主意,但是您可能会摆脱它。从来没有做到这一点与Array.prototype
或Object.prototype
; 在这些东西上创建可枚举的属性是Bad Thing™。
细节:
JavaScript是一种原型语言。这意味着每个对象都由原型对象支持。在JavaScript中,该原型是通过以下四种方式之一分配的:
new Foo
创建一个Foo.prototype
以其原型为对象的对象)Object.create
ES5(2009)中添加的功能__proto__
accessor属性(ES2015 +,仅在Web浏览器上,在标准化之前已存在于某些环境中)或Object.setPrototypeOf
(ES2015 +)因此,在您的示例中,由于firstName
是字符串原语,所以String
只要您在实例上调用方法,它就会被提升为实例,并且该String
实例的原型为String.prototype
。因此String.prototype
,在引用SayHi
该函数的属性中添加一个属性会使该函数在所有String
实例上可用(并且在字符串原语上有效,因为它们得到提升)。
例:
此扩展方法与C#扩展方法之间存在一些关键区别:
(正如DougR在评论中指出的那样) C#的扩展方法可以在null
reference上调用。如果有string
扩展方法,则此代码:
string s = null;
s.YourExtensionMethod();
作品。JavaScript并非如此;null
是它自己的类型,任何属性引用都会null
引发错误。(即使没有,也没有原型可以扩展为Null类型。)
(正如ChrisW在评论中指出的那样) C#的扩展方法不是全局的。仅当使用扩展方法的代码使用了定义的名称空间时,才可以访问它们。(它们实际上是静态调用的语法糖,这就是为什么它们可以使用的原因null
。)在JavaScript中不是这样的:如果更改内置原型,则您整个领域中的所有代码都可以看到该更改。在(领域是全局环境及其关联的内部对象等)中执行此操作。因此,如果您在网页上执行此操作,则在该页面上加载的所有代码都会看到更改。如果在Node.js模块中执行此操作,则所有在与该模块相同的领域中加载的代码会看到更改。在这两种情况下,这就是为什么您不在库代码中执行此操作的原因。(Web工作者和Node.js工作者线程是在其自己的领域中加载的,因此它们具有与主线程不同的全局环境和不同的内在函数。但是该领域仍与它们加载的任何模块共享。)
¹IE8确实具有Object.defineProperty
,但它仅适用于DOM对象,而不适用于JavaScript对象。String.prototype
是一个JavaScript对象。
String s = null;
部分的全部要用s.YourExtensionMethod()
!欣赏收获。:-)
class MyArray extends Array { singleOrDefault() { ... }}
),但这意味着MyArray.from(originalArray)
如果originalArray
是一个无聊的旧数组,则必须在使用它之前先做。还有一些针对JavaScript的类似LINQ的库(这是一个综述),类似地,它涉及在执行操作之前从数组创建Linq对象(嗯,LINQ to JavaScript就是这样做的,我没有使用过其他工具[也没有使用过那个多年])。(顺便说一句,更新了答案,谢谢。)
使用全局扩充
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
};