RSS
 

A safer Object.keys compatibility implementation

14 Oct

I was working with Object.keys() recently to sort a list of styles returned by window.getComputedStyle() and element.currentStyle. For those of you who don’t know, Object.keys() is a new addition to the language, defined in ECMAScript 5th Edition. It is supported by recent versions of Firefox, Google Chrome, Safari and it’s also included in the Internet Explorer 9 Beta. For older browsers, we can implement our own version by extending Object.

The first thing I did was to scour the web for implementations that would work in non-supporting browsers such as Opera and Internet Explorers 7 and 8. John Resig, the father of jQuery, has an example implementation in his post about ECMAScript 5 Objects and Properties. MDC has a very similar looking version on its documentation page for Object.keys(). It looks like this:

Object.keys = Object.keys || function(o) {  
    var result = [];  
    for(var name in o) {  
        if (o.hasOwnProperty(name))  
          result.push(name);  
    }  
    return result;  
};

PrototypeJS also has an implementation that looks like this:

function keys(object) {
    if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
    var results = [];
    for (var property in object) {
      if (object.hasOwnProperty(property)) {
        results.push(property);
      }
    }
    return results;
  }

Since I wasn’t using PrototypeJS, I decided to go with the MDC example implementation, adding a type check to the top of the function to make it more in line with the ECMAScript 5 specification. Done! …or so I thought. Turns out, my code was throwing an error in Internet Explorers 6 through 8. element.currentStyle has no such method “hasOwnProperty()”. While I did already know that, it really didn’t occur to me when I was implementing the code. In fact, all three implementations ( PrototypeJS’, MDC and Mr Resig’s) will throw this error on DOM objects in those aforementioned versions of Internet Explorer because DOM objects don’t inherit from and aren’t instances of Object (they’re actually scriptable ActiveXObjects). Internet Explorer 9 differs here, but that doesn’t matter because IE 9 already has the method we’re trying to implement.

Fortunately for us, we can borrow hasOwnProperty() from Object.prototype. Update: on a Prototype mailing list thread I started, kangax, a guy whose blog I’ve landed on quite a lot lately, mentioned that this doesn’t deal with IE’s DontEnum bug and also suggested further optimisations based on code he’d written in the past. I took his advice and improved the code further, although I chose succinctness over one or two micro-optimisations:

Object.keys = Object.keys || (function () {
    var hasOwnProperty = Object.prototype.hasOwnProperty,
        hasDontEnumBug = !{toString:null}.propertyIsEnumerable("toString"),
        DontEnums = [
            'toString',
            'toLocaleString',
            'valueOf',
            'hasOwnProperty',
            'isPrototypeOf',
            'propertyIsEnumerable',
            'constructor'
        ],
        DontEnumsLength = DontEnums.length;
 
    return function (o) {
        if (typeof o != "object" && typeof o != "function" || o === null)
            throw new TypeError("Object.keys called on a non-object");
    
        var result = [];
        for (var name in o) {
            if (hasOwnProperty.call(o, name))
                result.push(name);
        }
    
        if (hasDontEnumBug) {
            for (var i = 0; i < DontEnumsLength; i++) {
                if (hasOwnProperty.call(o, DontEnums[i]))
                    result.push(DontEnums[i]);
            }   
        }
    
        return result;
    };
})();

Hurray, it works! I've also posted the solution as an answer to a question on Stack Overflow - How to list the properties of an object.

 
 

Tags: , , , ,

Leave a Reply

 

 
  1. Nathan L Smith

    October 21st, 2010 at 1:14 pm

    This is almost identical to the implementation in Kris Kowal’s ES5 Shim (http://github.com/kriskowal/es5-shim/blob/master/es5-shim.js)

    It also sidesteps the obscure case in which the object your iterating over has it’s own “hasOwnProperty” property.

     
    • Andy

      October 21st, 2010 at 3:55 pm

      @Nathan: thanks for the link. Yes, they’re identical except for the error thrown when the argument passed in isn’t an object.

      The specification defines that a TypeError should be thrown:

      1. If the Type(O) is not Object, throw a TypeError exception.

      In fact, reading that reminds me that the internal type of functions is Object, and my code needs to check for that accordingly!

       
  2. Cross-context isArray and Internet Explorer « What the Head Said

    October 26th, 2010 at 11:59 pm

    [...] last year, kangax (I mentioned him in the Object.keys post) came to the rescue with a robust solution that worked across frames: 123function [...]

     
  3. Asen Bozhilov

    November 23rd, 2010 at 12:32 pm

    The detection of DontEnum bug is not a reliable. If someone augments Object.prototype your checking would be ever `false`.

    I’d prefer to use the follow checking:

    var DONT_ENUM_BUG = !{‘toString’ : true}.propertyIsEnumerable(‘toString’);

     
    • Andy

      November 23rd, 2010 at 2:19 pm

      Asen: that’s not correct — the local property declared in the object literal overrides the property inherited through the prototype chain.

      While your method is a little more concise and doesn’t require the loop, the end result is much the same. I do agree that it’s slightly better for those reasons, and I’ve updated the code to reflect that. The propertyIsEnumerable method just didn’t occur to me at the time of writing.

       
  4. Yaffle

    April 23rd, 2011 at 5:49 pm

    a little shorter version of Object.keys:

    https://gist.github.com/938823

     
    • Andy

      April 25th, 2011 at 4:04 pm

      Shorter; yes, a little. Slower; probably quite a bit due to the regular expression and variable initialization going on inside the function.

      When I wrote the implementation in this post, I tried to keep performance in mind (since I was recommending it as a patch for Prototype). I did avoid one or two micro-optimisations, but I would definitely choose array looping over string searching.

       
  5. David Lefkon

    May 17th, 2012 at 8:54 pm

    Thanks! .. works great in IE8

     
  6. bkira

    October 9th, 2012 at 8:26 pm

    Wonderful!!…hoooo…it works…Thanks a lot.

     
  7. Sung Am YANG

    March 29th, 2013 at 2:33 pm

    Thanks Andy. It’s really useful!

     
  8. Blueflamey

    July 29th, 2013 at 7:59 am

    Thanks. Spared me a lot of time.