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