手撕实现Call函数
简单分析一下要实现的功能
function test(a,b){
console.log(this,a,b)
return a + b
}
test.call({},1,2)
//输出 {} 1 2
利用基础知识实现最简单的call函数
首先我们都知道,call函数是写在Function
的原型中的,所以我们也要将自己的call函数,下面叫他myCall
函数吧,也写在原型中
Function.prototype.myCall = function(){
}
我们的myCall需要接收两类参数,一类是需要将this指向的对象,另一个是携带的参数。这里我们需要使用到我们ES6的方法,将参数分成两类
Function.prototype.myCall = function(thatObj,...args){
//前者是this要指向的对象,后者是携带的参数
}
ok下面我们就继续改造myCall
因为myCall是Function的原型函数,当我们用函数调用他时,他的this指向的是调用他的函数,所以我们可以为要指向的对象中添加一个属性保存原函数,这样,我们就可以改变this的指向啦
Function.prototype.myCall = function(thatObj,...args){
thatObj.fn = this
thatObj.fn(...args) //此时就将原函数的this指向了thatObj这个新对象,并将携带的参数传递回给原函数
//因为我们创建了新的属性,所以我们要将他删除
delete thatObj.fn
}
一个最简单的myCall就写好了
解决属性名重复和隐式的问题
因为thatObj.fn
的属性容易发生重名,即使属性名再复杂,也有重名的可能嘛。这时要利用我们ES6的Symbol来解决这个问题了
Function.prototype.myCall = function(thatObj,...args){
let key = Symbol('temp')
Object.defineProperties(thatObj,key,{
enumerable:true,
value:this
})
console.log(thatObj)
let result = thatObj[key](...args) //因为有可能有返回值,我们还要保留这个返回值后,再返回
delete thatObj.fn
return result
}
解决传进来的为null和undefined的问题
当我们传入的thatObj是null或者undefined时,我们将this指向window,但随着前端的发展,全局对象不只局限于浏览器了,同时还出现了Node的技术,所以我们将this指向globalThis。其余的我们用Object封装一下thatObj,防止他是Number等基础数据类型
Function.prototype.myCall = function(thatObj,...args){
thatObj = (thatObj === null || thatObj === undefined)?globalThis:Object(thatObj)
let key = Symbol('temp')
Object.defineProperties(thatObj,key,{
enumerable:true,
value:this
})
console.log(thatObj)
let result = thatObj[key](...args)
delete thatObj.fn
return result
}
完成体
function test(a,b){
console.log(this,a,b)
return a + b
}
test.call({},1,2)
Function.prototype.myCall = function(thatObj,...args){
thatObj = (thatObj === null || thatObj === undefined)?globalThis:Object(thatObj)
let key = Symbol('temp')
Object.defineProperties(thatObj,key,{
enumerable:true,
value:this
})
console.log(thatObj)
let result = thatObj[key](...args)
delete thatObj.fn
return result
}
test.myCall({},2,3)