Understanding by making your own: JavaScript call, apply & bind
Tags: javascript
Published: 2019-09-23 05:52:30

It ultimately makes sense not to reinvent the wheel, but it's also of a way to Improve yourself by (re)creating things that were already existing. I'm writing this series to mainly improving my understanding of how things work in JavaScript's Standard built-in objects like `call`,  `apply`,  `bind`. 


##Function.prototype.call()

> The call() method calls a function with a given this value and arguments provided individually. -- MDN

[Mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call)

Initially, the method  `call`  invokes the function and allows you to pass comma-separated arguments.


#### Example from MDN
 
```javascript

function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(name, price) {
  Product.call(this, name, price);
  this.category = 'food';
}

function Toy(name, price) {
  Product.call(this, name, price);
  this.category = 'toy';
}

const food = new Food('cheese', 5)
console.log(food.name) //  cheese
console.log(food) //  {name: 'chees', price: 5, category: 'food'}

const fun = new Toy('robot', 40);
console.log(fun.name) //  robot

```

#### Custom example

```javascript

const add = (a, b) => a + b
console.log(add.call(null, 3, 8, 10)) // 11

```

Above examples, we can understand the basic functionality of the call method. 



- Call changes the `this` context of the caller, In the above example `Product.call` replaces the `this` from its original function body with the first argument of `call`, That is `Food`.
> Using the call to chain constructors for an object -- MDN


![call context](https://thepracticaldev.s3.amazonaws.com/i/smxdcojxpxn7z7x666zx.png)

- If call called with more than one arguments then in left to right order, starting with the second argument, pass each argument to the original function.
   - in our case `name` and `price`.

- The `call` should not make any side effect on the `this` object.

 > The thisArg value is passed without modification as `this` value. This is a change from Edition 3, where an undefined or null thisArg is replaced with the global object and ToObject is applied to all other values and that result is passed as the `this` value. Even though the thisArg is passed without modification, non-strict functions still perform these transformations upon entry to the function. -- Ecma

[Ecma-spec](https://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.call)


## Lets re-implement the call method.

```javascript

if(!Function.prototype.fauxCall){
    Function.prototype.fauxCall = function(context){
       context.fn = this;
       return context.fn();
    }
}


const food = new Food('cheese', 5)
console.log(food) //  expected {name: 'chees', price: 5, category: 'food'}

```

If we run the above code, we'll get 

![Call result](https://thepracticaldev.s3.amazonaws.com/i/83nqogy3i8gqo8y5hqra.png)

instead of 
```javascript

{name: 'chees', price: 5, category: 'food'}

```

Ok, we need to pass original arguments when we call `fn()`. Seems easy, but 🤔 how do we know how many arguments are coming from the original call? 

Here we  can use `arguments` it is **Array-like** object accessible inside the function, but still, we have a problem; remember `arguments` is not an `array` its an `object` that's why **Array-like** 

We can convert this `object` to `array` with `Array.from`[(more ways)](https://dzone.com/articles/js-array-from-an-array-like-object), then ignore the first argument by `Array.slice` from the second element.

```javascript
if(!Function.prototype.fauxCall){
    Function.prototype.fauxCall = function(context){
       const args = Array.from(arguments).slice(1);
       context.fn = this;
       return context.fn(...args);
    }
}

const food = new Food('cheese', 5)
console.log(food) //  expected {name: 'chees', price: 5, category: 'food'}

```

If we run the above code, we'll get

![Call result](https://thepracticaldev.s3.amazonaws.com/i/jgifge75si7va0tuzikb.png)

Ok looks good, but still, we can see the side effect. Get rid of the side effect we can use `delete` operator, however, even if we can delete this side effect `fn` property that we created we have one more problem; if `context` already has a property with the same name `fn`. In this case, should form the random key then assign it to context then we have to delete it.


```javascript
if(!Function.prototype.fauxCall){
    Function.prototype.fauxCall = function(context){
       const fnName =
    [...Array(10)].map(_ => ((Math.random() * 36) | 0).toString(36)).join`` ||
    {};
       const args = Array.from(arguments).slice(1);
       context[fnName]= this;
       const result = obj[fnName](...args); 
       delete obj[fnName];
       return result;
    }
}

const food = new Food('cheese', 5)
console.log(food) //  expected {name: 'chees', price: 5, category: 'food'}

```
If we run the above code, we'll get

![Call success](https://thepracticaldev.s3.amazonaws.com/i/62atb9zhv15llj4ayq0i.png)

Almost success, but if we call with null instead of the object we'll get an error.   

Remember our `add` function? if we want to `fauxCall` add function without `this` argument we'll get error

```javascript
const add = (a, b) => a + b;
console.log(add.fauxCall(null, 5, 6, 7)); // 11 :: 7 will ignore by add method

```

![Call error](https://thepracticaldev.s3.amazonaws.com/i/0ptf5sbhiwija9hwjff3.png)

It's because o we're trying to set a property to a `null` object, and we can fix it by guard function.

Also, add two more methods to check the existing property and assign new property instead of static `fnName` variable.

1. `getRandomKey` : this function generates and returns a random string each time.
2. `checkRandomKey` : this function takes two arguments; key and context (object) and checks this object already has the same key as property if-then recurse it with the new key, until finding a unique new property for the property name.

#### Complete implementation

```javascript
const isOBject = obj => {
    const type = typeof obj;
    return type === "function" || (type === "object" && !!obj);
};

const getRandomKey = () => {
    return (
    [...Array(10)].map(_ => ((Math.random() * 36) | 0).toString(36)).join`` ||
    {}
  );
};

const checkRandomKey = (key, obj) => (obj[key] === undefined) ? key : checkRandomKey(getRandomKey(), obj);

if(!Function.prototype.fauxCall){
    Function.prototype.fauxCall = function(_context) {
       const context = isOBject(_context) ? _context : {};
       const fnName = checkRandomKey(getRandomKey(), context);
       const args = Array.from(arguments).slice(1);
       context[fnName] = this;
       const result = context[fnName](...args);
       delete context[fnName];
       return result;
    };
}

function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(name, price) {
  Product.fauxCall(this, name, price);
  this.category = "food";
}
const add = (a, b) => a + b;

console.log(new Food("cheese", 5)); // {name: 'chees', price: 5, category: 'food'}
console.log(add.fauxCall(null, 5, 6, 7)); // 11 :: 7 will ignore by add method
```

---

## Function.prototype.apply()

> The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object). -- MDN

[Mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call)

Initially, the method  `apply`  invokes the function and allows you to pass an array or array-like arguments. Sound familiar? yes because `call` and `apply` almost doing the same thing only different is call accept comma-separated arguments while apply accepts array or array-like object as the argument.

In this case, everything that we did for the `call` is valid for `apply` except `args` part, now we know exactly which argument should go with the function call. 


```javascript
//... all call helper codes
if(!Function.prototype.fauxApply){
    Function.prototype.fauxApply = function(_context, _args) {
        const context = isOBject(_context) ? _context : {};
        const fnName = checkRandomKey(getRandomKey(), context);
        const args = _args.length ? _args : []
        context[fnName] = this;
        const result = context[fnName](...args);
        delete context[fnName];
        return result;
    };
}
const numbers = [5, 6, 7];

console.log(new Food("cheese", 5)); // {name: 'chees', price: 5, category: 'food'}
console.log(add.fauxApply(null, 5, 6, 7)); // 11 :: 7 will ignore by add method

```

---


## Function.prototype.bind()

> The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

Only different between call and bind is call invoke the function and returns the value but bind returns a new function with updated context.

So we can simply return a new function that calls `call` with arguments and context.

```javascript
//... all call helper codes
Function.prototype.fauxBind = function(_contetxt){
  const args = Array.from(arguments).slice(1);
  const self = this;
  return function(){
      //return self.fauxApply(_contetxt, args)
      return self.fauxCall(_contetxt, ...args) // either call or apply
  }
}
console.log(add.fauxBind(null, 4,7)());
```

CodeSandbox

[![Edit js-call-apply-bind](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/js-call-apply-bind-yh997?fontsize=14)


{% codesandbox yh997 %}


♥


This implementation here is one of many ways. The purpose of this simulation is only to get how `call` works under the hood. If you find any issue or typo please let me know.