Five Useful Functions Missing in Javascript

Web & App Developer

JavaScript has been around for several years and its core continues to mature, including new classes and functions to help programmers do their job. However, some basic utility functions are still missing and instead implemented with libraries like jQuery, Prototype and MooTools. Working with these tools is great but in some cases their use is excessive for your needs. This article covers five useful functions that I have always thought belonged in JavaScript.

getElementsByClassName()

JavaScript allows an element to be retrieved using its id with the function getElementById(), but prior of HTML5 there was no native function to get one or more elements using a class name. The new function is called getElementsByClassName(), and it is available in Firefox 3+, Opera 9.5+, Safari 3.1+, and all versions of Google Chrome. Unfortunately, as you might guess, it isn’t available in all of the versions of Internet Explorer, the number one enemy browser for web designers. Only Internet Explorer 9+ supports getElementsByClassName(), so for older versions you need a wrapper function.

The best function I’ve found was written by Robert Nyman. His implementation is released under the MIT license, and it has been recommended by the WHATWG. It uses the native getElementsByClassName() method in browsers that support it, then falls back to the little-known document.evaluate() method, which is supported by older versions of Firefox (since at least 1.5) and Opera (since at least 9.27). If all else fails, the script falls back to recursively traversing the DOM and collecting elements that match the given classnames. You can find the code below and on his repository.

var getElementsByClassName = function (className, tag, elm){
  if (document.getElementsByClassName) {
    getElementsByClassName = function (className, tag, elm) {
      elm = elm || document;
      var elements = elm.getElementsByClassName(className),
      nodeName = (tag)? new RegExp("\b" + tag + "\b", "i") : null,
      returnElements = [],
      current;
      for(var i=0, il=elements.length; i<il; i+=1){
        current = elements[i];
        if(!nodeName || nodeName.test(current.nodeName)) {
          returnElements.push(current);
        }
      }
      return returnElements;
    };
  }
  else if (document.evaluate) {
    getElementsByClassName = function (className, tag, elm) {
      tag = tag || "*";
      elm = elm || document;
      var classes = className.split(" "),
      classesToCheck = "",
      xhtmlNamespace = "http://www.w3.org/1999/xhtml",
      namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
                           returnElements = [], elements, node;
      for(var j=0, jl=classes.length; j<jl; j+=1){
        classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";
      }
      try {
        elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);
      }
      catch (e) {
        elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);
      }
      while ((node = elements.iterateNext())) {
        returnElements.push(node);
      }
      return returnElements;
    };
  }
  else {
    getElementsByClassName = function (className, tag, elm) {
      tag = tag || "*";
      elm = elm || document;
      var classes = className.split(" "),
          classesToCheck = [],
          elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
          current,
          returnElements = [],
          match;
      for(var k=0, kl=classes.length; k<kl; k+=1){
        classesToCheck.push(new RegExp("(^|\s)" + classes[k] + "(\s|$)"));
      }
      for(var l=0, ll=elements.length; l<ll; l+=1){
        current = elements[l];
        match = false;
        for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
          match = classesToCheck[m].test(current.className);
          if (!match) {
            break;
          }
        }
        if (match) {
          returnElements.push(current);
        }
      }
      return returnElements;
    };
  }
  return getElementsByClassName(className, tag, elm);
};

extend()

If you have ever written a plugin, you have almost certainly faced the problem of merging two or more objects. This often happens when you have some default settings and want the user to be able to replace some of the default values. If you were using jQuery, you could use extend(), but since we’re talking about raw JavaScript, the bad news is that there’s no native function. Luckily, you can easily build it yourself. The following example will show you how to create code that works the same way as the jQuery method. I add our extend() method to the Object prototype so that all objects can share the same method.

Object.prototype.extend = function() {
  if (arguments.length === 0)
    return this;

  for (var i = 0; i < arguments.length; i++) {
    for (var property in arguments[i]) {
      if (arguments[i].hasOwnProperty(property))
        this[property] = arguments[i][property];
    }
  }
  return this;
};

This function accepts a variable number of arguments. This is possible thanks to the use of arguments, a local array-like object available inside every function. If you need more information about arguments, I advice you to read arguments: A JavaScript Oddity.

equals()

Object comparisons are a very common operation. While this test can be done using the strict equality operator (===), sometimes you don’t want to test if two variables refer to the same object in memory. Instead, you want to know if two objects have the same properties with the same values. The code below does exactly this. Please note that the following code is not mine; it belongs to a user called crazyx. Again, equals() has been added to the Object.prototype.

Object.prototype.equals = function(x) {
  var p;
  for(p in this) {
    if (typeof(x[p]) == "undefined")
      return false;
  }
  for(p in this) {
    if (this[p]) {
      switch(typeof(this[p])) {
        case "object":
          if (!this[p].equals(x[p]))
            return false;
          break;
        case "function":
          if (typeof(x[p]) == "undefined" ||
             (p != "equals" && this[p].toString() != x[p].toString()))
            return false;
          break;
        default:
          if (this[p] != x[p])
            return false;
      }
    }
    else {
      if (x[p])
        return false;
    }
  }
  for(p in x) {
    if(typeof(this[p])=="undefined")
      return false;
  }
  return true;
}

inArray()

JavaScript doesn’t have a native method to test if a value is in an array. We’ll write a function that, as you might expect, will return true if the value is present and false otherwise. This function simply does an identity comparison of the given value against every element of the array. Just like the previous two examples, inArray() is added to the prototype property of the Array class.

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

This function, due to its simplicity, in many cases doesn’t work as you might expect. Although it works well for basic types like String and Numbers, if you compare objects, it only returns true if the function finds the same object. To better understand how it works, let’s look at the following example.

var array = [1, 2, 3];
console.log(array.inArray(2)); // print true

var obj = {"prop": "value"};
array = [{"prop": 1}, {"prop": "a long string"}, {"prop": "value"}];
console.log(array.inArray(obj)); // print false, not true as you might expect

array = [{"prop": 1}, {"prop": "a long string"}, obj];
console.log(array.inArray(obj)); // print true

The function presented can be enhanced with the help of the equals() function discussed previously. In this way we can get a match if two objects have the same properties and values. Another improvement we can make is to have the function return the position of the element instead of simply true or false. The final version of the function is shown below.

Array.prototype.inArray = function (value) {
  for (var i = 0; i < this.length; i++) {
    if (typeof value === "object") {
      // If both are objects, uses the equals function
      if (typeof this[i] === "object" && value.equals(this[i]))
          return i;
    }
    else if (this[i] === value)
      return i;
  }
  return false;
};

Now, if you run again the examples above, you will get:

1
2
2

toggleClass()

Another method which is often used in jQuery is toggleClass(). It adds or removes a class from an element depending if the class name is already present or not. A simple version of toggleClass() is shown below.

function toggleClass(id, className) {
  var element = document.getElementById(id);
  var classes = element.className.split(/s+/);
  var length = classes.length;

  for(var i = 0; i < length; i++) {
    if (classes[i] === className) {
      classes.splice(i, 1);
      break;
    }
  }
  // The className is not found
  if (length === classes.length)
    classes.push(className);

  element.className = classes.join(" ");
}

This code can also be improved. The for loop in the function does nothing but search the classes array. We can replace the loop with a call to the inArray() function, resulting in the following code.

function toggleClass(id, className) {
  var element = document.getElementById(id);
  var classes = element.className.split(/s+/);
  var length = classes.length;
  var found = classes.inArray(className);
  if (found !== false)
    classes.splice(found, 1);
  // The className is not found
  if (length === classes.length)
    classes.push(className);
  element.className = classes.join(" ");
}

Conclusion

This article highlighted what I think are some of the most important functions missing from JavaScript. Of course, JavaScript misses other things, as we’ll see in the next weeks. But, for now, I’d like to point out the following:

  • Frameworks like jQuery have a lot of useful functions but they add an overhead. So, if you need just a couple of functions, use raw JavaScript and group the methods you need in an extenal file.
  • If you used a function that has been introduced in a new JavaScript version, don’t get rid of it. Wrap it with a conditional statement to test if it’s supported and if not, use your old code as pointed out for getElementsByClassName(). This way you’ll continue to support old browsers.
  • When possible add functions to the prototype of an object as shown for extend(). All of the instances will share the same method and you’ll have better performance.
  • When possible, reuse your code as shown in the second version of toggleClass().

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Federico Panico

    toggleClass is not technically missing but just not supported on older browsers.
    You access it by node.classList.toggle

    • http://www.audero.it/ Aurelio De Rosa

      You’re right but its support is not so wide in my opinion and due to its simplcity I think is almost better to use the raw function that check for compatibility. Infact, all the version of IE doesn’t support it (IE10 will be) and Opera introduced it just few versions ago as you can read here: https://developer.mozilla.org/en-US/docs/DOM/element.classList

  • lord.xeon

    inArray looks to me alot like array.some()

    • http://www.audero.it/ Aurelio De Rosa

      As always in IT there’re several ways to achieve the same goal. You can also use that method but I think it really overcomplicate a very easy and straightforward task, doesn’t it? Moreover, don’t you think that have a function called inArray() will help people with background of other languages like PHP and Java to find it?

  • Eric

    Very good article, really interesting. I think that Javascript should have all these functions and this lack is not so good since you’re obliged to use a framework. I hope in a future introduction. Anyway, well done, compliments!

    • http://www.audero.it/ Aurelio De Rosa

      Thank you for the compliments. I’m very happy to see that such a simple article raised up a so lively discussion.

  • Onebody

    “JavaScript doesn’t have a native method to test if a value is in an array. “: have you pros heard ever heard of Array.indexOf(); ?

    Instead of iterating over the whole Array, you could check for elements which are part of an array like that…:

    var array = [1, 2, 3];
    console.log(array.indexOf(2) > -1); // print true

    As a convenient way for lazy guys, your prototype extension could be reduced from 12 lines of code to just three…:

    Array.prototype.inArray = function (value) {
    return (this.indexOf(value) > -1);
    };

    Doesn’t support objects, though.

    • http://www.audero.it/ Aurelio De Rosa

      I don’t really understand what’s your point here. Many languages (i.e. PHP) have two distinct functions, one to retrieve the position of an element and one to get a boolean and I haven’t heard someone saying “inArray() is useless because it’s a duplicate”. So, although it’s true that you can retrieve the index using indexOf(), as I said, JavaScript hasn’t an inArray() function. Moreover, starting with that first version that uses the for loop, it has been more simple to explain the “advanced” version shown later which, unlike indexOf(), also allows to test for equal objects. In conclusion, for what I explained in the article, there’s no advantage in using indexOf().

  • http://www.brothercake.com/ James Edwards

    indexOf array is not well supported; for example, IE doesn’t support it until IE9 (because it’s a fairly recent addition to the spec). I would still prefer a custom inArray function to return an index though, returning -1 if the value isn’t there, so it serves both purposes in a single function.

    For your getElementsByClassName function I would actually recommend ditching both the native and evaluate forks, and do it only using DOM traversal, because compiling the wanted class inside a RegExp as you do, means you can pass it simple strings that evaluate as partial expressions. For example, you could go getElementsByClassName(“(foo|bar)”) and that would evaluate as elements with EITHER one class or the other, which is much more powerful and useful than simple matches.

  • http://www.audero.it/ Aurelio De Rosa

    Hi James. Good point. However, as I said in the article, the getElementsByClassName() function is not mine. It has been created by Robert Nyman so I hadn’t change it.

  • http://blog.rojakcoder.com/ Chee How Chua

    I recently have had the need to compare two objects and your article came just in time.
    I have one question: with the equals() method, on line 20, wouldn’t it be more accurate if the equality checking goes with strict equality (i.e. ‘===’ instead of ‘==’)?

    • http://www.audero.it/ Aurelio De Rosa

      Hi. At line 20, I see if (this[p] != x[p]), not an equal operator. However, as I said in the article, that function isn’t mine so I haven’t changed the code for honesty against the author but of course I tested it.

      • http://blog.rojakcoder.com/ Chee How Chua

        Ah, of course. I meant !== vs. !=

  • Nic da Costa

    Hey Aurelio

    Thanks for the article, i just feel that the title and introduction could be misleading, as one would think that this is about 5 functions missing from JavaScript, not 5 functions with support missing. Sure, while there are some highlighted above that do not exist as far as my current knowledge goes,such as extend and a possible equals (on an object’s property level), but the other do exist in the language.

    Some examples which have already been brought up, [].indexOf works covers the basic requirement and is native, also getElementsByClassName, there is another option which is even supported in IE8… document.querySelector() and document.querySelectorAll(). both these methods allow you to pass in a basic CSS class selector. I have tested this on this very site. As well as toggleClass (which was mentioned in the previous comments).

    I guess my point is that it is not that these functions don’t exist, but they are maybe not supported across all the browsers (specifically the legacy versions). All in all, a nice roundup of some functions that are used by majority of developers on a regular basis!

    • http://www.audero.it/ Aurelio De Rosa

      Hi Nic and thank you for the comment. As I already said the function I showed isn’t indexOf(). I create the classic inArray() function (many languages, like PHP, have both of them) that returns a boolean. Moreover, the first version I wrote was just to have the chance to easily carry the reader to the final version that also allows to test for objects. indexOf() doesn’t give this possibility, so they are definitively two different functions. I don’t really understand why the discussion about indexOf() raised up since it’s not explained here.

      About the functions getElementsByClassName() and toggleClass(), it’s true that several new browsers support them but they were not created from the beginning in JavaScript which is something that I wouldn’t expect since they have been used for a very long time. Don’t you think it’s a little bit strange to see those functions only in the latest versions of JS? Last but not least, considering toggleClass() I wrote, don’t you thing that due to its simplcity is almost better to use the raw function that check for compatibility?

      • Nic da Costa

        Hey Aurelio, thanks for the reply! :)

        I think with the case of the indexOf() example, is that it does cater for basic objects, if the object that is being referenced is the same object in the array, it does return true, eg:

        var obj = {}, arr = [1,2,3,obj,5];
        arr.indexOf( obj );
        // returns 3

        And witht he getElementsByClassName, i know the support is not great, but there are other better native options that achieve the same result, as i mentioned in my comment, document.querySelector() and document.querySelectorAll() achieves this very same functionality and it is supported in IE8 ( http://caniuse.com/queryselector ). So, unless you plan on supporting IE7 and below, then you would need to supply a fallback. And yes, the toggleClass() option would require a fallback. I would probably suggest using the native where possible as the perf benefits are better than using a custom built function. What i would even do, is go so far as to do the check in the custom function and if supported use native else continue with the function. But the easiest and probably best way (which should be the default method in my opinion) would be to check on initial load and just polyfill it, thus in your code it won’t make any difference.

        And yes, it is “sad” to a degree that some are only now available in the latest spec but at the same time, these methods only recently became popular/required with the recent popularity of DOM manipulation frameworks such as jQuery. It was because of these frameworks introducing these sets of functionality that developers have learned to rely on such things.

  • Duncan Kenzie

    Interesting article on extending native objects, but I really don’t know why you would bother with any of this when jQuery does it all already. The production (minified, gzipped) version of jQuery is 32K, so it’s already lightweight. Also, if you use a CDN to reference the jQuery script you most likley reduce any performance impact from the payload even further.

    • http://www.audero.it/ Aurelio De Rosa

      Hi Dancan. Of course jQuery is a great library and everyone would like to use it. However, imagine that for a little script you need to use just a couple of those functions; isn’t using jQuery an overhead?

  • Paul

    Wow, it seems like there’s a lot of nitpicking going on here. I think you did a really good article here and in my opinion FWIW I think you did a great job of defending your reasoning for imputing the various functions. Especially with the inArray function. document.selectorQuery() is slow. Before a judgement is made whether it’s better one needs to bench test it against the getElementsByClassname(). I think you should do that bench test Aurelio.

    I have always been a minimalist using always only what is necessary especially since not everyone has computer specs like a macbook pro. If I only needed one or two jQuery functions I would definitely do what you are suggesting. I am a middle class American and I use low spec computers and I personally know that there are plenty of people doing the same because a lot of the people I know are. Even business’ and colleges use low spec computers in their labs and networks because they can’t afford to update multiple computers every year. I think it’s ignorant to think that you don’t need to pay attention to minimalism.

    Obviously articles like these are for beginner to intermediate javascripters like me and it helps me a lot. The more advanced users who are commenting seem to be just trying to show off that they know something, but their arguments are baseless. I think that Aurelio’s defence substantiated that point every time. That’s usually the way it is in the tech world. When anyone tries to present something, then everyone and his brother takes a pot shot at it and tries to pic it apart. I have to say that some of the comments made some good points and most if not all were civilised. I just read your cookies article and I will continue to read your articles. I appreciate them.

    One suggestion though. There is always a disconnect between the advanced and the beginner user. Somehow the advanced instructor thinks that they can spit out a bunch of code and the beginner should be able to magically understand what is going on behind the scenes without an explanation. The beginner comes to you because he wants you to bridge that gap. May I suggest that since it seems that you are doing articles that are geared for beginners. Don’t just say what a statement does in general terms in the comments. Write a little the description describing specifically what will happen in technical terms so beginners don’t have to look at some general description and have to imagine what is actually happening behind the scenes. It will take your instructional skills to the next level and you will attract a lot of beginner users.

    • http://www.audero.it/ Aurelio De Rosa

      Thank you for your comment. I’m glad the article has been informative to you.

  • Jared Williams

    I have created a similar function to the inArray() function you have here that enables you to search for a keys of objects or arrays to see if it exists. It can be useful to do the same thing as inArray() but for objects which is why I ended up making it. http://anti-code.com/blog/keyexists-javascript-function/

    I also made a micro js library called Qlass.js that can addClass removeClass and hasClass on Elements and NodeLists. I intended on writing a toggleClass method to add to the library, but decided not to. It would be a nice addition to JavaScript though definitely.