Piszemy kolejny polyfill, tym razem dla funkcji call. Robimy to porządnie, wykorzystując typ Symbol – do dzieła!

Najpierw przykład call:

function greet(){
    console.log(`Hi my name is ${this.name} and Im ${this.age} years old`);
}

const obj = {
    name: 'John',
    age: 30
};

greet.call(obj);
//Hi my name is John and Im 30 years old

Teraz przykład z jakimś argumentem:

function greet(showAge=false){
    let msg = `Hi my name is ${this.name}`;
    if(showAge)
        msg += ` and Im ${this.age} years old`;
    console.log(msg);
}

const obj = {
    name: 'John',
    age: 30
};

greet.call(obj);
//Hi my name is John
greet.call(obj, true);
//Hi my name is John and Im 30 years old

Teraz rozpoczynamy zabawę:

function greet(showAge=false){
    let msg = `Hi my name is ${this.name}`;
    if(showAge)
        msg += ` and Im ${this.age} years old`;
    console.log(msg);
}

const obj = {
    name: 'John',
    age: 30
};

Function.prototype.myCall = function(obj, ...args){
    console.log(this);
}

greet.myCall(obj);
//function greet(showAge)

Skoro wiemy już, czym jest this (funkcją) to możemy tę funkcję przypisać do obiektu i wywołać z argumentami:

function greet(showAge=false){
    let msg = `Hi my name is ${this.name}`;
    if(showAge)
        msg += ` and Im ${this.age} years old`;
    console.log(msg);
}

const obj = {
    name: 'John',
    age: 30
};

Function.prototype.myCall = function(obj, ...args){
    obj.myFn = this;
    obj.myFn(...args);
}

greet.myCall(obj);
//function greet(showAge)

greet.myCall(obj, true);
//Hi my name is John and Im 30 years old

Wygląda, jakby działało. Zamieńmy prototyp Function na Object:

Object.prototype.myCall = function(obj, ...args){
    obj.myFn = this;
    obj.myFn(...args);
}

greet.myCall(obj);
//function greet(showAge)

greet.myCall(obj, true);
//Hi my name is John and Im 30 years old

[].myCall(obj, true);
//Uncaught TypeError: obj.myFn is not a function

Możemy to sobie przechwycić:

Object.prototype.myCall = function(obj, ...args){
    let fn=this;
    if(typeof fn !== "function"){
        throw new Error('Invalid function provided for binding.');
      }
    obj.myFn = this;
    obj.myFn(...args);
}

greet.myCall(obj);
//function greet(showAge)

greet.myCall(obj, true);
//Hi my name is John and Im 30 years old

[].myCall(obj, true);
//Uncaught Error: Invalid function provided for binding.

Albo wrócić do prototypu Function, ale zachować ten check:

Function.prototype.myCall = function(obj, ...args){
    let fn=this;
    if(typeof fn !== "function"){
        throw new Error('Invalid function provided for binding.');
      }
    obj.myFn = this;
    obj.myFn(...args);
}

Mamy inny, poważniejszy problem, jeżeli zamienimy clg na return:

function greet(showAge=false){
    let msg = `Hi my name is ${this.name}`;
    if(showAge)
        msg += ` and Im ${this.age} years old`;
    console.log(msg);
}

function greetRet(showAge=false){
    let msg = `Hi my name is ${this.name}`;
    if(showAge)
        msg += ` and Im ${this.age} years old`;
    return msg;
}

const obj = {
    name: 'John',
    age: 30
};

Function.prototype.myCall = function(obj, ...args){
    let fn=this;
    if(typeof fn !== "function"){
        throw new Error('Invalid function provided for binding.');
      }
    obj.myFn = this;
    obj.myFn(...args);
    
}

greet.myCall(obj, true);
//Hi my name is John and Im 30 years old

let result = greetRet.myCall(obj, true);
console.log(result);
//undefined

Da się to naprawić:

Function.prototype.myCall = function(obj, ...args){
    let fn=this;
    if(typeof fn !== "function"){
        throw new Error('Invalid function provided for binding.');
      }
    obj.myFn = this;
    let result = obj.myFn(...args);
    return result;
    
}

greet.myCall(obj, true);
//Hi my name is John and Im 30 years old

let result = greetRet.myCall(obj, true);
console.log(result);
//Hi my name is John and Im 30 years old

Jedyny problem, jaki teraz mamy, to fakt, że możemy nadpisać prawdziwą funkcję myFn, choć to wątpliwe:

const obj = {
    name: 'John',
    age: 30,
    myFn(){
        console.log("My fn");
    }
};

Function.prototype.myCall = function(obj, ...args){
    let fn=this;
    if(typeof fn !== "function"){
        throw new Error('Invalid function provided for binding.');
      }
    obj.myFn = this;
    let result = obj.myFn(...args);
    return result;
    
}
obj.myFn();
//My fn
greet.myCall(obj, true);
//Hi my name is John and Im 30 years old
obj.myFn();
//Hi my name is John

Tutaj z pomocą przyjdzie nam typ symbol:

const obj = {
    name: 'John',
    age: 30,
    myFn(){
        console.log("My fn");
    }
};

Function.prototype.myCall = function(obj, ...args){
    let fn=this;
    if(typeof fn !== "function"){
        throw new Error('Invalid function provided for binding.');
      }
    let myFn = Symbol('myFn');
    obj[myFn] = this;
    let result = obj[myFn](...args);
    return result;
    
}

obj.myFn();
//My fn
greet.myCall(obj, true);
//Hi my name is John and Im 30 years old
obj.myFn();
//My fn