Avoid EVAL and pass THIS to function? - javascript

I've built a GUI which passes in a long JS Object as settings for an animation plugin. One of the settings allows for the user to call a function once an animation is complete. Unfortunately, it has to be passed to the JS Object as a string.
... [ 'functioncall()' ] ......
Inside my animation callback, I'm retrieving this string and trying to run it as a function.
First attempt works perfectly, but uses eval...
eval( OS.path_jscall[index].path_jscall[0][i] )
I've setup a more preferred approach like so:
var HookFunction=new Function( OS.path_jscall[index].path_jscall[0][i] );
HookFunction();
Both examples call the functioncall() function perfectly. I'm not sure how to pass (this) to the functioncall() though...
functioncall(obj){ console.log(obj); };
Everything keeps referring to the window. Any ideas? Thanks!

Assuming that HookFunction is the name, you can do either a call() or apply()
HookFunction.call(this,arg1,arg2,...,argN);
//or
HookFunction.apply(this,[arg1,arg2,...,argN]);
the basic difference of the 2 is that call() receives your "this" and an enumerated list of arguments after it, while apply() receives your "this" and an array of arguments

Use .call when calling your function. .call assigns the first parameter to the this variable.
var myFunction = function(arg1, arg2) {
alert(this);
}
myFunction.call(this, "arg1", "arg2");
Using your second example, you could do this:
HookFunction.call(this);

Related

Javascript passing this to functions

I'm not used to working with this and trying to make some simple functions pass it back and forth. I'm not quite sure what javascript is expecting, but I don't think I'm doing it right.
$(".search-result").each(function() {
var x = $(this).find('.past-positions ol').children()
console.log(x)
//this prints as expected
pastJobs(this)
// this does not
})
function pastJobs() {
var x = $(this).find('.past-positions ol').children()
console.log(x)
// this prints as undefined
}
I assume its possible to pass this to functions, but I don't think I'm doing it in the right way.
What am I doing wrong?
Try pastJobs.call(this) instead.
Actually, here pastJobs(this) you're passing the lexical context this as param rather than binding that context to the function.
You can use the function bind to achieve what you want:
pastJobs.bind(this)()
pastJobs(this) you are passing this as an argument
and you're function doesn't accept arguments function pastJobs(). so doing $(this) in pastJobs is really out of context.
you could call the function .call(this)/.apply(this), or bind() and then call it. (bind only binds this object but unlike apply or call doens't invoke the function.
keep in mind that call and apply takes arguments after this object in a different manner. The call() method takes arguments separately.
The apply() method takes arguments as an array.
you need something like
$(".search-result").each(function() {
var x = $(this).find('.past-positions ol').children()
console.log(x)
//this prints as expected
pastJobs.call(this);
// this does not
})

Passing arguments on top of those passed by trigger() in backbone.js

I have a method that passes up an argument in a nested view in Backbone, like such:
page.get('condition') ? this.trigger('someEvent', object) : this.trigger('someOtherEvent', object);
As I understand it, this code will pass object to 'someEvent' or 'someOtherEvent' as the first argument to the callback specified in the listener for that event.
My listener (in the parent view file) looks like:
this.parentView.on('someEvent', this.someFunction('extraArgument'), this);
By default, if the second parameter was just this.someFunction, I assume that the first argument to that function would be object.
My issue is that I want to pass 'extraArgument' in addition to the implicitly passed object.
As such, I structure the signature of this.someFunction like this:
someFunction: function(object, extraArg) {
...
}
However, debug statements within the function reveal that object is in fact the extraArgument I passed manually, and that extraArg is undefined in the scope of the function. How can I pass the extraArgument without overwriting the argument passed up from the child view?
When you say this:
this.parentView.on('someEvent', this.someFunction('extraArgument'), this);
you're calling this.someFunction right there when the argument list for this.parentView.on is being built. Then whatever this.someFunction('extraArgument') returns is bound as the callback for the 'someEvent' event. Your someFunction probably doesn't return a function so this on call won't do anything useful.
Sounds like you want to partial evaluate this.someFunction so that you create a new function that is just like this.someFunction but the first argument is always 'extraArgument'. Underscore offers _.partial for just that purpose:
partial _.partial(function, *arguments)
Partially apply a function by filling in any number of its arguments, without changing its dynamic this value.
So you'd say:
this.parentView.on(
'someEvent',
_(this.someFunction).partial('extraArgument'),
this
);
You can also use _.bind for this if you want to set the this along the way.

Why does not bind() or apply() work here, but call() does?

Take this very simple framework I am experimenting with (so that I can learn JavaScript's function prototype more in depth.)
(function(){
var app = {
ui: {
app: document.querySelector('.app'),
settings: document.querySelector('.settings'),
},
actions: 'click settings openSidebar'
,
functions: {
openSidebar: function(e){
console.log(this); // <- expected value of this is 'app.ui'
}
},
run: function(){
var a1 = this.actions.split("\n");
var a2 = this.actions.split(" ");
var self = this;
this.ui[a2[1]].addEventListener(a2[0], function(e){
app.functions.openSidebar.call(self.ui,e);
});
}
};
app.run();
})();
This works great. Output from console is:
Object {ui: Object, actions: "click settings openSidebar", functions: Object, run: function}
However, when I try to do it like this:
var self = this;
this.ui[a2[1]].addEventListener(a2[0], function(e){
app.functions.openSidebar(e);
}.bind(this));
The context of this in openSidebar() is openSidebar (aka bind has no effect). Console output:
Object {openSidebar: function}
However, when I try to use the apply function, like so:
app.functions.openSidebar.apply(self.ui,e);
It works fine (this in openSidebar is app.ui) EXCEPT that the argument (e) does not get passed, so e == undefined.
Here goes:
1. Why does not bind work (at all) in the first example?
2. Why does apply work without passing arguments (the e (event))?
And for added brownie points:
3. Why does call work as expected?
Why does not bind work (at all) in the first example?
It "works", it just doesn't do what you expect.
Inside your anon function this is indeed the value set by bind. However, when you then call a function that is also a property of an object (functions.openSidebar) then for that invocation this is automatically bound to that object inside the function (i.e. this === functions). The value of this from the parent context is never "inherited" down the call chain.
Why does apply work without passing arguments (the e (event))?
Because apply tries to pull out the arguments for the call from its own second argument e by treating it as an array. This means that its length property is checked first; your event object doesn't have a length so apply gets the undefined value produced and effectively behaves as if you had passed in an empty array. See the annotated ES5 reference for the details.
Why does call work as expected?
This is a strange question. Why would it not work? You are using it exactly like it's meant to be used.
You'll need to do this:
this.ui[a2[1]].addEventListener(a2[0], app.functions.openSidebar.bind(this));
bind returns you a new function with a manually set context of whatever you pass in. Because you're not using bind on the right function, you're calling your function with app.functions to the left of the invoked function, which henceforth is known as this inside the invoked function!
Apply takes arguments as an array, not named parameters...
I can't explain the third without saying that that is how call works!

.call() / .apply() with NO parameters VS simply calling a function with () parenthesis

I've seen it done differently in code out there, but is there any benefit or reason to doing a (blank params) .call / .apply over a regular () function execution.
This of course is an over-simplified example
var func = function () { /* do whatever */ };
func.call();
func.apply();
VERSUS just the simple parenthesis.
func();
Haven't seen any information on this anywhere, I know why call/apply are used when params are passed.
When you call a method with func();, this variable inside the method points to window object.
Where as when you use call(...)/apply(...) the first parameter passed to the method call becomes this inside the method. If you are not passing any arguments/pass null or undefined then this will become global object in non strict mode.
Yes, there is an important difference in some cases. For example, dealing with callbacks. Let's assume you have a class with constructor that accepts callback parameter and stores it under this.callback. Later, when this callback is invoked via this.callback(), suddenly the foreign code gets the reference to your object via this. It is not always desirable, and it is better/safer to use this.callback.call() in such case.
That way, the foreign code will get undefined as this and won't try to modify your object by accident. And properly written callback won't be affected by this usage of call() anyways, since they would supply the callback defined as an arrow function or as bound function (via .bind()). In both such cases, the callback will have its own, proper this, unaffected by call(), since arrow and bound functions just ignore the value, set by apply()/call().

What's a more secure alternative for eval() when I just want to call a function?

I know PHP has call_user_func, I was just wondering if JavaScript had something similar, where the method I want to call is, for example: object.set$fieldID($fieldValue)
I would rather not go through if/else/switch blocks just to execute one line of code properly.
If it helps, I am using jQuery.
object["set" + $fieldID]($fieldValue);
Some reading material for the above: Member Operators on MDC.
Some advanced methods include Function.prototype.call and Function.prototype.apply. The first is somehow equivalent to PHP's call_user_func() while the latter is somehow equivalent to PHP's call_user_func_array().
The difference between PHP's functions and JavaScript's is that JavaScript allows you to call methods of some object in the context of another object. This is done through the use of the first argument of call() and apply().
An equivalent for the above example, but using call() and apply() looks like this:
object["set" + $fieldID].call(object, $fieldValue);
object["set" + $fieldID].apply(object, [$fieldValue]);
The first argument must be object otherwise the method will be executed with the this pointer bound to the global object, window in the case of browsers.
#lonut is correct. More generally though, if you want to call functions that aren't a part of an explicit object (e.g.: global), you can call them on the window object:
var foo = function() { alert(1); };
window['foo']();
since all objects are contained in window.
Another example:
var object = function() { this.method = function() { alert(2); }; }
var instance = new object();
window['instance']['method']();

Categories