My project was progressing quickly, cross-browser testing having succeeded in Chrome, Firefox, Safari, and IE8. Then I ran it on IE7 and found this mysterious error:
“constructor is null or not an object.”
As usual with IE, the line number and column were meaningless, so I looked for variables called “constructor.” Curiously, there weren’t any. Perhaps it was abbreviating an expression like “x.constructor.foo”, and saying that “x.constructor” is undefined. So I looked for such expressions, and found many occurrences of expressions like this:
return new x.constructor(…);
Suspicious that IE was pointing me in the wrong direction, I created a test case to reproduce the error. Sure enough, the message is different. IE gives the significantly more laconic “object expected” error if you call an undefined function.
This left only one possibility, that x was null or undefined and the error message was wrong. Sure enough, this test produces the misleading message “constructor is null or not an object”:
var x = null;
alert(x.constructor);
With that bit of confusion out of the way, I started searching through the code to find places where I could possibly be asking for the constructor of a null or undefined value. I figured it had to be a fairly weird case, since the program worked just fine on other browsers and the failing module had no DOM references.
After commenting out various constructor use sites, I finally found the problem. The expression looked like this:
if (xs[xs.length - 1].constructor === Function) {...}
I commented out the line and ran my unit tests again. This time there were no errors, but a lot of failures. Every array I made an assertion about appeared to be empty. For example:
var xs = new caterwaul.seq.finite([1, 2, 3, 4, 5]);
assert_equal(xs.length, 5); // Fails: xs.length is 0
(This happens only on IE7, remember. Other browsers work just fine.)
This test isolated the problem perfectly:
var f = function () {};
f.prototype = [];
f.prototype.constructor = f;
var x = new f();
x.length = 10;
alert(x.length); // alerts 0 on IE7
The reason for this weirdness is that IE7 uses a getter/setter property for “length” (which violates the ECMA standards); other browsers treat length normally. Getters and setters create a big problem for prototypical inheritance patterns like the one I was trying to establish: you can’t override something with a setter.
It gets worse, too. I thought of a hack to work around the problem. If IE7 is the only failure case, then some special logic to use the native push() method to update the length should do the trick. So something like this:
var expected_length = 10;
while (xs.length < expected_length) xs.push(xs[xs.length - 1]);
In any reasonable system this would update the length, taking O(n) time but otherwise not causing problems. However, IE7′s implementation of push() detects that xs is an object, not a native array, and silently does nothing. So in fact the above code loops forever if xs inherits from, but isn’t, a real array.
As a last attempt, I tried externally invoking Array.prototype.push on xs and xs[xs.length - 1]. This had the same effect as calling push() directly.
The solution I’m going with is to create a size() method and have the logic failover to using .length if that’s unavailable. If anyone knows of a better solution (preserving Array in the inheritance chain), I’d love to hear it.