Quick Tip: How to Extend Built-in Objects in JavaScript
videos

Quick Tip: How to Extend Built-in Objects in JavaScript

Share

Constructor functions, like Array, offer a wide range of methods and properties that you can make use of. But have ever wished that one of these objects offered some method that isn’t built-in? Is there a way to do so yourself? Absolutely! Let’s see how.

Reversing a String

This little snippet takes advantage of the Array object’s “reverse” method, and applies its functionality to a given string. In effect, something like “hello” will be turned into “olleh,” and can be accessed by using “myString.reverse()”.

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

var myString = 'hello';
console.log(myString.reverse());

Bonus Question

Now that we have a basic of understanding of augmenting objects, can you figure out a way to write a custom “contains” method for the String object? For example, the jQuery library allows us to write things like:

$("div:contains('John')").css('fontWeight', 'bold');

The snippet above will search through all of the divs on the page, and then filter that wrapped set down to only those that contain the string “John.” How could we extend the String object, with raw JavaScript, to allow for this? Leave your solution in the comments, and we’ll discuss!

Related Posts

Add Comment

Discussion 44 Comments

  1. Michael says:

    Perhaps the better question is why you would want to extend the string object to make element selections…

    That seems far out of the bounds of rational OOP.

    Traditionally you would make a custom type that manages a particular type of selection and distill that to a prototype method.

    Grooming aspiring JS developers to extend primitive types for DOM scraping doesn’t seem well-advised in my book.

  2. Jeffrey Way says:

    So if you’re stumped on the Bonus question, here’s what I came up with.

    <body>
    	<p>Hello World</p>			
    
    <script type="text/javascript">
    var para = document.getElementsByTagName("p")[0].innerHTML;
    
    String.prototype.contains = function(word) {
    	return !!( new RegExp(word, "ig").test(this) );
    };
    
    if ( para.contains("hello") ) {
    	alert("Matched!");
    }
    </script>
    </body>
    

    Got something better? :)

    • Teylor Feliz says:

      What about?

      String.prototype.contains = function(word){
      return !!(this.indexOf(word) + 1);
      }

      String.prototype.contains = function(word){
      return !(this.indexOf(word)=== -1);
      }

      String.prototype.contains = function(word){
      var rx = new RegExp((^|\\s) + word + (\\s|$));
      return rx.test(this);
      }

  3. Richard says:

    I am still learning JS here (btw, loving the JS from null series, keep it up!) but I’m curious as to why you need to call prototype again on the Array within the string prototype call?

    It seems to me like the prototype in array isn’t necessary because it works as array.reverse… but is there something that’s doing that is incorrect and I should therefore avoid?

  4. String.prototype.contains=function(s){return this.indexOf(s) > -1;};

    :)

    Also, your reverse example could be simplified to:

    this.split(”).reverse().join(”)

    It might be worth mentioning, just for readers’ sake (just encase they all run off and start extending everything!), that you should never extend “Object.prototype”… It’s generally considered a bad practice as doing so will, since everything is an object, extend EVERYTHING… which can have some fishy results.

    Also, the addition to native methods is a controversial topic in itself. The Prototype and MooTools libraries use it heavily and they have come under heavy fire for it in the past. I think it’s mostly down to preference, although obviously you have to consider future compatibility (as you mentioned) etc..

    Also, there can be some cross-browser issues when dealing with element prototypes, specifically in IE.

    • Was just going to post the comment.

      String.prototype.contains = function(needle) {
      if(haystack.indexOf(needle) > -1){
      return true;
      }else{
      return false;
      }
      }

      Congrats on the solution :)

    • Jeffrey Way says:

      Nice one James. :)

      Absolutely, in reference to extending the native methods argument. I spoke about that a bit at the end of the screencast…but it probably requires more discussion.

    • Jeffrey Way says:

      Also – should be noted that, though I’m 100% that the indexOf method will be faster, it doesn’t allow for case-insensitive matching…at least not to my knowledge. :)

      But yeah – very smart.

    • Beat me to it. That’s exactly what came to mind when I read the bonus question.

    • Teylor Feliz says:

      100% agree with James. For example, people should check the article written by Nicholas C. Zakas
      named a href=http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/ rel=nofollowMaintainable JavaScript: Don’t modify objects you don’t own/a, to see why they should not extend build in objects.

      If you are going to extends you have to make sure you are not overwriting existing methods. I good way to do it is:

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

      or

      if(typeof String.prototype.reverse !== function){
      code here
      }

      I would recommend their use only in personal project like a portfolio websites where you are free to do all you want and nobody else can overwrite your code.

  5. MikeC says:

    String.prototype.contains = function (what) {
    return !(this.indexOf(what) == -1);
    }

  6. Can someone explain how this works.

    function (a, b) {
    return a.compareDocumentPosition(b) & 16;
    }

    • “compareDocumentPosition”, as the name would suggest, allows you to compare the document position of two DOM nodes. It returns a bitmask, i.e. the numbers 1, 2, 4, 8 or 16 dependent on the result.

      “&” is the bitwise AND operator, and will return a 1 in every bit position that is 1 in both its operands. It’s kinda complex, and I generally stay away from bitwise ops in JavaScript because (as Crockford said), they’re slow! (and hard to understand)

      Your function will end up returning the number 16 if “b” is contained by “a” (i.e. “a” is an ancestor of “b”). I’m presuming you got that from jQuery’s source. It’s actually a bug, since it should return a boolean. I believe it’s been fixed in the latest nightlies.

      Read more: https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition

  7. Brian Egan says:

    http://jsfiddle.net/NmDTG/

    Returns True or false whether it finds the string or not, but James’ version is a little simpler.

    Cheers!

    • Jeffrey Way says:

      Yeah – I’m thinking that would be the next best solution, if you need it to be case-insensitive. That way, you don’t have to muddy up the script with the two “toLowerCase” calls.

  8. Jeffrey Way says:

    I need to look up how jQuery specifically handles :contains.

    Have you guys seen James’ handy dandy source viewer?

    http://james.padolsey.com/jquery/#v=1.4&fn=contains

  9. Micko says:

    I’ve been wondering this lately but what !! actually does? Jeffrey used it in his example.

  10. Here’s what I came up with:

    String.prototype.contains = function (s, c) {

    return new RegExp(s, (c) ? ‘i’ : ”).test(this);

    }

    Obviously, the second parameter is for case sensitivity.

  11. Derrick Nelson says:

    It seems pointless to extend the String class for a ‘contains’ method, given that it already has the ability to do this via indexOf(). But, I suppose you could wrap it in a prototype if you find it unbearably ugly or something…

    String.prototype.contains = function ( substr ) { return this.indexOf ( substr ) !== -1; }

    • Jeff says:

      I generally agree – even if IndexOf isn’t case insensitive. This was mostly to demonstrate how you COULD do it if you wanted. Plu, it makes for good discussion. ;)

    • Code reuse, clarity, and easier to type. If I were going to perform this type of operation several times through an app, I’d wrap the functionality as a contains() method or something similar.

    • Leigh Kaszick says:

      The jQuery contains method does not return a simple true or false value for each of the nodes it finds containing the specified string (whereas simply running an indexOf type method would), it returns an object containing a reference to all the DOM nodes that contain the string value and therefore using the each method could all be manipulated by other methods or properties.

  12. Jack Franklin says:

    I don’t get why you need to use Array.prototype within the function itself…read the above comments but none the wiser? :S

  13. Ashit Vora says:

    /*
    Replaces first letter of the word with Uppercase and rest all with Lowercase.
    usage: “heLLo woRLD”.capitalize();
    */
    String.prototype.capitalize = function() {
    return this.replace(/\w+/g, function(a) {
    return a.charAt(0).toUpperCase() + a.substr(1).toLowerCase();
    });
    };

    /*
    Replaces multiple white spaces and tabs with single white space.
    usage: “Hello World. “.squeeze();
    */

    String.prototype.squeeze = function() {
    return this.replace(/(\t|\s+)/gm, ” “);
    };

  14. Timothy says:

    It’s important to note that you do not have to reference the Array prototype as was done above. Instead, you can just do:

    String.prototype.reverse = function() {

    this.split(”)
    .reverse()
    .join(”);

    };

    Although both cases are correct.

    And there is one case in which you reference Array.prototype that is extremely useful. This is when you want to deal with a function’s arguments as a native array, which it is not by default. If you tried to work with the array object in a forEach or another Array method it would likely cause problems.

    So, to turn arguments into an array, you just need to do this:

    var func = function() {

    var args = Array
    .prototype
    .slice
    .call(arguments);

    };

    I hope the spacing in my comment sticks :/

  15. Jelmer says:

    Hi Jeff, I have a short question, how do you do the ‘beginhtml’ thing every time in these screeencasts? Is it something like a macro?

  16. Matt says:

    String.prototype.contains = function(s, c) {
    return (c ? ((this.indexOf(s) >= 0) ? true : false) : (this.toLowerCase().indexOf(s.toLowerCase()) >= 0) ? true : false);
    }

    Obfuscated, I know, but this has support for using indexOf (which I believe is faster than regexp), and support case and non-case sensitive checks.

    • Matt says:

      Oh, and
      /*
      Usage, myString.contains(String, Boolean). The first parameter is an argument to be checked against, while the boolean indicates whether to use case sensitive search.
      */

  17. Eyal mrejen says:

    Great screen cast.
    But I want to go back to the last remark Jeff has made. He tells us to think about the future, and what it tomorrow, the new javascript engine running in your browser will give this method on the string.
    The inline implementation of the browser will always be preferable to any javascript implementation we can do, so what you really want to do it check if this method exists already, and only if not, THEN add it.
    You do this like so:
    if(!String.prototype.reverse){
    String.prototype.reverse = function() {
    return Array.prototype.reverse.apply(this.split(”)).join(”);
    };
    }

    This way, if the browser has an implementation then you will use it, and if not, your script will still function since you implement it yourself

  18. I would be curious to see the load time of an all JavaScript based website.

  19. good article
    but iam so sorry , iam not good in js
    thanks alot

Add a Comment