Augmenting built-in objects

The objects created by the built-in constructor functions, such as Array, String, and even Object and Function, can be augmented (or enhanced) through the use of prototypes. This means that you can, for example, add new methods to the Array prototype, and in this way you can make them available to all arrays. Let's see how to do this.

In PHP, there is a function called in_array(), which tells you whether a value exists in an array. In JavaScript, there is no inArray() method, although, in ES5, there's indexOf(), which you can use for the same purpose. So, let's implement it and add it to Array.prototype, as follows:

    Array.prototype.inArray = function (needle) { 
      for (var i = 0, len = this.length; i < len; i++) { 
        if (this[i] === needle) { 
          return true; 
        } 
      } 
      return false; 
    }; 

Now, all arrays have access to the new method. Let's test the following code:

    > var colors = ['red', 'green', 'blue']; 
    > colors.inArray('red'), 
    true 
    > colors.inArray('yellow'), 
    false 

That was nice and easy! Let's do it again. Imagine your application often needs to spell words backward, and you feel there should be a built-in reverse() method for string objects. After all, arrays have reverse(). You can easily add a reverse() method to the String prototype by borrowing Array.prototype.reverse() (there was a similar exercise at the end of Chapter 4, Objects):

    String.prototype.reverse = function () { 
      return Array.prototype.reverse. 
               apply(this.split('')).join(''), 
    }; 

This code uses the split() method to create an array from a string, then calls the reverse() method on this array, which produces a reversed array. The resulting array is then turned back into a string using the join() method. Let's test the new method:

    > "bumblebee".reverse(); 
      "eebelbmub"

Augmenting built-in objects - discussion

Augmenting built-in objects through the prototype is a powerful technique, and you can use it to shape JavaScript in any way you like. Because of its power, though, you should always thoroughly consider your options before using this approach.

The reason is that once you know JavaScript, you're expecting it to work the same way, no matter which third-party library or widget you're using. Modifying core objects can confuse the users and maintainers of your code and create unexpected errors.

JavaScript evolves and browser's vendors continuously support more features. What you consider a missing method today and decide to add to a core prototype could be a built-in method tomorrow. In this case, your method is no longer needed. Additionally, what if you have already written a lot of code that uses the method and your method is slightly different from the new built-in implementation?

The most common and acceptable use case to augment built-in prototypes is to add support for new features (ones that are already standardized by the ECMAScript committee and implemented in new browsers) to old browsers. One example will be adding an ES5 method to old versions of IE. These extensions are known as shims or polyfills.

When augmenting prototypes, you will first check if the method exists before implementing it yourself. This way, you can use the native implementation in the browser if one exists. For example, let's add the trim() method for strings, which is a method that exists in ES5 but is missing in older browsers:

    if (typeof String.prototype.trim !== 'function') { 
      String.prototype.trim = function () { 
        return this.replace(/^s+|s+$/g,''), 
      }; 
    } 
    > " hello ".trim(); 
    "hello" 

Tip

Best practice

If you decide to augment a built-in object, or its prototype with a new property, do check for the existence of the new property first.

Prototype gotchas

The following are the two important behaviors to consider when dealing with prototypes:

  • The prototype chain is live, except for when you completely replace the prototype object
  • The prototype.constructor method is not reliable

Let's create a simple constructor function and two objects:

    > function Dog() { 
        this.tail = true; 
      } 
    > var benji = new Dog(); 
    > var rusty = new Dog(); 

Even after you've created the benji and rusty objects, you can still add properties to the prototype of Dog() and the existing objects will have access to the new properties. Let's throw in the say() method:

    > Dog.prototype.say = function () { 
        return 'Woof!'; 
      }; 

Both objects have access to the new method:

    > benji.say(); 
    "Woof!" 
     rusty.say(); 
    "Woof!" 

Up to this point, if you consult your objects, asking which constructor function was used to create them, they'll report it correctly:

    > benji.constructor === Dog; 
    true 
    > rusty.constructor === Dog; 
    true 

Now, let's completely overwrite the prototype object with a brand new object:

    > Dog.prototype = { 
        paws: 4, 
        hair: true 
      }; 

It turns out that the old objects do not get access to the new prototype's properties; they still keep the secret link pointing to the old prototype object, as follows:

    > typeof benji.paws; 
    "undefined" 
    > benji.say(); 
    "Woof!" 
    > typeof benji.__proto__.say; 
    "function" 
    > typeof benji.__proto__.paws; 
    "undefined" 

Any new objects that you will create from now on will use the updated prototype, which is as follows:

    > var lucy = new Dog(); 
    > lucy.say(); 
    TypeError: lucy.say is not a function 
    > lucy.paws; 
    4 

The secret __proto__ link points to the new prototype object, as shown in the following lines of code:

    > typeof lucy.__proto__.say; 
    "undefined" 
    > typeof lucy.__proto__.paws; 
    "number" 

Now the constructor property of the new object no longer reports correctly. You will expect it to point to Dog(), but instead it points to Object(), as you can see in the following example:

    > lucy.constructor; 
    function Object() { [native code] } 
    > benji.constructor; 
    function Dog() { 
      this.tail = true; 
    } 

You can easily prevent this confusion by resetting the constructor property after you overwrite the prototype completely, as follows:

    > function Dog() {} 
    > Dog.prototype = {}; 
    > new Dog().constructor === Dog; 
    false 
    > Dog.prototype.constructor = Dog; 
    > new Dog().constructor === Dog; 
    true 

Tip

Best practice

When you overwrite the prototype, remember to reset the constructor property.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.105.137