dynamic object construction in javascript? - javascript

When I want to call a function in javascript with arguments supplied from elsewhere I can use the apply method of the function like:
array = ["arg1", 5, "arg3"]
...
someFunc.apply(null, array);
but what if I need to call a constructor in a similar fashion? This does not seem to work:
array = ["arg1", 5, "arg3"]
...
someConstructor.apply({}, array);
at least not as I am attempting:
template = ['string1', string2, 'etc'];
var resultTpl = Ext.XTemplate.apply({}, template);
this does not work wither:
Ext.XTemplate.prototype.constructor.apply({}, template);
Any way to make that one work? (In this particular case I found that new Ext.XTemplate(template) will work, but I am interested in the general case)
similar question but specific to built-in types and without an answer I can use:
Instantiating a JavaScript object by calling prototype.constructor.apply
Thank you.
Edit:
Time has passed and ES6 and transpilers are now a thing.
In ES6 it is trivial to do what I wanted: new someConstructor(...array).
Babel will turn that into ES5 new (Function.prototype.bind.apply(someConstructor, [null].concat(array)))(); which is explained in How to construct JavaScript object (using 'apply')?.

There's no simple, straightforward way to do this with a constructor function. This is because special things happen when you use the new keyword to call a constructor function, and so if you're not going to do that, you have to emulate all of those special things. They are:
Creating a new object instance (you're doing that).
Setting that object's internal prototype to constructor function's prototype property.
Setting that object's constructor property.
Calling the constructor function with that object instance as the this value (you're doing that).
Handling the special return value from the constructor function.
I think that's about it, but worth double-checking in the spec.
So if you can avoid it and just use the constructor function directly, I'd do that. :-) If you can't, though, you can still do it, it's just awkward and involves workarounds. (See also this related answer here on StackOverflow, although I cover all of the ground here [and then some] as well.)
Your biggest issue is #2 above: Setting the internal prototype of the object. For a long time, there was no standard way to do this. Some browsers supported a __proto__ property that did it, so you can use that if it's there. The good news is that ECMAScript 5 introduces a way to do this explicitly: Object.create. So cutting-edge browsers like Chrome will have that. But if you're dealing with a browser that has neither Object.create nor __proto__, it gets a bit ugly:
1) Define a custom constructor function.
2) Set its prototype property to the prototype property of the real constructor function
3) Use it to create a blank object instance.
That handles the prototype for you. Then you continue with:
4) Replace the constructor property on that instance with the real constructor function.
5) Call the real constructor function via apply.
6) If the return value of the real constructor function is an object, use it instead of the one you created; otherwise, use the one you created.
Something like this (live example):
function applyConstruct(ctor, params) {
var obj, newobj;
// Use a fake constructor function with the target constructor's
// `prototype` property to create the object with the right prototype
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
obj = new fakeCtor();
// Set the object's `constructor`
obj.constructor = ctor;
// Call the constructor function
newobj = ctor.apply(obj, params);
// Use the returned object if there is one.
// Note that we handle the funky edge case of the `Function` constructor,
// thanks to Mike's comment below. Double-checked the spec, that should be
// the lot.
if (newobj !== null
&& (typeof newobj === "object" || typeof newobj === "function")
) {
obj = newobj;
}
// Done
return obj;
}
You could take it a step further and only use the fake constructor if necessary, looking to see if Object.create or __proto__ are supported first, like this (live example):
function applyConstruct(ctor, params) {
var obj, newobj;
// Create the object with the desired prototype
if (typeof Object.create === "function") {
// ECMAScript 5
obj = Object.create(ctor.prototype);
}
else if ({}.__proto__) {
// Non-standard __proto__, supported by some browsers
obj = {};
obj.__proto__ = ctor.prototype;
if (obj.__proto__ !== ctor.prototype) {
// Setting it didn't work
obj = makeObjectWithFakeCtor();
}
}
else {
// Fallback
obj = makeObjectWithFakeCtor();
}
// Set the object's constructor
obj.constructor = ctor;
// Apply the constructor function
newobj = ctor.apply(obj, params);
// If a constructor function returns an object, that
// becomes the return value of `new`, so we handle
// that here.
if (typeof newobj === "object") {
obj = newobj;
}
// Done!
return obj;
// Subroutine for building objects with specific prototypes
function makeObjectWithFakeCtor() {
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
return new fakeCtor();
}
}
On Chrome 6, the above uses Object.create; on Firefox 3.6 and Opera, it uses __proto__. On IE8, it uses the fake constructor function.
The above is fairly off-the-cuff, but it mostly handles the issues I'm aware of in this area.

From developer.mozilla:
Bound functions are automatically suitable for use with the new operator to construct new instances created by the target function. When a bound function is used to construct a value, the provided this is ignored. However, provided arguments are still prepended to the constructor call.
That said, we still need to use apply to get the arguments out of an array and into the bind call. Further, we need to also reset bind's function as the this argument of the apply function. This gives us a very succinct one-liner that does exactly as needed.
function constructorApply(ctor, args){
return new (ctor.bind.apply(ctor, [null].concat(args)))();
};

Since the original question is quite old, here's a simpler methods in practically any modern browser (as of writing, 2018, I'm counting Chrome, FF, IE11, Edge...).
var template = ['string1', string2, 'etc'];
var resultTpl = Object.create(Ext.XTemplate.prototype);
Ext.XTemplate.apply(resultTpl, template);
Those two lines also explain how the new operator basically works.

I think another way of achieving this could be to extend the Ext Template class so that the constructor of the new object takes your array and does the stuff you want to do. This way all the constructing stuff would be done for you and you could just call the constructor of your superclass with your arguments.

Related

Set the type name for an object

I'm defining a JavaScript function like this:
function F() {
}
It gets some more properties and methods through a prototype object. When it's time to use the function, I'd like to see the object name, like for Array or other built-in objects. But I always see "Object" as the object type.
var i = new F();
console.log(i);
// Object { }
// wanted: something with "F" instead of "Object"
var a = [];
console.log(a);
// Array []
console.log(document);
// HTMLDocument about:newtab
What can I do to get the desired output?
I found several other questions here but none had the desired effect. I'm not too experienced with the whole prototype and new thing in JavaScript so I don't know the correct terms to find relevant results.
My function F must still be callable as a regular function, so I don't think I am interested in creating a class as supported in newer web browsers.
My code targets any web browsers that's in wide use today, which is probably Chrome, Firefox, Safari and a bunch of Android apps with unknown internals (Samsung?).
The display name of objects is controlled by their .toString() behavior. The default implementation of .toString() uses the value returned by the Symbol.toStringTag property (see here), which will be found on the Object prototype if not defined explicitly for your type. Thus it will have the value "Object" unless you override it.
function F() { }
let i = new F();
console.log(i.toString()); //[object Object]
F.prototype[Symbol.toStringTag] = "F";
console.log(i.toString()); //[object F]

Implementation of Javascript class generic setter (modifying setter without knowing the property name)

Originally my objective is when any property of the instances of class OnlyOneProp is set,
i.e. obj.what_ever_property = "value",
at last it will only modify obj.the_only_prop.
Behavior is like this:
var obj = new OnlyOneProp();
obj.what_ever_property = "value";
console.log(obj.only_property, obj.what_ever_property);
// expected output:
// >value undefined
Original Question: Is there a way to implement above behaviour?
edit:
With respect to the solution in the answer,
here are some follow up questions:
1) Is there any major flaw to the above code? (Like I had once mixed up receiver and target, which may cause infinite looping on the setter)
2) Would Proxy hinder the performance a lot?
3) Is there any way to bypass the above proxy setter? (Like defineProperty()or so)
4) It can also be an implementation of ReadOnlyObject (after removing the setting line in setter), but would there be a better implementation? (It's a bit out of topic but I also want to know, because I really want to remove the top Proxy which is just overriding the constructor)
If you return an object from the constructor, the new operator returns that object, not the new object it passed to the constructor as this. So a more straight forward version of OnePropertyClass might be
class OnePropertyClass{
constructor( value) {
var self = this;
return new Proxy( this, {
set: function(target, property, value) {
self["only_property"] = value;
return true;
}
}
);
}
}
This can be simplified by using an arrow function instead of the closure:
class OnePropertyClass{
constructor() {
return new Proxy( this, {
set: (target, property, value) => {
this.only_property = value;
return true;
}
}
);
}
}
var obj = new OnePropertyClass();
obj.what_ever_property = "value";
console.log(obj.only_property, obj.what_ever_property);
It doesn't set up any setter loops because the setter stores the value on the actual this object of the constructor, not on the proxy object returned.
Instances of this version of OnePropertyClass inherit per usual - the constructor property returns the OnePropertyClass constructor function, and Object.prototype properties and methods are still inherited.
You may wish to freeze OnePropertyClass.prototype to prevent additions of any other inherited properties. You may also wish to provide trap functions for defineProperty and possibly setPrototype to prevent run time property additions - see MDN handler object methods for details.
Proxy implementation is probably written in C++ and I would expect most of the additional overheads will lie in calling the setter function.
I have not tested this version for extensibility and did not use the target parameter of the set handler Please experiment before use :-)
After digging up from MDN Proxy and inspiration from dynamic setter/getter,
I've come up with the code below:
var OnlyOneProp = new Proxy(
// target
class{// normal class definition
constructor(){
// console.log("anonymous constructor");
}
}, {
construct(target, args, caller){
// if(!new.target){}
// console.log("proxy construct");
return new Proxy(new target(), {
set(target, name, value, receiver){
target.only_property = value;
return true; // must have according to some specification
}
});
},
});
var obj = new OnlyOneProp();
obj.what_ever_property = "value";
console.log(obj.only_property, obj.what_ever_property);
// output: value undefined
It's fully functioning but as you may see there are two new Proxy() instantiation (although the first one only execute once), which I want to remove if possible.

Is there any benefit to call Reflect.apply() over Function.prototype.apply() in ECMAScript 2015?

I am just wondering if there is any good reason to call:
Reflect.apply(myFunction, myObject, args);
instead of:
myFunction.apply(myObject, args);
You can compare the definition of Function.prototype.apply and Reflect.apply in the spec.
Basically they are equivalent, but there is a difference: if the arguments list is null or undefined, Function.prototype.apply will call the function with no arguments, and Reflect.apply will throw.
function func() {
return arguments.length;
}
func.apply(void 0, null); // 0
Reflect.apply(func, void 0, null); // TypeError: null is not a non-null object
Another difference is that, when you use func.apply, you assume
func is a Function instance, i.e. it inherits from Function.prototype
func has no apply own property which would shadow Function.prototype.apply
But Reflect.apply doesn't require that. For example,
var obj = document.createElement('object');
typeof obj; // "function" -- can be called
obj.apply; // undefined -- does not inherit from Function.prototype
Reflect.apply(obj, thisArg, argList); // -- works properly
var func = a => a;
func.apply = a => 0;
func.apply(void 0, [123]); // 0 -- Function.prototype.apply is shadowed by an own property
Reflect.apply(func, void 0, [123]); // 123 -- works properly
See also the SO question What does the Reflect object do in JavaScript?, which includes this text in the top answer:
Now that we have modules, a “#reflect” module is a more natural place for many of the reflection methods previously defined on Object. For backwards-compatibility purposes, it is unlikely that the static methods on Object will disappear. However, new methods should likely be added to the “#reflect” module rather than to the Object constructor
My understanding is that in previous iterations of JS, tools related to "reflection" have been scattered around the language, as part of the Object prototype and Function prototype. The Reflect object is an effort to bring them under one roof.
So, in the case of your question, although there are differences (see Oriol's answer), the reason for both to exist is a general move to future-proof reflection tooling in the ES spec.
One use i can think of is using Reflect.apply in flow managment or in functions that execute an array of function
function execFuncs(funcArr){
var obj = this.someObj;
funcArr.forEach(function(func){
Reflect.apply(func,obj)
});
}
which much more convinient then
function execFuncs(funcArray){
var obj = this.someObj;
funcArray.forEach(function(func){
func.prototype.apply(obj)
})
}
since you have more control.

What are the edge cases when using Object.prototype.toString?

So far I have relied on Object.prototype.toString.call(x) to distinguish between the different native object types in Javascript, arrays in particular.
If you subclass arrays, you get some strange behavior:
function Ctor() {}
Ctor.prototype = Object.create(Array.prototype);
var x = new Ctor();
x.push(1);
Object.prototype.toString.call(x); // [object Object]
Probably this is documented in the ES5 specs (and no longer an issue in ES6), but I consider it a quirk of the current version of the language. I adapted my corresponding functions as follows:
function objTypeOf(deep, type) {
return function _objTypeOf(x) {
do {
if (Object.prototype.toString.call(x).slice(8, -1).toLowerCase() === type) return true;
x = Object.getPrototypeOf(x);
} while(deep && x !== null);
return false;
};
}
var arr = objTypeOf(false, "array"),
arrP = objTypeOf(true, "array"); // array prototype
console.log(arr(x)); // false
console.log(arrP(x)); // true
objTypeOf checks the current object and the entire prototype chain until there is a type match. It accepts an object even if merely one of the prototypes matches the expected type. objTypeOf is not based on prototype identities, but on strings (lacking identity).
I wonder now if there are other edge cases when using Object.prototype.toString, that need special treatment?
Well your problem is not with Object.prototype.toString, but that you tried to subclass arrays. It just doesn't work, and toString correctly tells you that you failed to create an array. It's merely an object that has Array.prototype in its prototype chain (if that was what you cared for, use instanceof Array).
Regardless, to answer your title question:
What are the edge cases when using Object.prototype.toString?
Host objects. Everything that is not a native JS object, despite looking like one, might return any [[Class]] value that you didn't expect. There are even known cases where callable objects do not report Function.

mootools Type function

So I am trying to learn javascript by learning how Mootools works internally. I am looking at these lines specifically:
var Type = this.Type = function(name, object){
if (name){
var lower = name.toLowerCase();
var typeCheck = function(item){
return (typeOf(item) == lower);
};
Type['is' + name] = typeCheck;
if (object != null){
object.prototype.$family = (function(){
return lower;
}).hide();
}
}
if (object == null) return null;
object.extend(this);
object.$constructor = Type;
object.prototype.$constructor = object;
return object;
};
//some more code
new Type('Type',Type);
What is happening here?
What is the object being assigned to in the constructor statement down at the bottom, the global window object?
Why is it being called as a constructor with the new statement when the Type function seems to only update the passed in object rather than creating a new one?
Specifically what is 'this' in the line object.extend(this); ? is it the global window object, if so is it adding all the key, value pairs of that object to the Type object?
Sheesh, questions lately seem to focus a lot more on mootools internals.
I will answer to the best of my knowledge as I am not a core dev.
Type in MooTools is pretty similar to Class (in fact, the Class constructor itself is a Type) but it focuses more on data / values and the Types of values. It also is meant to extend the native Types defined by ECMA spec and make them more flexible.
I guess there is no point in talking about data types in general (String, Number, Array, Object, etc). Why is there are need to extend them? Well, for starters, Duct Typing is a little quirky in js. typeof {}; // object, typeof []; // object, typeof new Date(); // object etc - not the most helpful, even if since all types inherit from object and is logical for them to be grouped together, it does not help you write code.
So, in the context of js objects, they are created from constructor objects...
What Type does is not replace the constructor functions but changes an existing constructor by adding new methods or properties to it.
eg. new Type('Array', Array);
This will turn the native Array constructor into a type object of sorts. You don't need to save the result or anything - it's a one off operation that mods the original and leaves it open for manipulation.
typeOf(Array); // type
So, what is different? Well, for starters, typeOf([]) is now able to actually tell us what it really is - array. And what really happens here is this: object.extend(Type);, the magical bit. It will copy to the target object all the properties defined on the Type object - you can see them here:
https://github.com/mootools/mootools-core/blob/master/Source/Core/Core.js#L211-232
So, immediately, your newly created Type gets the all important implement and extend methods.
More advanced, let's create a new type that is based on the native Array constructor:
var foo = new Type('foo', Array),
a = new foo();
// it's a real boy!
a.push('hi');
// ['hi'], foo, object
console.log(a, typeOf(a), typeof a);
But, what if we wanted a custom Type? One that is magical and special? No problem, because argument 2 can actually be a (anonymous) function.
var Awesome = new Type('awesome', function(name, surname) {
// console.log('I R teh construct0r!');
this.name = name;
this.surname = surname;
});
// extend it a little.
Awesome.implement({
getName: function() {
return this.name;
},
getSurname: function() {
return this.surname;
}
});
var Dimitar = new Awesome('dimitar', 'christoff');
console.log(typeOf(Dimitar)); // awesome
console.log(Dimitar.getName()); // dimitar
In another example, look at DOMEvent. It takes the above and makes it into a faster leaner Object type. https://github.com/mootools/mootools-core/blob/master/Source/Types/DOMEvent.js#L21
So why is that not a class? Because, Classes are more expensive and events happen all the time.
I hope this helps you somewhat. for a more detailed explanation, ask on the mailing list and hope for the best, perhaps a core dev will have time as it's not your standard 'how do I get the accordion working' type of question...

Categories

Resources