JavaScript's new Operator (or: How to Read ECMA-262)
Before we dig in, I must briefly describe the state of JavaScript, aka ECMAScript, aka ECMA-262.
Two versions of JavaScript are in wide use today. ECMA-262, 3rd edition (commonly known as ES3) was standardized in 1999 and is supported by pretty much anything that claims to support JavaScript. ECMA-262, 5th edition (commonly known as ES5) is a slightly more modern language that, while common among the latest web browsers, is not ubiquitous. Note that ECMA-262, 5.1 edition, is a bugfix release of the ES5 standard.
The ECMA-262 standards are quite approachable and freely available online.
(This is a tangent but it's cool. You should know the test262 project provides an ES5.1 conformance suite, and it appears that IE10 is the most compliant implementation at this time.)
On to the topic at hand, which is the behavior of JavaScript's new
operator and why you might want a JavaScript implementation of it.
Open the ES5 spec and examine section 11.2.2. You can disregard the fact that there are two versions of the new
operator: the specification distinguishes between new X
and new X.Y(Z)
.
The key point is that new
evaluates its argument expression, which must result in an Object, and calls said object's internal [[Construct]] method. Only Function objects have a [[Construct]] method, and its behavior is precisely defined in section 13.2.2.
I will demonstrate a JavaScript function that implements new
. The first argument is the constructor.
function new_(T) { if (!(T instanceof Function)) { throw new TypeError(T + ' is not a constructor'); } var proto = T.prototype; if (!(proto instanceof Object)) { proto = Object.prototype; } var obj = Object.create(proto); var result = T.apply(obj, [].slice.call(arguments, 1)); return (result instanceof Object) ? result : obj; }
Why is such a function useful?
First, and primarily, new_
can take a variable list of arguments, via new_.apply
. That can't be done with the traditional new operator.
Secondly, new_
can be bound to produce factory functions. Let's say you have a constructor SomeClass
. You can create a SomeClass factory as such:
var SomeClassFactory = new_.bind(undefined, SomeClass); var instance = SomeClassFactory(); (instance instanceof SomeClass); // true
Thirdly, new_
illustrates some non-intuitive behavors of the new
operator. For example, the created object's prototype is set to Object.prototype if the constructor's prototype is not an object. Consider:
function foo() {} foo.prototype = null; var instance = new foo; Object.getPrototypeOf(instance) === Object.prototype; // true
A lesser-known behavior is that constructors can themselves return values. If the value returned is an Object (remember that Functions are Objects too), new
returns it instead of the object passed to the constructor as this
. For example:
function Wooo() { return Wooo; } Wooo === new new new new new new new new Wooo; // true
I don't recommend it, but this technique could be used to write constructors that behave identically whether they are called directly or as a constructor:
function Flexible(arg) { var this_ = Object.create(Flexible.prototype); this_.arg = arg; return this_; } Flexible.prototype.method = function() { return this.arg; } var a = Flexible('success'); var b = new Flexible('success'); 'success' === a.method(); // true 'success' === b.method(); // true
I hope that you've learned something about JavaScript's new
operator. Before sending you on your way, I'd like to point out that Object.create
is an ES5 method and thus unsupported in ES3 implementations. So could we write an implementation of new_
that runs in old browsers by avoiding Object.create
?
Object.create
is effectively a primitive operation on which the new
operator is built. And indeed, we can bypass the need for Object.create
by using some of new
's behavior to create an object with a specified prototype. Here is the ES3 version of new_
:
function new_(T) { if (!(T instanceof Function)) { throw new TypeError(T + ' is not a constructor'); } var proto = T.prototype; if (!(proto instanceof Object)) { proto = Object.prototype; } function dummy() {} dummy.prototype = proto; var obj = new dummy; // Object.getPrototypeOf(obj) === proto var result = T.apply(obj, [].slice.call(arguments, 1)); return (result instanceof Object) ? result : obj; }
function Foo() {console.log(arguments);}
new Foo(1, 2); new Foo(1, 2, 3);
I feel like I'm missing something?
Let's say you have a variable list of arguments 'args' and want to pass its values as arguments to a constructor Foo. There's no way to express that except by writing a JavaScript implementation of operator new.
If you have a normal function, you can easily just call func.apply.
In Python you could write SomeClass(*args)
Ah, that was not clear to me from your article. Probably due to the ambiguity of "variable list of arguments."
I think this is part of the reason why passing a single object to constructors is such a common pattern in JS.
Why try{ (0,â€.charAt)() } catch(e){for(k in e) alert(e[k])} gives gives string expected in ie6?