JavaScript Prototyping: Extending Built-in Objects with Custom Functions



This content originally appeared on DEV Community and was authored by Alex Aslam

JavaScript’s prototype system is one of its most powerful (and sometimes misunderstood) features. It allows you to add new methods and properties to existing objects, including native types like Arrays, Numbers, and Objects. In this guide, we’ll explore how prototyping works and demonstrate practical examples of extending built-in objects.

Understanding JavaScript Prototypes

Every JavaScript object has a hidden [[Prototype]] property that links to another object. When you try to access a property that doesn’t exist on an object, JavaScript looks up the prototype chain until it finds the property or reaches null.

The Prototype Chain

const arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

Extending Built-in Objects

While extending native prototypes is controversial (it can cause conflicts in large codebases), it can be useful for utility functions you use frequently.

1. Adding a Method to Arrays

Let’s create a sum() method for arrays:

Array.prototype.sum = function() {
  return this.reduce((total, num) => total + num, 0);
};

const numbers = [1, 2, 3, 4];
console.log(numbers.sum()); // 10

2. Adding a Method to Numbers/Integers

We can add a square() method to all numbers:

Number.prototype.square = function() {
  return this * this;
};

const num = 5;
console.log(num.square()); // 25

3. Adding a Method to Objects

Let’s add a deepClone() method to all objects:

Object.prototype.deepClone = function() {
  return JSON.parse(JSON.stringify(this));
};

const original = { a: 1, b: { c: 2 } };
const clone = original.deepClone();
clone.b.c = 3;
console.log(original.b.c); // 2 (unchanged)

Best Practices for Prototyping

  1. Check for Existing Methods First
   if (!Array.prototype.sum) {
     Array.prototype.sum = function() { /* ... */ };
   }
  1. Use Object.defineProperty for Non-Enumerable Methods
   Object.defineProperty(Array.prototype, 'sum', {
     value: function() { /* ... */ },
     enumerable: false // Won't show up in for...in loops
   });
  1. Consider Alternatives For shared code, consider utility functions instead:
   function sumArray(arr) {
     return arr.reduce((t, n) => t + n, 0);
   }

Advanced Example: Chaining Custom Methods

Array.prototype.square = function() {
  return this.map(n => n * n);
};

Array.prototype.average = function() {
  return this.sum() / this.length;
};

const results = [1, 2, 3].square().average();
console.log(results); // 4.666... (average of [1, 4, 9])

When Not to Modify Prototypes

  1. In large team projects where naming conflicts might occur
  2. When working with third-party libraries that might expect default behavior
  3. For properties/methods that might become native JavaScript features in the future

Conclusion

JavaScript’s prototype system offers powerful capabilities for extending built-in objects. While you should use this feature judiciously, understanding prototypes is crucial for advanced JavaScript development. Whether you choose to extend native prototypes or create utility functions, the prototype chain remains fundamental to how JavaScript works.

Feel Free To Ask Questions, Happy coding! 🚀


This content originally appeared on DEV Community and was authored by Alex Aslam