Dynamic instantiation in JavaScript [Classname as String] - javascript

I am working with Javascript and Appdescriptors in JSON format.
What I want to do is creating a instance of a Class, where the classname is saved as string in oModelConf[sModelName].type. If that is not the case I want to take "sap.ui.model.odata.ODataModel"
Related Question offers this solution:
function instantiate(className, args) {
var o, f, c;
c = window[className]; // get reference to class constructor function
f = function(){}; // dummy function
f.prototype = c.prototype; // reference same prototype
o = new f(); // instantiate dummy function to copy prototype properties
c.apply(o, args); // call class constructor, supplying new object as context
o.constructor = c; // assign correct constructor (not f)
return o;
}
This is not a very good solution I think.
EDIT It does not work for me because my class is not defined on window, so window[className] is undefined. I do not know where my function is defined in SAPUI5
A second Solution:
eval(`a = new ${oModelConf[sModelName].type || "sap.ui.model.odata.ODataModel"}(sServiceUrl, true);`);
This is not a better solution because we should not use eval().
Are there any better solutions?
EDIT2
Because of the url in pimskies answer I found an other solution:
Since window.sap.ui.model.odata.ODataModel is the same as sap.ui.model.odata.ODataModel and window.sap is the same as window[sap]
I could take my string, and replace all . with ][, put the right brackets to front and end.
I will not code that because it is not a going solution.(I should not have coded the evalthing too...)

You could use jQuery.sap.getObject to access the class:
var ModelClass = jQuery.sap.getObject(oModelConf[sModelName].type || "sap.ui.model.odata.ODataModel");
var model = new ModelClass();
Edit: An other way (which i would recommend if you use AMD)
If you are using the modern AMD modules and you don't know if the module containing your class has already been loaded, you should use sap.ui.require() to load the module asynchronously. It requires the module to be specified via its unified resource name (the conversion is probably the most ugly part):
var className = oModelConf[sModelName].type || "sap.ui.model.odata.ODataModel";
var urn = className.replace(".", "/"); //Convert to unified resource name
sap.ui.require([urn],function(ModelClass){
//This function is called when the module is available
var model = new ModelClass();
...
});

Maybe map the string to a class?
function Foo() {
console.log('new foo');
}
function Bar() {
console.log('new bar');
}
var objects = {
'foo': Foo,
'bar': Bar
};
var cls = objects.foo || Bar;
new cls();
https://jsfiddle.net/ckd56d9v/1/
Or take a look at this answer: https://stackoverflow.com/a/9804142/5930258

What not combine the two? Since window[className] is failing, replace it with
oModelConf[className].type || sap.ui.model.odata.ODataModel...
function instantiate(className, args) {
var o, f, c;
c = oModelConf[className] || sap.ui.model.odata.ODataModel;
f = function(){}; // dummy function
f.prototype = c.prototype; // reference same prototype
o = new f(); // instantiate dummy function to copy prototype properties
c.apply(o, args); // call class constructor, supplying new object as context
o.constructor = c; // assign correct constructor (not f)
return o;
}

Related

Custom implementation of `Object.create` not involving private `function`

According to various sources, including the specs, if one was to create a custom implementation of the simpler version of Object.create then it would go like this:
if (typeof Object.mycreate1 !== 'function') {
Object.mycreate1 = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
However, as new basically creates a new instance and calls F on it, I wonder whether or not this is also correct:
if (typeof Object.mycreate2 !== 'function') {
Object.mycreate2 = function( o ) {
var _ = { };
_.prototype = o;
return _;
}
}
Simple tests suggest that this works:
var p = { name : 'Jan', surname : 'Kowalski' };
var q = Object.mycreate2( p );
q.surname = 'Malinowski';
p.surname = 'Kowalski';
console.log( q.surname );
console.log( p.surname );
The question is: is the latter implementation correct? If no, I'd also like to know why.
I might probably know the answer: object's prototype property is read only and while it works in FireFox, it shouldn't. On the other hand, function's prototype is read-write and it can be changed.
The problem with this hypothetical answer is that I can't validate it in the specs.
Your second function is not equivalent to the first. Setting the "prototype" property of a simple object doesn't have any effect. A better test:
var x = Object.mycreate2({foo: "bar"});
console.log(x.foo); // undefined
The "prototype" property of a constructor function is interesting, because it actually does have a role in establishing the inheritance chain for a constructed object.

Dynamically control arguments while creating objects in javascript

I have multiple eatable classes in javascript eg: food, drinks, snacks.
Each of this class requires a different set of parameters. I have another factory class which creates an instance of the eatable item that is sent to it.
I am not able to figure out how can we dynamically select the eatable item and pass the arguments (which are in an array form) using this factory?
I have come up with two solutions -
Solution 1:
var factory = function(eatable, argumentList){
var obj = new eatable(argumentList);
return obj
};
This is a problem because argumentList is an array.
Solution 2
var factory = function(eatable, argumentList){
var obj = eatable.apply({}, argumentList);
return obj
};
this does not really create an object of the eatable type.
The effect that I really want
Say I am able to convert the argumentList into a js argument type object then -
var obj = new eatable(argumentList.toArguments());
obj instanceOf eatable; // should return true
Please help!
Ah, yes. I've encountered this problem before - you can't use new and apply together in JavaScript. A similar question has been asked before: Use of .apply() with 'new' operator. Is this possible?
The problem is quite apparent - new is a keyword, not a function; and apply can only be used on a function. If new was a function instead of a keyword then we could use it in conjuction with apply.
To understand how to do so let's create a function called new which does exactly what the keyword new does:
Function.prototype.new = (function () {
function Factory(constructor, args) {
return constructor.apply(this, args);
}
return function() {
Factory.prototype = this.prototype;
return new Factory(this, arguments);
};
}());
Now instead of calling a constructor as follows:
var object = new constructor(arg1, ...);
You can call a constructor as follows:
var object = constructor.new(arg1, ...);
What's the advantage of doing so you ask? Well it's simple really. Because new is now a function instead of a keyword you can use it in conjunction with apply as follows:
var object = Function.new.apply(constructor, [arg1, ...]);
Hence your eatable factory function now becomes:
var factory = function(eatable, argumentList) {
var obj = Function.new.apply(eatable, argumentList);
return obj;
};
Edit: If all your factory function does is take an eatable constructor and an argumentList and return new.apply(eatable, argumentList) then as Bergi pointed out in his comment you could define factory as follows instead:
var factory = Function.apply.bind(Function.new);
Hope this helped.
You can use Object.create to set up the prototype chain correctly:
function factory(eatable, argumentList){
var obj = Object.create(eatable.prototyope);
return eatable.apply(obj, argumentList) || obj;
}
This is basically what the new operator does.
You can define a function init to initialize the object .
function Eatable(){
}
Eatable.prototype.init = function(/** arg1, arg2, arg3 **/){
// initialize object
}
In factory function
var eatable = new Eatable();
eatable.init.apply(eatable, /** pass arguments array here **/);
return eatable;
You have to provide context to apply, The context is the object you are trying to apply the arguments to. The context you are currently passing {} is of type Object
var factory = function(eatable, argumentList){
var obj = eatable.apply(new Eatable(), argumentList);
return obj
};
I can not use factories with out polymorphism so if you didn't create those eatables in way they extend an Eatalbe object you will not be able to do it.
One more way to achieve this is as follows -
var _bind = Function.prototype.bind;
var factory = function(_constructor, _argumentList){
var obj = _bind.apply(_constructor, [null].concat(_argumentList));
return obj
};

Is it possible to call function.apply without changing the context?

In some Javascript code (node.js specifically), I need to call a function with an unknown set of arguments without changing the context. For example:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(this, args);
}
The problem in the above is that when I call apply, I'm change the context by passing this as the first argument. I'd like to pass args to the function being called without changing the context of the function being called. I essentially want to do this:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(<otherFn's original context>, args);
}
Edit: Adding more detail regarding my specific question. I am creating a Client class that contains a socket (socket.io) object among other info pertaining to a connection. I am exposing the socket's event listeners via the client object itself.
class Client
constructor: (socket) ->
#socket = socket
#avatar = socket.handshake.avatar
#listeners = {}
addListener: (name, handler) ->
#listeners[name] ||= {}
#listeners[name][handler.clientListenerId] = wrapper = =>
# append client object as the first argument before passing to handler
args = Array.prototype.slice.call(arguments)
args.unshift(this)
handler.apply(this, args) # <---- HANDLER'S CONTEXT IS CHANGING HERE :(
#socket.addListener(name, wrapper)
removeListener: (name, handler) ->
try
obj = #listeners[name]
#socket.removeListener(obj[handler.clientListenerId])
delete obj[handler.clientListenerId]
Note that clientListenerId is a custom unique identifier property that is essentially the same as the answer found here.
If I understand you correctly:
changes context
| n | y |
accepts array n | func() | func.call() |
of arguments y | ???????? | func.apply() |
PHP has a function for this, call_user_func_array. Unfortunately, JavaScript is lacking in this regard. It looks like you simulate this behavior using eval().
Function.prototype.invoke = function(args) {
var i, code = 'this(';
for (i=0; i<args.length; i++) {
if (i) { code += ',' }
code += 'args[' + i + ']';
}
eval(code + ');');
}
Yes, I know. Nobody likes eval(). It's slow and dangerous. However, in this situation you probably don't have to worry about cross-site scripting, at least, as all variables are contained within the function. Really, it's too bad that JavaScript doesn't have a native function for this, but I suppose that it's for situations like this that we have eval.
Proof that it works:
function showArgs() {
for (x in arguments) {console.log(arguments[x]);}
}
showArgs.invoke(['foo',/bar/g]);
showArgs.invoke([window,[1,2,3]]);
Firefox console output:
--
[12:31:05.778] "foo"
[12:31:05.778] [object RegExp]
[12:31:05.778] [object Window]
[12:31:05.778] [object Array]
Simply put, just assign the this to what you want it to be, which is otherFn:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(otherFn, args);
}
'this' is a reference to your function's context. That's really the point.
If you mean to call it in the context of a different object like this:
otherObj.otherFn(args)
then simply substitute that object in for the context:
otherObj.otherFn.apply(otherObj, args);
That should be it.
If you bind the function to an object and you use everywhere the bound function, you can call apply with null, but still get the correct context
var Person = function(name){
this.name = name;
}
Person.prototype.printName = function(){
console.log("Name: " + this.name);
}
var bob = new Person("Bob");
bob.printName.apply(null); //window.name
bob.printName.bind(bob).apply(null); //"Bob"
One way that you can work around the change of context that can happen in JavaScript when functions are called, is to use methods that are part of the object's constructor if you need them to be able to operate in a context where this is not going to mean the parent object, by effectively creating a local private variable to store the original this identifier.
I concede that - like most discussions of scope in JavaScript - this is not entirely clear, so here is an example of how I have done this:
function CounterType()
{
var counter=1;
var self=this; // 'self' will now be visible to all
var incrementCount = function()
{
// it doesn't matter that 'this' has changed because 'self' now points to CounterType()
self.counter++;
};
}
function SecondaryType()
{
var myCounter = new CounterType();
console.log("First Counter : "+myCounter.counter); // 0
myCounter.incrementCount.apply(this);
console.log("Second Counter: "+myCounter.counter); // 1
}
These days you can use rest parameters:
function fn(...args) {
otherFn(...args);
}
The only downside is, if you want to use some specific params in fn, you have to extract it from args:
function fn(...args) {
let importantParam = args[2]; //third param
// ...
otherFn(...args);
}
Here's an example to try (ES next version to keep it short):
// a one-line "sum any number of arguments" function
const sum = (...args) => args.reduce((sum, value) => sum + value);
// a "proxy" function to test:
var pass = (...args) => sum(...args);
console.log(pass(1, 2, 15));
I'm not going to accept this as an answer, as I'm still hoping for something more suitable. But here's the approach I'm using right now based upon the feedback on this question so far.
For any class that will be calling Client.prototype.addListener or Client.prototype.removeListener, I did added the following code to their constructor:
class ExampleClass
constructor: ->
# ...
for name, fn of this
this[name] = fn.bind(this) if typeof(fn) == 'function'
message: (recipient, body) ->
# ...
broadcast: (body) ->
# ...
In the above example, message and broadcast will always be bound to the new ExampleClass prototype object when it's instantiated, allowing the addListener code in my original question to work.
I'm sure some of you are wondering why I didn't just do something like the following:
example = new ExampleClass
client.addListener('message', example.bind(example))
# ...
client.removeListener('message', example.bind(example))
The problem is that every time .bind( ) is called, it's a new object. So that means that the following is true:
example.bind(example) != example.bind(example)
As such, the removeListener would never work successfully, thus my binding the method once when the object is instantiated.
Since you seem to want to be using the bind function as it is defined in Javascript 1.8.5, and be able to retrieve the original this object you pass the bind function, I recommend redefining the Function.prototype.bind function:
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
/** here's the additional code **/
fBound.getContext = function() {
return oThis;
};
/**/
return fBound;
};
Now you can retrieve the original context that you called the bind function with:
function A() {
return this.foo+' '+this.bar;
}
var HelloWorld = A.bind({
foo: 'hello',
bar: 'world',
});
HelloWorld(); // returns "hello world";
HelloWorld.getContext(); // returns {foo:"hello", bar:"world"};
I was just reminded of this question after a long time. Looking back now, I think what I was really trying to accomplish here was something similar to how the React library works with its automatic binding.
Essentially, each function is a wrapped bound function being called:
function SomeClass() {
};
SomeClass.prototype.whoami = function () {
return this;
};
SomeClass.createInstance = function () {
var obj = new SomeClass();
for (var fn in obj) {
if (typeof obj[fn] == 'function') {
var original = obj[fn];
obj[fn] = function () {
return original.apply(obj, arguments);
};
}
}
return obj;
};
var instance = SomeClass.createInstance();
instance.whoami() == instance; // true
instance.whoami.apply(null) == instance; // true
Just push properties directly to the function's object and call it with it's own "context".
function otherFn() {
console.log(this.foo+' '+this.bar); // prints: "hello world" when called from rootFn()
}
otherFn.foo = 'hello';
otherFn.bar = 'world';
function rootFn() {
// by the way, unless you are removing or adding elements to 'arguments',
// just pass the arguments object directly instead of casting it to Array
otherFn.apply(otherFn, arguments);
}

Help with prototype object

I am mlearning javascript and have some trouble creating an onject via prototype.
I have this:
<script type="text/javascript">
function myclass(a, b, c) {
if (arguments.length) { this.Init(a, b, c); }
}
myclass.prototype.Init = function(a, b, c) {
this.param1 = a;
this.param2 = b;
this.param3 = c;
};
myclass.prototype.Print = function() {
alert(this.param1 + '-' + this.param2 + '-' + this.param3);
};
var myObject = myclass(3, 5, 6);
myObject.Print();
</script>
but I get an error on line with this.Init(a, b, c);
Error: Object doesn't support this property or method
You forgot the new keyword when you declare myObject:
var myObject = new myclass(3, 5, 6);
Just out of curiosity is there a particular reason you have a separate "init" method?
The function that defines your "class" is called the "constructor" and you can just perform the setup there. If you wanted to "re-initialize" the object, then it may be helpful but it doesn't seem to serve a point here.
For instance:
// You might as well start wrapping your code now:
var myExample = (function myExample () {
// A common convention is to start the name of constructors with a
// capital letter, one reason is it help makes it more obvious
// when you forget the new keyword...Whether you use it or not
// is up to you. Also note, calling it "MyClass" is a little
// misleading because it's not a "class" really. You might
// confuse yourself if you think of it as a class too much.
// If you're wondering why I put the name twice, it's because
// otherwise it would be an anonymous function which can be
// annoying when debugging. You can just use var MyClass = function () {}
// if you want
var MyClass = function MyClass(a, b, c) {
// This will set each parameter to whatever was provided
// or if nothing is provided: null. If you leave out
// the || "" part then any
// time a value is not provided the parameter will
// return "undefined". This may be what you want in some cases.
this.param1 = a || "";
this.param2 = b || "";
this.param3 = c || "";
};
// likewise it's convention to start most variables/functions lowercase
// I think it's easier to type/looks better, but do as you please.
MyClass.prototype.print = function print() {
alert(this.param1 + '-' + this.param2 + '-' + this.param3);
};
var myObject = new MyClass();
myObject.print();
}());
The "wrapping" is
(function () {
//your code here
}());
It's mostly pointless here, but it's something you'll have to start doing eventually so might as well start now. That's just one way to "wrap" there are others as well.
Basically, the way your script was written, if the user ran another script that had a function called MyClass, it could overwrite yours or vice versa, causing problems.
The "wrapping" keeps it all within that function. If you need to make something available to outside stuff, you can expose it.
per comment:
You can access functions and variables from inside the wrapper by exposing them to the outside like so:
var myApp = (function myApp(){
// The constructor for our "class", this will be available from outside because
// we will expose it later
var myClass = function(){
//code to set up "class" etc
// See how we can use private function within myApp
privateFunction();
};
// Here we set up the private function, it will not be available outside myApp
// because will will not expose it
var privateFunction = function(){ };
// Another public function that we will expose later
var otherPublic = function(){};
//now we expose the stuff we want public by returning an object containing
// whatever it is we want public, in this case it's just myClass and otherPublic
return { myClass: myClass, otherPublic: otherPublic };
}());
Note in that example we are just exposing the constructor, if you wanted instance of the object
you'd have to collect them in a variable and expose that variable like:
var theInstance = new myClass();
return { theInstance : theInstance };
It would now be available outside myApp as myApp.theInstance
You can also use a more basic wrapping scheme:
var myApp = {
myClass: function(){
//if we want to call another function in myApp we have to do it like so:
myApp.publicFunction();
},
publicFunction: function(){},
someString: "this is a string"
};
There myApp is just an object literal containing your functions etc. The main difference is that EVERYTHING in myApp can be accessed from outside via myApp.name or myApp[name];

Any Other Ideas for prototyping

I've used Douglass Crockford's Object.beget, but augmented it slightly to:
Object.spawn = function (o, spec) {
var F = function () {}, that = {}, node = {};
F.prototype = o;
that = new F();
for (node in spec) {
if (spec.hasOwnProperty(node)) {
that[node] = spec[node];
}
}
return that;
};
This way you can "beget" and augment in one fell swoop.
var fop = Object.spawn(bar, {
a: 'fast',
b: 'prototyping'
});
In English that means, "Make me a new object called 'fop' with 'bar' as its prototype, but change or add the members 'a' and 'b'.
You can even nest it the spec to prototype deeper elements, should you choose.
var fop = Object.spawn(bar, {
a: 'fast',
b: Object.spawn(quux,{
farple: 'deep'
}),
c: 'prototyping'
});
This can help avoid hopping into an object's prototype unintentionally in a long object name like:
foo.bar.quux.peanut = 'farple';
If quux is part of the prototype and not foo's own object, your change to 'peanut' will actually change the protoype, affecting all objects prototyped by foo's prototype object.
But I digress... My question is this. Because your spec can itself be another object and that object could itself have properties from it's prototype in your new object - and you may want those properties...(at least you should be aware of them before you decided to use it as a spec)...
I want to be able to grab all of the elements from all of the spec's prototype chain, except for the prototype object itself... This would flatten them into the new object.
Currently I'm using...
Object.spawn = function (o, spec) {
var F = function () {}, that = {}, node = {};
F.prototype = o;
that = new F();
for (node in spec) {
that[node] = spec[node];
}
return that;
};
I use it for every object I prototype, but because i use it so much, I'm looking to hone it down to the best possible.... I would love thoughts and suggestions...
If I've understood your question properly, you're asking how to use an approach like the one you have provided, but still be able to access prototype properties when they are overridden by the spec?
One way to get around the problem of inaccessible (overridden) prototype properties is to add them to the object along with the spec, but namespace them.
This example show how you could add the overridden to the object by prefixing it with an underscore. Put your namespacing of choice in it's place! (for example, you could use a 'super' property on the object)
Object.spawn = function (o, spec) {
var F = function () {}, that = {}, node = {};
F.prototype = o;
that = new F();
for (node in spec) {
if("undefined" !== typeof o[node]) {
that['_' + node] = o[node];
}
that[node] = spec[node];
}
return that;
};

Categories

Resources