Javascript 'this' value changing, but can't figure out why - javascript

I'm a total Javascript newb, and I'm trying to wrap my head around OLN. What I'm encountering is that, when calling an object method from another method on the same object, the value of local value of 'this' in the called method is changing. Here's my code:
var generator = {
generateForLevelSkillAndCount : function(level, skill, count) {
var functionCall = this['generate_' + level + '_' + skill];
return functionCall(count);
},
generate_0_4 : function(count) {
return this.generate_generic_dots(count, 3);
},
generate_generic_dots : function(count, maxDots) {
/* do cool stuff and return it */
}
};
So, I call generator.generateForLevelSkillAndCount(0, 4, 20) and it works properly, calling generate_0_4(count). However, this is where it fails, with Chrome's Javascript console telling me "Uncaught TypeError: Object [object DOMWindow] has no method 'generate_generic_dots'."
I know enough to know that the problem is that the value of this in generate_0_4 is a DOMWindow object, rather than generator (which is what this is pointing to in generateForSkillLevelAndCount but I can't figure out why that would possibly be happening.
Update: I updated the example code per CMS's suggestion to get rid of eval, but the same error is being returned, so it's not just an eval bug.

In JavaScript, the context object (this) is set to the "global object" (window, in browsers) unless the method is accessed as an object property. Therefore:
var foo = { bar: function() { alert(this.baz); }, baz: 5 };
var bar = foo.bar;
var baz = 3;
foo.bar(); // alerts 5, from foo
foo["bar"](); // alerts 5, from foo
bar(); // alerts 3, from the global object
Note that all three function calls are to the exact same function!
So, in your code, you're assigning the desired method to functionCall and calling it directly, which causes the function to use window as its context object. There are two ways around this: access the method as an object property or use .call() or .apply():
function generateForLevelSkillAndCount1(level, skill, count) {
return this['generate_' + level + '_' + skill](count);
}
function generateForLevelSkillAndCount2(level, skill, count) {
var functionCall = this['generate_' + level + '_' + skill];
return functionCall.call(this, count);
}

First of all, I would encourage you to avoid eval where you don't need it, for example, in your fist function:
//...
generateForLevelSkillAndCount : function(level, skill, count) {
var functionCall = this['generate_' + level + '_' + skill];
return functionCall(count);
},
//...
You can use the bracket notation property accessor instead eval, it's unnecessary in this case.
Now, I guess you are trying your code on the Chrome's Console, and eval is failing because the console has a bug, when eval is invoked from a FunctionExpression (such as generateForLevelSkillAndCount), the evaluated code uses the Global context for its Variable Environment and Lexical Environment.
See this answer for more information on this bug.
Edit: After re-reading your code, the problem happens because you lose the base object reference when you assign the function to your functionCall variable, you can either:
Invoke the function directly, without using that variable:
//...
generateForLevelSkillAndCount : function(level, skill, count) {
this['generate_' + level + '_' + skill](count);
},
//...
Or still use your variable, but persist the this value:
//...
generateForLevelSkillAndCount : function(level, skill, count) {
var functionCall = this['generate_' + level + '_' + skill];
return functionCall.call(this, count);
},
//...
More info on this...

You can control the execution context of the method call by using call():
var generator = {
generateForLevelSkillAndCount : function(level, skill, count) {
return this['generate_' + level + '_' + skill].call(this, count);
},
generate_0_4 : function(count) {
return this.generate_generic_dots.call(this, count, 3);
},
generate_generic_dots : function(count, maxDots) {
/* do cool stuff and return it */
}
};

I'm as baffled as you are, but have you considered checking out what happens if you call generate_0_4 explicitly, instead of parsing it through eval()?

When you call generate_0_4 dynamically (using an implicit to_string()) it is returned to generateForLevelSkillAndCount as an ad-hoc function. Because it's in Window scope rather than Object scope, it can't reference this, and the internal call fails because this doesn't exist in that context.
Here's how to see what's happening:
generate_0_4 : function(count) {
throw(this);
return this.generate_generic_dots(count, 3);
},
With generator.generateForLevelSkillAndCount(0, 4, 1); you get [object Window] or [object DOMWindow].
With generator.generate_0_4(1); you get what you're expecting (and what works): [object Object] or #<an Object>.

This is a feature of Javascript: the value of this will depend on the object from which the function was called, not where it was defined. (Which makes sense when functions are first-class objects themselves.) this in most other contexts refers to the window object.
There are two common workarounds, using a wrapper function:
function bind(func, obj) {
return function() {
func.apply(obj, arguments);
}
}
or using a closure:
var self = this;
function generate_blah() {
// use self instead of this here
}
In your case, though, simply replacing
var functionCall = this['generate_' + level + '_' + skill];
return functionCall(count);
with
this['generate_' + level + '_' + skill](count);
would do the trick.

Related

Javascript bind to object

function Developer(skill) {
this.skill = skill;
this.says = function() {
alert(this.skill + ' rocks!');
}
}
var john = new Developer('Ruby');
var func = john.says;
func();
I tried this example when I do this I get the following message undefined rocks! instead of Ruby rocks!.
can u explain why is that.
Function Execution Context and the this Keyword
JavaScript functions have an execution context at invocation time such that the this keyword is bound to the object they are invoked from. If you call john.says() the execution context of the function would have a this keyword that points to john. If you instead assign a global variable func to the method says found on the object john you have changed the execution context to the global object. When you invoke the func function, this dereferences to window (or undefined*) and since window.skill is undefined, says will coerce that value into a string to concatenate it with the string ' rocks!'.
How to guarantee execution context using bind
You can instead bind a copy of the function to an object (effectively locking it's context reference):
var func = john.says.bind(john);
How to guarantee execution context using a closure
Alternately you can close over the relevant bits by using a closure in your constructor:
function Developer(skill){
var _this = this; // we keep a reference here
this.skill = skill;
this.says = function(){
alert(_this.skill + ' rocks!');
// when invoked _this refers to the context at construction
}
return this;
}
How to guarantee a value using a closure
You could just reference the skill value directly from the method and so not need the context at all:
function Developer(skill){
// because skill is defined in this context, says will refer to this context
// to get the value of the skill variable.
this.says = function(){
alert(skill + ' rocks!');
}
}
How to guarantee an execution context at invocation time using call and apply
The final options are to to invoke the method with the context you want at invocation time:
func.call(john /*, optional arguments... */);
func.apply(john /*, optional arguments as an array */);
How to use prototypes to allow the dynamic execution context to set the right this
If we want to reuse a method between object instances or types but have the right execution context when invoked we can use the prototype property.
function Developer(skill){
this.skill = skill;
this.says();
}
Developer.prototype.says = function(){
alert(this.skill + ' rocks!');
}
var john = new Developer("Ruby"); // alert("Ruby rocks!")
var tony = new Developer("JavaScript"); // alert("JavaScript rocks!")
More reading:
Specification for execution contexts: http://ecma-international.org/ecma-262/5.1/#sec-10.3
MDN Docs about this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FOperators%2Fthis
* "use strict" activates a special strict mode representing the future of JavaScript. This special strict executing environment will not resolve to the global object when a context has not been set, instead resolving this to the appropriately scoped value undefined.
Let's illustrate what is happening, by actually returning the object:
function Developer(skill) {
this.skill = skill;
this.says = function() {
alert(this.skill + ' rocks!');
}
return this; //this is basically { skill: skill, says: function() {} }
}
var john = new Developer('Ruby');
var func = john.says; // all it knows is the function() part, has no knowledge of skill
func(); //undefined rocks!
Ok, so why are we getting undefined? Well, once we rip out the function, it no longer has context - it doesn't know what this is. If we were to do:
func.apply(john);
Then we are passing in John as the this parameter. A workaround, it to pass the value in to the function when we create it:
function Developer(skill) {
this.skill = skill;
this.says = function(skill) { //<--calling this function
return function() { //<--returning this function
alert(skill + ' rocks!');
};
}(this.skill); //<--passing in the value here
return this;
}
When you call a function, this will refer to the object on which the function was called.
When you say
var func = john.says;
you are just getting the function object (it doesn't remember the object on which it is defined).
When you invoke the function, like this
func();
there is no current object, on which func is invoked. So, by default, JavaScript assigns the global object(window object in browser) as this. Since, skill is not defined in the global object, undefined is returned.
Note: In Strict mode, this will be undefined, if there is no current object.
That is why we have to explicitly bind the object to the function, like this
func.bind(john)();
Function.prototype.bind will return a new function, which is bound to john. So, when you invoke this this will refer john.
Because func() only has the function and not any other related info.(this.skill)
The context is lost. this referred to the john when the says() is called.
But now, when you call func() this refers to the window. Therefore this.skills will return undefined unless you have a global variable of the same name.
the fastest way to keep the above pattern is to bind this direct to the method this.says = function () { ... }.bind(this);:
function Developer(skill) {
this.skill = skill;
this.says = function () {
alert(this.skill + ' rocks!');
}.bind(this);
}
var john = new Developer('Ruby');
var func = john.says;
func(); // Ruby rocks!
try this
function Developer(skill) {
this.skill = skill;
this.says = function() {
alert(this.skill + ' rocks!');
}
}
var john = new Developer('Ruby');
john.says();
working demo
http://jsfiddle.net/ZENtL/

Javascript function object with this reference to itself

I can't seem to find an example of what I'm trying to achieve, although I'm sure it has been done many times before...
I want to create an object which will have a set of properties and member functions but that I can also call directly. In the same way the jQuery object allows you to call $("selector") or $.method(...)
Here's a slimmed down version of what I'm trying to achieve :
var foobar = function(param) {
return "FOO : " + this.lookup(param);
}
foobar.vals = {
1: "one",
2: "two"
};
foobar.lookup = function (param) {
return "BAR : " + this.vals[param];
}
foobar.lookup("1")
// returns "BAR : one"
foobar("1")
// returns error since 'this' points to global scope
// I'd want it to return "FOO : BAR : one"
I've also tried various approaches with function prototype but can't seem to find a method which gives me everything I want...
var foobar = function(param) {
return "FOO : " + foobar.lookup(param);
}
will return you what you want
To understand this, maybe you should take a look at the basics of JavaScript. What are functions how to instanciate an object and what are objects...
To get something like JQuery this is not very difficult, the JQuery main object is simply a function which also has "static" functions.
to declare a variable as function you do
var myFunc = function(){};
to use the variable and extend it with static stuff you simply assign it via
myFunc.staticFunc = function(){};
this doesn't mean that myFunc.staticFunc can be accessed with this in any instance of myFucn because you didn't add the function to the prototype...
To define a class like object which can be instanciated you also define it as function and then extend the prototype. Prototype is your class definition which is used to construct the object's instance:
myFunc = function(){
// ctor
this.lala = "blub";
} ;
myFunc.prototype.objectFunc = function() {
return this.lala;
}
now the object myFunc has a function objectFunc. I have to initialize it with new...
alert(new myFunc().objectFunc());
instances can access itself with this...
To do something like jquery you'll have to do some tricks. Your global variable must be a function which returns an instance of your "real" object, which can implement whatever...
Then you can call your variable as if it is a function, e.g. myFunc()...
Hope the following example makes it more clear how this works: (can be found on jsfiddle)
(function ($) {
var myRealObj = function (outId, printText) {
this.text = printText;
$("#" + outId).append("<li>" + this.text + "</li>");
};
myRealObj.prototype.objectFunc = function () {
return this.lala
};
var myFunc = function (out, txt) {
return new myRealObj(out, txt);
};
myFunc.someFunc = function () {
myFunc("out", "myFunc.someFunc got called");
};
myFunc.static = {};
myFunc.static.someFunc = function () {
myFunc("out", "myFunc.static.someFunc got called");
};
window.$$ = myFunc;
})($);
$$("out", "test obj function");
$$.someFunc();
$$.static.someFunc();
You could add:
foobar = foobar.bind(foobar);
to make the variable refer to a bound version of the function. Any call to the (updated) "foobar" would have this bound to the original function object. You'd also have to mirror the properties if you wanted to get at them directly, which is sort-of a mess.
In the jQuery implementation, there's a separate internal function that handles the basic routing of the master $() function.
Note also that the "global" $.whatever() functions don't really have much to do with the set of methods supported by jQuery instances (objects returned from the $() function). The $ object just serves as a namespace so that those global utilities don't pollute the global (window) namespace.
you declare var foobar = function(param) {... in the global scope so this will always be a window

Method vs Functions, and other questions

With respect to JS, what's the difference between the two? I know methods are associated with objects, but am confused what's the purpose of functions? How does the syntax of each of them differ?
Also, what's the difference between these 2 syntax'es:
var myFirstFunc = function(param) {
//Do something
};
and
function myFirstFunc(param) {
//Do something
};
Also, I saw somewhere that we need to do something like this before using a function:
obj.myFirstFunc = myFirstFunc;
obj.myFirstFunc("param");
Why is the first line required, and what does it do?
Sorry if these are basic questions, but I'm starting with JS and am confused.
EDIT: For the last bit of code, this is what I'm talking about:
// here we define our method using "this", before we even introduce bob
var setAge = function (newAge) {
this.age = newAge;
};
// now we make bob
var bob = new Object();
bob.age = 30;
// and down here we just use the method we already made
bob.setAge = setAge;
To answer your title question as to what is the difference between a 'function' and a 'method'.
It's semantics and has to do with what you are trying to express.
In JavaScript every function is an object. An object is a collection of key:value pairs. If a value is a primitive (number, string, boolean), or another object, the value is considered a property. If a value is a function, it is called a 'method'.
Within the scope of an object, a function is referred to as a method of that object. It is invoked from the object namespace MyObj.theMethod(). Since we said that a function is an object, a function within a function can be considered a method of that function.
You could say things like “I am going to use the save method of my object.” And "This save method accepts a function as a parameter.” But you generally wouldn't say that a function accepts a method as a parameter.
Btw, the book JavaScript Patterns by Stoyan Stefanov covers your questions in detail, and I highly recommend it if you really want to understand the language. Here's a quote from the book on this subject:
So it could happen that a function A, being an object, has properties and methods, one of which happens to be another function B. Then B can accept a function C as an argument and, when executed, can return another function D.
There is a slight difference -
Method : Method is a function when object is associated with it.
var obj = {
name : "John snow",
work : function someFun(paramA, paramB) {
// some code..
}
Function : When no object is associated with it , it comes to function.
function fun(param1, param2){
// some code...
}
Many answers are saying something along the lines that a method is what a function is called when it is defined on an object.
While this is often true in the way the word is used when people talk about JavaScript or object oriented programming in general (see here), it is worth noting that in ES6 the term method has taken on a very specific meaning (see section 14.3 Method Definitions of the specs).
Method Definitions
A method (in the strict sense) is a function that was defined through the concise method syntax in an object literal or as a class method in a class declaration / expression:
// In object literals:
const obj = {
method() {}
};
// In class declarations:
class MyClass {
method() {}
}
Method Specificities
This answer gives a good overview about the specificities of methods (in the strict sense), namely:
methods get assigned an internal [[HomeObject]] property which allows them to use super.
methods are not created with a prototype property and they don't have an internal [[Construct]] method which means that they cannot be called with new.
the name of a method does not become a binding in the method's scope.
Below are some examples illustrating how methods (in the strict sense) differ from functions defined on objects through function expressions:
Example 1
const obj = {
method() {
super.test; // All good!
},
ordinaryFunction: function ordinaryFunction() {
super.test; // SyntaxError: 'super' keyword unexpected here
}
};
Example 2
const obj = {
method() {},
ordinaryFunction: function ordinaryFunction() {}
};
console.log( obj.ordinaryFunction.hasOwnProperty( 'prototype' ) ); // true
console.log( obj.method.hasOwnProperty( 'prototype' ) ); // false
new obj.ordinaryFunction(); // All good !
new obj.method(); // TypeError: obj.method is not a constructor
Example 3
const obj = {
method() {
console.log( method );
},
ordinaryFunction: function ordinaryFunction() {
console.log( ordinaryFunction );
}
};
obj.ordinaryFunction() // All good!
obj.method() // ReferenceError: method is not defined
A method is a property of an object whose value is a function. Methods are called on objects in the following format: object.method().
//this is an object named developer
const developer = {
name: 'Andrew',
sayHello: function () {
console.log('Hi there!');
},
favoriteLanguage: function (language) {
console.log(`My favorite programming language is ${language}`);
}
};
// favoriteLanguage: and sayHello: and name: all of them are proprieties in the object named developer
now lets say you needed to call favoriteLanguage propriety witch is a function inside the object..
you call it this way
developer.favoriteLanguage('JavaScript');
// My favorite programming language is JavaScript'
so what we name this: developer.favoriteLanguage('JavaScript');
its not a function its not an object? what it is? its a method
Your first line, is creating an object that references a function. You would reference it like this:
myFirstFunc(param);
But you can pass it to another function since it will return the function like so:
function mySecondFunction(func_param){}
mySecondFunction(myFirstFunc);
The second line just creates a function called myFirstFunc which would be referenced like this:
myFirstFunc(param);
And is limited in scope depending on where it is declared, if it is declared outside of any other function it belongs to the global scope. However you can declare a function inside another function. The scope of that function is then limited to the function its declared inside of.
function functionOne(){
function functionTwo(){}; //only accessed via the functionOne scope!
}
Your final examples are creating instances of functions that are then referenced though an object parameter. So this:
function myFirstFunc(param){};
obj.myFirst = myFirstFunc(); //not right!
obj.myFirst = new myFirstFunc(); //right!
obj.myFirst('something here'); //now calling the function
Says that you have an object that references an instance of a function. The key here is that if the function changes the reference you stored in obj.myFirst will not be changed.
While #kevin is basically right there is only functions in JS you can create functions that are much more like methods then functions, take this for example:
function player(){
this.stats = {
health: 0,
mana: 0,
get : function(){
return this;
},
set : function( stats ){
this.health = stats.health;
this.mana = stats.mana;
}
}
You could then call player.stats.get() and it would return to you the value of heath, and mana. So I would consider get and set in this instance to be methods of the player.stats object.
A function executes a list of statements example:
function add() {
var a = 2;
var b = 3;
var c = a + b;
return c;
}
1) A method is a function that is applied to an object example:
var message = "Hello world!";
var x = message.toUpperCase(); // .toUpperCase() is a built in function
2) Creating a method using an object constructor. Once the method belongs to the object you can apply it to that object. example:
function Person(first, last, age, eyecolor) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eyecolor;
this.name = function() {return this.firstName + " " + this.lastName;};
}
document.getElementById("demo").innerHTML = person.fullName(); // using the
method
Definition of a method: A method is a property of an object that is a function. Methods are defined the way normal functions are defined, except that they have to be assigned as the property of an object.
var myFirstFunc = function(param) {
//Do something
};
and
function myFirstFunc(param) {
//Do something
};
are (almost) identical. The second is (usually) just shorthand. However, as this jsfiddle (http://jsfiddle.net/cu2Sy/) shows, function myFirstFunc will cause the function to be defined as soon as the enclosing scope is entered, whereas myFirstFunc = function will only create it once execution reaches that line.
As for methods, they have a this argument, which is the current object, so:
var obj = {};
obj.func = function( ) {
// here, "this" is obj
this.test = 2;
}
console.log( obj.test ); // undefined
obj.func( );
console.log( obj.test ); // 2
The exact syntax you showed is because you can also do this:
function abc( ) {
this.test = 2;
}
var obj = {};
obj.func = abc;
obj.func( ); // sets obj.test to 2
but you shouldn't without good reason.
ecma document
4.3.31method :
function that is the value of a property
NOTE When a function is called as a method of an object, the object is
passed to the function as its this value.
It is very clear: when you call a function if it implicitly has a this (to point an object) and if you can't call the function without an object, the function deserves to name as method.

The reason to use JS .call() method?

I'm interested what's the reason to have call() method in JS. It seems it duplicates usual method of calling this.
For example, I have a code with call().
var obj = {
objType: "Dog"
}
f = function(did_what, what) {
alert(this.objType + " " + did_what + " " + what);
}
f.call(obj, "ate", "food");
The output is "Dog ate food". But the same result I can get assigning the function to the object.
var obj = {
objType: "Dog"
}
f = function(did_what, what) {
alert(this.objType + " " + did_what + " " + what);
}
obj.a = f;
obj.a("ate", "food");
The result is the same. But this way is more understandable and convenient to use. Why call() is needed?
call is used when you want to control the scope that will be used in the function called. You might want the this keyword to be something else than the scope you assigned the function to, in those cases you can use call or apply to call the function with your own scope.
F.ex, it also allows you to call utility methods outside the scope, like when using "private" functions:
var obj = (function() {
var privateFn = function() {
alert(this.id);
}
return {
id: 123,
publicFn: function() {
privateFn.call(this);
}
};
}());
obj.publicFn();
In the example above, privateFn is not exposed in obj but it can still be constructed as if it was a part of the public scope (using this in the same way).
2017 Update
All functions by way of Function.prototype have the .call method. The reason to use .call() is to specify what the variable "this" refers to.
MDN specifies:
The call() method calls a function with a given this value and
arguments provided individually.
Consider the following:
function x() {
return this;
}
x()
In strict mode x() returns undefined in non strict mode it returns the Global object, Window in a browser context.
Example with .call() we tell it what "this" refers to:
function x() {
return this;
}
var obj = {
myName : 'Robert',
myLocation : 'Earth'
}
x.call(obj);
Result: {myName: "Robert", myLocation: "Earth"}. In the above example we are specifying the obj object as the value of this inside the function x()
It can be used to emulate inheritance in OOP.
Example:
var Robert = {
name: "Robert Rocha",
age: 12,
height: "5,1",
sex: "male",
describe: function() {
return "This is me " + this.name + " " + this.age + " " + this.height + " " + this.sex;
}
};
Lets say that the above is a master object(prototype) and you want to inherit the function describe in another object:
var Richard = {
name: "Richard Sash",
age: 25,
height: "6,4",
sex: "male",
}
The Richard object does not have the describe function and you want to simply inherit ,so to speak, the function. You would do it like so:
console.log( Robert.describe.call( Richard ) );
Output: This is me Richard Sash 25 6,4 male
You would probably use the second way in your example, but sometimes you want to use one object's functions on another object. An example would be using Array methods on Array-like objects like NodeLists
var el = document.getElementById("foo");
[].forEach.call(el.children, function(child, index) {
//Iterate over an element's children, performing an action on each one
});
It's to do with the concept of a first class function. Basically languages like Javascript allow you to treat functions as things their own right. Functions can be stored in variables or passed to other functions.
call() provides a way to execute a free standing function not attached to any other object.
After I read this , I understand why.
A common mistake for new JavaScript programmers is to extract a method from an object, then to later call that function and expect it to use the original object as its this (e.g., by using the method in callback-based code).
Without special care, however, the original object is usually lost. Creating a bound function from the function, using the original object, neatly solves this problem:
this.x = 9; // 'this' refers to global 'window' object here in a browser
const module = {
x: 81,
getX: function() { return this.x; }
};
module.getX();
// returns 81
const retrieveX = module.getX;
retrieveX();
// returns 9; the function gets invoked at the global scope
// Create a new function with 'this' bound to module
// New programmers might confuse the
// global variable 'x' with module's property 'x'
const boundGetX = retrieveX.bind(module);
boundGetX();
// returns 81
The origin is here

The Javascript "apply" function doesn't exist in window.external extension object

I'm using javascript extension (AKA window.external) on IE8 (might as well be any IE version) to expose certain functions.
I'm trying to call the apply function, which is (supposed to be, according to here) natively embedded in every JS function, on a window.external object's function, but the browser keep throwing exception that the apply function doesn't exist for that function.
For example, this code works:
function onDataReceived(url, success, status, data, errorMessage) {
alert(onDataReceived);
}
function innerTest() {
alert(arguments[0] + ", " + arguments[1]);
}
function outerTest() {
innerTest.apply(null, arguments);
}
outerTest("hello", "world");
// alerts "hello, world"
But This code throws exception:
function outerTest() {
window.external.innerTest.apply(null, arguments); // <-- exception
}
outerTest("hello", "world");
Bottom line is - I need to pass an unknown number of arguments to the external function, and so far i've reached a dead end...
Any ideas?
EDIT:
I accepted Mike Samuel's answer since (as far as i understand) the apply function doesn't exist in the window.external object, because it's not a native javascript object.
What Mike suggested as the "worst case" is what I ended up doing, for the moment.
thanks
If window.external is a host object, or from some extension mechanism that doesn't want its prototype exposed to page logic, then it may be a function but may not have the usual call and apply members. Luckily, you can call call and apply apply:
Function.prototype.apply.call(window.external, null, [argumentsToExtension])
or to be really meta,
Function.prototype.apply.apply(window.external, [null, [argumentsToExtension]])
where null is what is passed as the value of this which should be interpreted as window by the usual call/apply rules.
EDIT:
If that doesn't work, you can always fall back to the triangle of hackery.
function triangleOfHackery(obj, methodName, args) {
switch (args.length) {
case 0: return obj[methodName]();
case 1: return obj[methodName](args[0]);
case 2: return obj[methodName](args[0], args[1]);
case 3: return obj[methodName](args[0], args[1], args[2]);
...
}
}
Actually there is a general solution. A simple example is like below:
function invoke (funcName, ...args) {
var argStr = '';
for (let i in args) {
argStr += ', this.args[' + i + ']';
}
return (new Function('return window.external[\'' + funcName + '\'](' + argStr.substring(2) + ')')).bind({args})();
}
// define a circular structure which cannot be stringified:
const a = {}; a.a = a;
// make a call:
invoke('foo', 10, 'abc', new Date(), function(){}, a);
// equivalent to:
window.external.foo(10, 'abc', new Date(), function(){}, a);
As you can see, there is no necessary to keep the parameters serializable.
The main idea is properly specifying the context, and mounting all the parameters onto that context.
I used ES6 syntax here just to make the code easier to understand. And of course, to make it work in IE8, you can translate it to ES5-compatible syntax and you might have ES5 shim included first to polyfill Function.prototype.bind. OR, you just never need bind at all:
EDIT: ES5 compatible version:
function invoke (funcName) {
var argStr = '';
for (var i=1; i<arguments.length; i++) {
argStr += ', this.args[' + i + ']';
}
return {
args: arguments,
func: new Function('return window.external[\'' + funcName + '\'](' + argStr.substring(2) + ')')
}.func();
}

Categories