首先声明一点,这里手写的函数跟js真正的函数的实现是不一样的,我们真正调用的apply,call,bind函数是会考虑很多边界情况的,我们这里只注重函数的核心实现逻辑,对边界问题并不做过多的处理,手写这些函数只是为了辅助理解这些函数,以达到加深印象的目的。
手写apply,call,bind函数怎么入手?
入手点:
(1)外壳:函数的声明
首先我们要从函数声明开始,了解这个函数的函数声明两个要素(参数,返回值),并进行模拟,这样可以做到使用相同的调用方式,获取到相同的返回值,做到函数外壳的一致性,让别人用我们的函数和使用js的函数获得使用上的一致性。
(2)逻辑:函数实现的功能
其次我们要做到功能实现的一致性,手写函数的目的就是为了模拟函数的功能实现,所以我们可以从原生函数实现的功能思考,思考它是怎么做到这样的功能的,然后进行模拟,达到与函数功能的一致性。
(3)函数的调用方式
最后我们还需要做到和原生函数一样的调用方式,这里需要做到一点,就是任何函数都是可以调用apply,call,bind这三个函数的,所以我们实现的函数也是需要做到任何函数都可以做到。
当我们手写的函数的声明,调用方式和函数的实现的功能都和原生函数一致时,我们就像可以说我们实现了这个函数。
怎么做到任何函数都可以调用我们手写的函数?
这里需要用原型的概念,当我们把一个函数添加到一个对象的原型中的时候,任何是这种原型的对象,都可以使用我们添加的这个函数。因为任何函数的原型都是Function 函数这个对象的的原型,所以要做到任何函数都可以调用我们手写的函数, 我们就需要将我们的函数添加到Function 函数这个对象的原型中。
这里可以简单的将原型理解为为原型就是一个类,任何是这个类的对象,都可以使用这个类中的方法,我们将函数添加到原型中,就相当于将函数添加到了类中。由于我们是将手写的函数添加到了Function这个类中,而我们写的所有的函数都是Function这个类的对象,所以所有的函数也就可以使用我们手写的函数了。
上述的这种理解只是为了辅助理解,真正的原型的概念并非如此,请注意。
对于原型不理解的,可以查看我关于原型的总结的文章。
手写call函数
1.分析call函数
fun.call(thisArg,arg1,arg2,arg3,...)
(1)参数:call函数的第一个参数是在 在 `fun`函数运行时指定的 `this`值,它和我们函数内部的this指向不是一回事,剩余参数我们调用函数需要真正传递的值。
(2)返回值:call函数的返回值,是调用call函数的函数的返回值。
(3)功能:call函数实现的功能,功能就是调用 函数。
(4)调用方式:call函数的调用方式,任何函数都可以调用
2.call函数具体实现
//给所有的函数都添加一个zhCall的方法,通过给Function函数的原型添加函数的形式,达到目的
Function.prototype.zhCall = function(thisArg,...agrs){//函数接受n个参数,thisArg接收绑定对象的“this”值,...args接收剩余参数
//1.获取需要执行的函数,call 函数是显示绑定this原则,所以这里的this,指向的是调用call函数的那个函数
var fn = this
//2.对thisArg转成对象类型(防止它传入的非对象类型),将其转化为一个对象,如果使用者传入的不是一个对象类型,则使用默认的window
thisArg = (thisArg !== null && thisArg !==undefined) ? Object(thisArg):window
//3.调用需要被执行的函数
thisArg.fn = fn
var result = thisArg.fn(...args) // 通过隐式绑定this 规则,将传递进来的this对象,传入到函数内部
delete thisArg.fn
//4.将最终的结果返回出去
return result
}
手写apply函数
分析apply函数
fun.apply(thisArg,[arg1,arg2,arg3])
(1)参数:apply函数的第一个参数是在 在 `fun`函数运行时指定的 `this`值,,第二个参数是一个数组,包含了函数调用时的剩余参数。
(2)返回值:apply函数的返回值,是调用apply函数的函数的返回值。
(3)功能:apply函数实现的功能,功能就是调用函数。
(4)调用方式:apply函数的调用方式,任何函数都可以调用
2.apply函数具体实现
Function.prototype.zhapply = function (thisArg,argArray){
//1.获取要执行的函数
var fn = this
//2.处理绑定的thisArg ,将其转化为一个对象,如果使用者传入的不是一个对象类型,则使用默认的window
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg):window
//3.执行函数
thisArg.fn = fn
var result
argArray = argArray || []
result = thisArg.fn(...argArray) //this隐式绑定,将传入的this传递给要执行的函数
delete thisArg.fn
//4.返回结果
return result
}
手写bind函数
1.分析函数
bind函数使用分两步
(1)绑定:
newFn = fun.bind(thisArg,第一次函数可选传入)
(2)调用
newFn(第二次函数参数传入)
(1)参数:bind函数的第一个参数是在 在 `fun`函数运行时指定的 `this`值,,第二个参数是第一次可以传入的可选参数。
(2)返回值:bind函数的返回值,是返回绑定好this值的函数本身。
(3)功能:bind函数实现的功能,主要是实现调用绑定。
(4)调用方式:bind函数的调用方式,任何函数都可以调用,这里需要注意的是在调用时,还可以传入其他的参数。
2.bind函数实现
Function.proptype.zhBind = function(thisArg,...argArray){
//1.获取真实需要调用的函数
var fn = this
//2. 绑定this
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg):window
function proxyFn(..args){
//3.将函数放到thisArg中进行调用
thisArg.fn = fn
//特殊:对两个传入的参数进行合并
var finalArgs = [...argArray,...args]
var result = thisArg.fn(...finalArgs)
delete thisArg.fn
//4.返回结果
return result
}
//返回要调用的函数本身,这里是添加过this绑定值的函数
return proxyFn
}