'JavaScript: Best way to add custom properties/methods to built-in objects (e.g. arrays)
It is known that you should not customize the built-in prototypes in JavaScript. But, what is the best way to add custom properties/methods to specific instances of theses classes? Things to consider are performance, clean code, easy to use, drawbacks/limitations etc...
I have in mind the following 4 ways to to this, and you are free to present your own. In the examples I have only 1 array and only 2 methods to add, but the question is about many objects with many additinal properties/methods.
1st way: Add methods directly to objects
let myArray1 = [1,2,3,2,1];
myArray1.unique = () => [...new Set(myArray1)];
myArray1.last = () => myArray1.at(-1);
2nd way, create a new (sub)class
class superArray extends Array{
constructor() {
super();
}
unique(){return [...new Set(this)]}
last(){return this.at(-1)}
}
let myArray2 = superArray.from([1,2,3,2,1]);
(The above example was updated, responding to @Bergi's comments)
3rd way, Object.assign
let superArrayMethods = {
unique: function(){return [...new Set(this)]},
last: function(){return this.at(-1)}
}
let myArray3 = [1,2,3,2,1];
myArray3 = Object.assign(myArray3, superArrayPrototype);
4th method, set prototype of en enhanced prototype of the Array prototype
let superArrayPrototype = {
unique: function(){return [...new Set(this)]},
last: function(){return this.at(-1)}
}
Object.setPrototypeOf(superArrayPrototype,Array.prototype);
let myArray4 = [1,2,3,2,1];
Object.setPrototypeOf(myArray4,superArrayPrototype);
In Chrome console, we see the four results:
The question!
What is the best way to add custom properties/methods to specific instances of built-in objects? And why?
Remember, the question is about creating many objects/arrays with (the same) many functions (that, of course, will be constructed/enhanced all together / in an organized fashion, not one by one as in these simplified examples!)
Do the same answer apply to other built-in objects, like HTML elements? What is the best way to add custom properties/methods to an HTML element? (maybe for another topic?)
Solution 1:[1]
Method 1 and Method 3 do the same thing. You're just defining functions against an instantiated array. If you're after speed simply stick with methods 1 or 3. Turns out classes extended from array and prototypes from arrays (which is the same thing under the hood) are simply very slow when it comes to large volumes of data. Small cases it's probably fine. I can't seem to find much info on this subject myself either. But hopefully this is a good starting point for further testing. It could take 10ish seconds to complete the test so be patient...
//Method 1
let myArray1 = [];
myArray1.unique = () => [...new Set(myArray1)];
myArray1.last = () => myArray1.at(-1);
//Method 2
class superArray extends Array {
constructor(...items) { super(...items) }
unique(){return [...new Set(this)]}
last(){ return this.at(-1)}
}
let myArray2 = new superArray();
//Method 3
let superArrayMethods = {
unique: function(){return [...new Set(this)]},
last: function(){return this.at(-1)}
}
let myArray3 = [];
myArray3 = Object.assign(myArray3, superArrayMethods);
//Method 4
let superArrayPrototype = {
unique: function(){return [...new Set(this)]},
last: function(){return this.at(-1)}
}
Object.setPrototypeOf(superArrayPrototype,Array.prototype);
let myArray4 = [];
Object.setPrototypeOf(myArray4,superArrayPrototype);
//Timers
console.time("myArray1")
for(i=0; i<10000000; i++) { myArray1.push(i); }
console.timeEnd("myArray1")
console.time("myArray2")
for(i=0; i<10000000; i++) { myArray2.push(i); }
console.timeEnd("myArray2")
console.time("myArray3")
for(i=0; i<10000000; i++) { myArray3.push(i); }
console.timeEnd("myArray3")
console.time("myArray4")
for(i=0; i<10000000; i++) { myArray4.push(i); }
console.timeEnd("myArray4")
console.time("unique myArray1")
myArray1.unique();
console.timeEnd("unique myArray1")
console.time("unique myArray2")
myArray2.unique();
console.timeEnd("unique myArray2")
console.time("unique myArray3")
myArray3.unique();
console.timeEnd("unique myArray3")
console.time("unique myArray4")
myArray4.unique();
console.timeEnd("unique myArray4")
Many arrays, fewer items
As requested, here is a similar test, however we are now testing if having many array with fewer items has any bearing on the speed.
The following code seems to demonstrate a similar result; (Open Developer Tools (F12) to see an easier to read table of the results.
Like any test scripts, always run them a few times to validate;
//Method 1
let method1 = () => {
let myArray1 = []
myArray1.unique = () => [...new Set(myArray1)];
myArray1.last = () => myArray1.at(-1);
return myArray1;
}
//Method 2
class superArray extends Array {
constructor(...items) { super(...items) }
unique(){return [...new Set(this)]}
last(){ return this.at(-1)}
}
let method2 = () => {
return new superArray();
}
//Method 3
let superArrayMethods = {
unique: function(){return [...new Set(this)]},
last: function(){return this.at(-1)}
}
let method3 = () => {
let myArray3 = [];
myArray3 = Object.assign(myArray3, superArrayMethods);
return myArray3;
}
//Method 4
let superArrayPrototype = {
unique: function(){return [...new Set(this)]},
last: function(){return this.at(-1)}
}
Object.setPrototypeOf(superArrayPrototype,Array.prototype);
let method4 = () => {
let myArray4 = [];
Object.setPrototypeOf(myArray4,superArrayPrototype);
return myArray4;
}
let results = {};
let arrayBuilder = (method) => {
let preDefinedObjectsArray = Array.from({ length: itemsPerArray }, () => ({ test: "string" }));
results[method.name] = { Creation: 0, Population: 0, CallingFunction:0 }
//Test method array creation speed;
let t0 = performance.now();
for(let i = 0; i < numberOfArrays; i++) {
let x = method();
}
let t1 = performance.now();
results[method.name].Creation = (t1 - t0).toFixed(4);
//Create an array of all arrays to test adding items and calling it's functions;
let tmpArray = Array.from({ length: numberOfArrays }, () => (method()));
//Test array population speed;
t0 = performance.now();
tmpArray.forEach(a => preDefinedObjectsArray.forEach(o => a.push(o)));
t1 = performance.now();
results[method.name].Population = (t1 - t0).toFixed(4);
//Test function calling on array;
t0 = performance.now();
tmpArray.forEach(a => a.unique());
t1 = performance.now();
results[method.name].CallingFunction = (t1 - t0).toFixed(4);
tmpArray = null;
}
const itemsPerArray = 100;
const numberOfArrays = 100000;
console.log(`Running test - Creating ${numberOfArrays} arrays with ${itemsPerArray} items per array per method. Be patient...`);
setTimeout(_ => {
[method1, method2, method3, method4].forEach(m => arrayBuilder(m));
console.table(results);
console.log(results);
console.log('Open Console for clearer view of results (F12)')
},100);
.as-console-wrapper { max-height: 100% !important; }
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |