You might have seen a new property being added to a native JavaScript object through assignment like so:
String.prototype.capitalize = function () { return this.charAt(0).toUpperCase() + this.slice(1); } console.log('foo'.capitalize()); // Foo
Or, some people use Object.assign()
too for this, like so:
// ES6+ Object.assign(String.prototype, { capitalize() { return this.charAt(0).toUpperCase() + this.slice(1); } }); console.log('foo'.capitalize()); // Foo
While they both "work", the implementations are problematic for the following reasons:
- The property you add would show up during property enumeration (for example using
for...in
loop orObject.keys
method) which might not be be the desired behavior in some cases; - The property could easily be overwritten by another implementation of the same name (for e.g. by a library, or another piece of code in your project code base);
- The property could be deleted.
All of the above could easily lead to unwanted side-effects and problems and break your code. Therefore, you should consider using any of the other solutions suggested in this article.
Using Object.defineProperty()
to Extend Native JavaScript Objects
An ideal approach would be to use Object.defineProperty()
(which is available since ES5). It provides us with finer control over the property we wish to add to an existing object, by allowing us to add descriptors. For example, we can decide whether we want to allow the property to be overwritten, deleted, or enumerable.
Let's re-write our capitalize
function using Object.defineProperty()
:
// ES5+ Object.defineProperty(String.prototype, 'capitalize', { // prevent it from being overwritten writable: false, // prevent it from being enumerated enumerable: false, // prevent it from being deleted configurable: false, value() { return this.charAt(0).toUpperCase() + this.slice(1); } }); console.log('foo'.capitalize()); // Foo
Please note that writable: false
, enumerable: false
and configurable: false
are default values and can safely be ommited. They were added to show how the behavior of the new property can be defined using Object.defineProperty
.
Creating a New Class That Extends the Native JavaScript Object
Another approach we could take is to create a separate class altogether that extends the native object. This way we do not pollute the native object, and are more conscious about the methods we add/remove. For example:
// ES6+ class MyString extends String { capitalize() { return this.charAt(0).toUpperCase() + this.slice(1); } } const str = new MyString('foo'); console.log(str.capitalize()); // Foo // and you have access to all the native `String` methods console.log(str.toUpperCase()); // FOO
This could especially be useful if you have many new methods you would like to introduce to the same object type.
You could also re-write the capitalize
method as a static method, like so:
class MyString extends String { static capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } } console.log(MyString.capitalize('foo')); // Foo
Use of static methods is a highly opinionated topic — you might want to spend some time to understand the arguments for and against it before you use it.
Creating a Function
You could alternatively, create a function, like so:
function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } console.log(capitalize('foo')); // Foo
The advantage of this approach is that we could export
and import
/require
individual functions and use them wherever we need them.
Please note that import
and export
are a feature of ES6.
This post was published by Daniyal Hamid. Daniyal currently works as the Head of Engineering in Germany and has 20+ years of experience in software engineering, design and marketing. Please show your love and support by sharing this post.