深入理解并模拟实现apply和call

359 阅读3分钟

了解call和apply

  • 相同点:call和apply都是改变函数this指向并传入参数执行的一种方法。
  • 不同点:call的传参方式为逐个传参,apply为数组传参。 举个栗子:
let obj = {
	value: 1
}
function foo(name, age){
	console.log(this.value)
	console.log(name, age)
}
// 改变this指向为obj, 并传入参数
foo.call(obj, '谢大脚', 40)	// 1	'谢大脚'	40
foo.apply(obj, ['永强', 28])	// 1	'永强'	28

call

分析:

试想一下,如果我们把foo函数变成obj的某一个属性,执行obj.foo

let obj = {
    value: 1,
    foo(){
        console.log(this.value)
    }
}
obj.foo()

不难看出,打印结果为1,根据这步操作可以知道,在call的时候,可以先把函数放到绑定的this的某一个属性上面,然后执行这个函数,最后删除这个属性。

第一版

根据call的使用方式可知call存在于Function.prototype中,所以我们开始写第一版,给它命名为call2。

Function.prototype.call2 = function(ctx) {
    // function函数的this谁调用就指向谁,所以这里的this就是我们要执行的改变this指向的函数foo
    // ctx为调用call2时传入的第一个参数(也就是新的this指向,就是obj)
    ctx.fn = this
    ctx.fn()
    delete ctx.fn
}
// 测试一下
let obj = {
    value: 1
}
function foo(){
    console.log(this.value)
}
foo.call2(obj)

打印结果正常,开心的yia批(❁´◡`❁)

第二版

但是还没有实现参数的传递,如果需要传递参数,而且参数的数量也不是固定的,那该怎么办呢? 可以从Arguments对象中取值啊。

  • 引用MDN的一句介绍:arguments对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数。 因为arguments[0]为ctx,我们需要删除它,arguments为类数组,说到删除第一项,我瞬间想到了Array.prototype.shift.call(arguments),但是我们在手动实现call啊喂,肯定不能再调用call🤣。只能先用Array.from转换为数组。
Function.prototype.call2 = function(ctx) {
    let args = Array.from(arguments)
    args.shift()
    ctx.fn = this
    ctx.fn(...args)
    delete ctx.fn
}
// 试一下
let obj = {
    value: 1
}
function foo(name, age){
    console.log(this.value)
    console.log(name, age)
}
foo.call2(obj, '刘英', 27)

OK,一切正常

第三版

call还有另外一个属性就是当传入的被绑定的this为null的时候,默认绑定到全局this上面,函数体第一行增加代码

ctx = ctx || globalThis	// 当ctx为null时,ctx指向globalThis
最终版

最后还有一点,假如函数有返回值,我们上面写的岂不是就有漏洞了,肯定是返回undefined的,所以修改代码为

Function.prototype.call2 = function(ctx) {
    ctx = ctx || globalThis
    let args = Array.from(arguments)
    args.shift()
    ctx.fn = this
    let res = ctx.fn(...args)
    delete ctx.fn
    return res
}

手写call到这里就完成了,么么<( ̄ c ̄)y▂ξ

apply

apply其实就是参数传递方式和call不同而已,外卖要到了,就直接贴代码了😁。

最终版
Function.prototype.apply2 = function(ctx, arr = []) {
    ctx = ctx || globalThis
    ctx.fn = this
    let res = ctx.fn(...arr)
    delete ctx.fn
    return res
}
// 试一下
let obj = {
    value: 1
}
function foo(name, age){
    console.log(this.value)
    console.log(name, age)
    return 'success'
}
console.log(foo.apply2(obj, ['刘英', 27]))
//打印结果为 1 '刘英' 27 'success' 

好了,本篇内容就到这儿了,如果有错误或者不严谨的地方,请务必指正,万分感谢😛。