If the JavaScript new
operator was a function, how would it be defined?
function _new(fn) {
// What goes here?
}
If the JavaScript new
operator was a function, how would it be defined?
function _new(fn) {
// What goes here?
}
There are a few limitations owing to the capabilities of user code: native constructors such as new Date
and new RegExp
and bound function cannot be shimmed properly using this function.
If you're looking for the relevant specification sections, they are §11.2.2 and §13.2.2.
Anyway, here goes:
// we're assuming that correct arguments are given
function _new(F, args) {
// we could have host objects, make sure edge case not left out
// (typeof could be other than object or function in this case - see bottom)
function Type(arg) {
if (arg === undefined) return 'Undefined';
if (arg === null) return 'Null';
if (arg === false || arg === true) return 'Boolean';
var type = typeof arg;
if (type === 'string') return 'String';
if (type === 'number') return 'Number';
return 'Object';
}
// 1. Let obj be a newly created native ECMAScript object.
// 2. Set all the internal methods of obj as specified in 8.12.
// 3. Set the [[Class]] internal property of obj to "Object".
// 4. Set the [[Extensible]] internal property of obj to true.
// All of the steps above are implicitly completed in steps 6 or 7
var obj;
// 5. Let proto be the value of calling the [[Get]] internal property of F
// with argument "prototype".
var proto = F.prototype;
// 6. If Type(proto) is Object, set the [[Prototype]] internal property of
// obj to proto.
if (Type(proto) === 'Object') obj = Object.create(proto);
// 7. If Type(proto) is not Object, set the [[Prototype]] internal property
// of obj to the standard built-in Object prototype object as described
// in 15.2.4.
else obj = {};
// 8. Let result be the result of calling the [[Call]] internal property of
// F, providing obj as the this value and providing the argument list
// passed into [[Construct]] as args.
var result = F.apply(obj, args);
// 9. If Type(result) is Object then return result.
if (Type(result) === 'Object') return result;
// 10. Return obj.
return obj;
}
About the typeof
cases, that is for the edge case when host objects return a typeof
value which is not object
or function
. By testing for anything they can't be, this allows us to test for it properly instead of relying on them being native objects.
Note that this is for at least ES5 -- the only way to shim Object.create
in ES3 or older environments would be to use the exact thing that we're currently trying to emulate, which defeats the point of doing so.
Had this same curiosity recently.
There is an issue with some of the steps being internal tasks, which simply can't be reimplemented within language.
And, over my original take, Qantas has a good point about using typeof
as a faux Type()
-- that it should be exclusive to support custom host object types.
But, with that, this is as close as I could manage. Though, it requires that Object.create()
be available (so, ES5+).
Object.new = function (constructor /*, args */) {
function isObject(operand) {
// detect and refuse primitives
var type = typeof operand;
return type !== 'undefined' &&
type !== 'boolean' &&
type !== 'number' &&
type !== 'string' &&
operand !== null;
}
var argList = Array.prototype.slice.call(arguments, 1);
if (typeof constructor !== 'function') {
throw new TypeError((typeof constructor) + ' is not a function');
}
var proto = constructor.prototype;
var obj = Object.create(isObject(proto) ? proto : Object.prototype);
var result = constructor.apply(obj, argList);
return isObject(result) ? result : obj;
};
Example:
function Foo(one, two) {
this.one = one;
this.two = two;
}
var bar = Object.new(Foo, 'a', 'b');
console.log(bar instanceof Foo); // true
console.log(bar.one); // "a"
console.log(bar.two); // "b"
And, the annotated version, with steps from 11.2.2 new
Operator and 13.2.2 [[Construct]]
:
// 1. Let ref be the result of evaluating MemberExpression.
// 2. Let constructor be GetValue(ref).
Object.new = function (constructor /*, args */) {
function isObject(operand) {
// detect and refuse primitives
var type = typeof operand;
return type !== 'undefined' &&
type !== 'boolean' &&
type !== 'number' &&
type !== 'string' &&
operand !== null;
}
// 3. Let argList be the result of evaluating Arguments, producing an internal list of argument values (11.2.4).
var argList = Array.prototype.slice.call(arguments, 1);
// 4. If Type(constructor) is not Object, throw a TypeError exception.
// 5. If constructor does not implement the [[Construct]] internal method, throw a TypeError exception.
if (typeof constructor !== 'function') {
throw new TypeError((typeof constructor) + ' is not a function');
}
// 6. Return the result of calling the [[Construct]] internal method on constructor, providing the list argList as the argument values.
// For [[Construct]], it gets a bit out of order with current options for internal vs. abstractions.
// 5. Let proto be the value of calling the [[Get]] internal property of F with argument "prototype".
var proto = constructor.prototype;
// 1. Let obj be a newly created native ECMAScript object.
// 2. Set all the internal methods of obj as specified in 8.12.
// 3. Set the [[Class]] internal property of obj to "Object".
// 4. Set the [[Extensible]] internal property of obj to true.
//
// 6. If Type(proto) is Object, set the [[Prototype]] internal property of obj to proto.
// 7. If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the standard built-in Object prototype object as described in 15.2.4.
var obj = Object.create(isObject(proto) ? proto : Object.prototype);
// 8. Let result be the result of calling the [[Call]] internal property of F, providing obj as the this value and providing the argument list passed into [[Construct]] as args.
var result = constructor.apply(obj, argList);
// 9. If Type(result) is Object then return result.
// 10. Return obj.
return isObject(result) ? result : obj;
};
This appears to be the minimum necessary, albeit not 100% conformant with the language definition and without error checking. It's ES5 only. There is a shim for Object.create
, but by necessity it must call new
making it rather pointless in this context!
function _new(T, args) {
var o = Object.create(T.prototype);
var res = o.constructor.apply(o, args);
return (typeof res === 'object') ? res : o;
}
with usage:
function A(name) {
this.name = name;
}
A.prototype.hello = function() {
console.log(this);
}
var foo = _new(A, ['foo']);
var bar = _new(A, ['bar']);
console.log(foo.name);
console.log(bar.name);
The test of the return value of the call to the constructor is necessary because a constructor does not have to return this
- if the constructor returns nothing the this
is implicit.
Haters gonna hate, but something like this?
Obviously this is just a round about alias of new
but with scope to extend it's potential with the arguments.
function _new(classname, arguments) {
// other logic
return new classname(arguments || {});
}
function car(args){
alert(args.make);
}
var MyNewCar = _new(car, {make: "vw"});