I need to write a few extension methods in JS. I know just how to do this in 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 doesn’t have an exact analogue for C#’s extension methods. JavaScript and C# are quite different languages.
The nearest similar thing is to modify the prototype object of all string objects:
String.prototype. Add your function as a method to the prototype, and usethiswithin the function to access the string it was called on. (Don’t use an arrow function, since you wantthiscontrolled by the caller, you don’t want to close over it.) 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). To match the enumerability, writability, and configurability of other string methods, it would look like this:(The default for
enumerableisfalse.)If you needed to support obsolete environments, then for
String.prototype, specifically, you could probably get away with creating an enumerable property:That’s not a good idea, but you might get away with it. Never do that with
Array.prototypeorObject.prototype; creating enumerable properties on those is a Bad Thing™.Details:
JavaScript is a prototypical language. That means that every object is backed by a prototype object. In JavaScript, that prototype is assigned in one of four ways:
new Foocreates an object withFoo.prototypeas its prototype)Object.createfunction added in ES5 (2009)Object.setPrototypeOffunction (ES2015+) [or the deprecated__proto__setter (ES2015+, optional, and only exists on objects that inherit [directly or indirectly] fromObject.prototype), orSo in your example, since
firstNameis a string primitive, it gets promoted to aStringinstance whenever you call a method on it, and thatStringinstance’s prototype isString.prototype. So adding a property toString.prototypethat references yourSayHifunction makes that function available on allStringinstances (and effectively on string primitives, because they get promoted).Example:
There are some key differences between this and C# extension methods:
(As DougR pointed out in a comment) C#’s extension methods can be called on
nullreferences. If you have astringextension method, this code:works (unless
YourExtensionMethodthrows when it receivesnullas the instance parameter). That isn’t true with JavaScript;nullis its own type, and any property access onnullthrows an error. (And even if it didn’t, there’s no prototype to extend for the Null type.)(As ChrisW 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 all 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, 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. 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.)¹ IE8 does have
Object.defineProperty, but it only works on DOM objects, not JavaScript objects.String.prototypeis a JavaScript object.