这才是正确的手写Call函数的姿势

前言

今天一起来写个Call函数。

手写Call代码,大家肯定不陌生了,或者说,现在都被大家讲烂,只要是个面试相关的文章,都会有这个题目。

当然了,写法也是多种多样。由于只是当成一个面试题来说的话,肯定说的没有那么完美的!

总有点缺陷

分析

首先呢,我们来说说call函数;

call方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

当函数执行call方法的时候,其实就是把函数放到call的第一个参数的某个属性上,然后在通过这个属性来执行函数。

代码示例:

fun.call(ctx,arg1,…) ==> ctx.fn=fun;ctx.fn(arg1,…);

再来看下官方对于call函数的参数解释:

参数

thisArg

可选的。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

arg1,arg2…

参数列表

我可以发现,call函数在严格模式和非严格模式下还是有区分的!再来看看网上一些普遍的写法。

示例1

我们来看下大部分网友的写法。

Function.prototype.call=function(context){
    context=context||window;
    context['fn']=this;
    let fnArgs=[...arguments].slice(1);
    const result=context['fn'](...fnArgs);
    delete context['fn']
    return result
}

看过这段代码后,我们是不是会有疑惑:

  • 假如context上面有fn属性的时候,咋办
  • this肯定是一个函数吗?
  • context如果不是一个Object类型咋办?
  • 用es6来写,本身就隐藏了很多细节

接下来,我们看下一个进阶版本的:

示例2

Function.prototype.call=function(context){
    let fn=Symbol('fn');
    context=(context===null || context === undefined)?window:new Object(context)
    context[fn]=this;
    
    var realArgs=[];for(var i=1;i<arguments.length;i++){
        realArgs.push(`arguments[${i}]`);
    }
    var result=eval(`context[fn](${realArgs.join(',')})`)delete context[fn]return result
}

这个示例呢,在细节上考虑的比较多

  • context 进行Object包装
  • 对于参数的处理,也考虑了兼容性,用了很基础的语法
  • 也考虑了fn属性的唯一

通过这俩个示例,其实我们可以发现一些通用的细节问题!

手写call的细节问题

我这里归纳了一下:

  • this 是否是一个函数

  • 当context为null,undefined的时候,一定是指向window吗?node环境呢?

  • fn属性要保证唯一

  • 安全的执行函数

    • 如果用eval的话,eval是否能保证一定可以执行?
  • 尽量使用es5的语法来实现

  • 当前是否是严格模式

针对以上的细节,我们这里来实现一个比较完美的版本吧

代码

前面说的差不多了,这里直接上代码吧,代码有注释;

我这里没有处理严格模式还是非严格模式,直接都包装Object了

//动态生成函数
function createFn(argumentsLength) {
    var str = []
    for (var i = 0; i < argumentsLength; i++) {
        str.push('agrs[' + i + ']')
    }
    var fnCode = 'return context[fnName](' + str.join(',') + ')'
    return new Function('context', 'fnName', 'agrs', fnCode)
}Function.prototype.call = function (context) {
    //判断是否是在函数上调用call
    if (typeof this !== 'function') {
        throw new Error('this is not function')
    }//获取当前环境的上下文
    var defaultContext = null
    //web workers 中是self
    if (typeof self !== 'undefined') {
        defaultContext = self
    }
    if (typeof global !== 'undefined') {
        defaultContext = global
    }
    if (typeof window !== 'undefined') {
        defaultContext = window
    }//设置上下文
    context = context === null || context === undefined ? defaultContext : new Object(context)var fnName = 'fn'var temp = null
    if (context[fnName]) {
        temp = context[fnName]
    }
    context[fnName] = this//获取参数列表
    var realArgs = []
    for (var i = 1; i < arguments.length; i++) {
        realArgs.push(arguments[i])
    }// var result=eval(`context[fnName](${realArgs.join(',')})`)
    //这里使用Function来调用
    var fnRun = createFn(realArgs.length)
    var result = fnRun(context, fnName, realArgs)if (temp) {
        context[fnName] = temp
    } else {
        delete context[fnName]
    }
    return result
}

总结

这道手写Call的题,还是能学习很多东西

  • Function.prototype.call this不一定是Function
  • 动态生成函数
  • 区分环境上下文
  • call的使用方法
  • delete函数
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端3K小哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值