1
\$\begingroup\$

I was try to reimplement the native call method in JavaScript. Here we need to handle the object which we pass in the call function and additional params. I handle the additional params using eval() method. Is there any more efficient way to write it?

var name = {
    name: "JavaScript", 
  version: "6",
}   

function printName(location, district){
    alert(this.name + ", " + this.version + ", " + location + ", " + district);
}

Function.prototype.myCall = function(...args){
  var param = args.slice(1),
      paramLength = param.length, 
      paramString = "JSONarg.myFun(";
  for(var i = 1; i <= paramLength; i++){
    paramString += "args["+i+"],";
  }
  paramString += ")";

  if(typeof this != 'function'){
    throw new Error(this + " is not a Function");
  }

  var JSONarg =  {
    ...args[0],
    myFun: this
  }
  return eval(paramString);
}

printName.myCall(name, "Chrome", "browser");
\$\endgroup\$
  • \$\begingroup\$ Hey this is an interesting post! It appears that the code above shows an alert with the message undefined, undefined, Chrome, browser - is that what you expected, or was there something else you intended to have it do? \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Apr 29 at 17:01
2
\$\begingroup\$

Review

To be quick I will just add comments to the code with some alternatives

// if you add to a built in prototype use Object.defineProperty or Object.defineProperties
// using the default settings
// Function.prototype.myCall = function(...args){ // use 2 parameters
Function.prototype.myCall = function(name, ...args){ 

  // var param = args.slice(1),  // is now second parameter 

  // Should be a constant
  //    paramLength = param.length, 
  const  paramLength = args.length;

  // not really a param string. Rather its a eval function
  //  paramString = "JSONarg.myFun(";
  var evaledFunc = "JSONarg.myFun(";

  // The variable if defined in the loop should be let
  // Now starts at index 0  
  //for(var i = 1; i <= paramLength; i++){ 
  for (let i = 0; i < paramLength; i++) {  // add space for ( and ) {

    evaledFunc += "args[" + i + "],";  // use spaces between operators
  }
  evaledFunc += ")";

  // This function is a prototype of Function. You would have to go hard 
  // to have the next statement be true. Just let the native exception 
  // generate the error
  /*if(typeof this != 'function'){
    throw new Error(this + " is not a Function");
  }*/

  // Does not change, thus should be a const
  // var JSONarg =  {
  const JSONarg =  {  // would be best defined at the top of the function

  // args[0] is now the param name
  //  ...args[0],
      ...name,
    myFun: this 
  // }  // there should be a ; here
  };
  return eval(evaledFunc);
}

Removing the comments and some minor mods we get...

function myCaller(name, ...args) { 
    const JSONarg =  {...name, myFun: this};
    var func = "JSONarg.myFun(";
    for (let i = 0; i < args.length; i++) {
        func += "args[" + i + "],"; 
    }
    func += ")";
    return eval(func);
}

Rather than eval you could also have use new Function

Object.defineProperty

To prevent problems (myCall showing up in strange places) and as it is in a very often used native type.( All functions get the new property). enumerable = true can slow every function call just a little depending on JS engine and version.

Add to prototype safely using Object.defineProperty. Default accessor configurable, enumerable, writable are false

if (! Function.prototype.myCall) {
    Object.defineProperty(Function.prototype, myCall, {value: myCaller});
}

Function.bind

With all that said there is a much easier way to do the same. Function.bind will bind a functions with an object (sets the functions this)

function printName(location, district){
    alert(this.name + ", " + this.version + ", " + location + ", " + district);
}
printName.bind({...name})( "Chrome", "browser");

// or just use original name obj

printName.bind(name)( "Chrome", "browser");

The bound function is a new reference, the original function remains unbound

const printNameBound = printName.bind(name);

printNameBound("Chrome", "browser");

printName("Chrome", "browser"); // will fail as not bound to name object

Function.call or Function.apply

You can also use Function.call or Function.apply to do the same. In this case there is no new reference to the function created. The binding is temporary.

printName.call(name, "Chrome", "browser"); 

// or

printName.apply(name, ["Chrome", "browser"]); // args as array
\$\endgroup\$
  • \$\begingroup\$ How can i use new Function instead of eval()? \$\endgroup\$ – Kallis Apr 30 at 4:55
  • \$\begingroup\$ @Kallis function myCaller(name, ...args) { return (new Function("name", "func", "args", "return ({...name, func})(...args);")(name, this, ...args);} \$\endgroup\$ – Blindman67 Apr 30 at 13:43
  • \$\begingroup\$ can you check this jsfiddle.net/gowtham25/m1gnLd6x/12 \$\endgroup\$ – Kallis Apr 30 at 17:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.