首页  >  问答  >  正文

javascript - js中call方法的实现

Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    eval('context.fn(' + args +')');
    delete context.fn;
}

是为了模拟call的实现,请问为什么要push一个字符串,下面再用eval?直接传入arguments[i],然后下面用context.fn(args)为什么不可以?

PHP中文网PHP中文网2685 天前818

全部回复(3)我来回复

  • 给我你的怀抱

    给我你的怀抱2017-06-12 09:32:07

    这里我相信你也已经明白了call的原理,这里我简要还是说明一下原理,我也是参考JavaScript权威指南的说明然后用代码实现的。

    首先我们看看call的语法和定义,来自ECMAScript规范中文版:

    还是简单举个栗子吧:

    var jawil = {
        name: "jawil",
        sayHello: function (age) {
             console.log("hello, i am ", this.name + " " + age + " years old");
         }
    };
    
    var  lulin = {
        name: "lulin",
    };
    
    jawil.sayHello(24);
    
    // hello, i am jawil 24 years old

    然后看看使用call之后的输出:

    jawil.sayHello.call(lulin, 24);// hello, i am lulin 24 years old
    

    下面我结合栗子来解答你的疑问:

    是为了模拟call的实现,请问为什么要push一个字符串,下面再用eval?直接传入arguments[i],然后下面用context.fn(args)的实现,请问为什么要push一个字符串,下面再用eval?直接传入arguments[i],然后下面用context.fn(args)为什么不可以?

    首先你要明白上面模拟的函数对应栗子中变量的关系:

    context  => lulin
    
    context.fn => jawil.sayHello

    注意到这一步,我们只是把 jawil.sayHello的引用地址给了lulin.sayHello

    原来 jawil.sayHello.call(context,arg1,arg2,arg3,arg4)

    按照你的方式剔除context得到args=[arg1,arg2,arg3,arg4]

    然后执行lulin.sayHello([arg1,arg2,arg3,arg4])哈哈,很有迷惑性是不是,看着没问题,其实已经变味了,原来是原来是四个参数,现在集合到一个数组就是一个数组参数了,问题就出在这里。

    那么怎么解决这个问题,思路就是上面那样,把所有参数拼成字符串,然后用eval执行。

    我们想要的效果是lulin.sayHello(arg1,arg2,arg3,arg4)这样的,因为lulin.sayHello要重组参数,你不能lulin.sayHello(arg1,arg2,arg3,arg4)这样的,因为lulin.sayHello要重组参数,你不能拿到一个参数执行一次函数吧,或者把参数存到一起一次执行吧,唯一的想到的做法就是把所有参数拼成字符串,然后用eval拿到一个参数执行一次函数

    吧,或者

    把参数存到一起一次执行lulin.sayHello([arg1,arg2,arg3,arg4]),也不是lulin.sayHello(arg1),lulin.sayHello(arg2)吧,唯一的想到的做法就是把所有参数拼成字符串,然后用

    执行,

    类似这种:“lulin.sayHello(arg1,arg2,arg3,arg4)”这才是我们想要的方式,而不是

    ,也不是lulin.sayHello(arg1),lulin.sayHello(arg2)...eval
    什么是eval,这里也简单说一下,我就当你什么也不知道。

    先简单了解一下

    函数吧

    定义和用法


    eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。eval(string)

    语法:

    🎜 🎜🎜string必需。要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句。该方法只接受原始字符串作为参数,如果 string 参数不是原始字符串,那么该方法将不作任何改变地返回。因此请不要为 eval() 函数传递 String 对象来作为参数。🎜

    简单来说吧,就是用JavaScript的解析引擎来解析这一堆字符串里面的内容,这么说吧,你可以这么理解,你把eval看成是<script>标签。

    eval('function Test(a,b,c,d){console.log(a,b,c,d)};
    Test(1,2,3,4)')

    就是相当于这样

    <script>
    function Test(a,b,c,d){
    console.log(a,b,c,d)
    };
    Test(1,2,3,4)
    </script>

    好了,我们再来看上面那段代码,其实还有坑的,来看看调式直观点。下面是完整的调式代码:

    Function.prototype.call2 = function(context) {
        context.fn = this;
        var args = [];
        for (var i = 1, len = arguments.length; i < len; i++) {
            args.push('arguments[' + i + ']');
        }
    
        console.log(args)
    
        var star = 'context.fn(' + args + ')'
    
        console.log(star)
        
        eval('context.fn(' + args + ')');
    
        delete context.fn;
    }
    
    
    var jawil = {
        name: "jawil",
        sayHello: function(age) {
            console.log("hello, i am ", this.name + " " + age + " years old");
        }
    };
    
    var lulin = {
        name: "lulin",
    };
    
    
    jawil.sayHello.call2(lulin, 24, 25);
    

    看args的输出:

    ["arguments[1]", "arguments[2]"]

    然后再看 'context.fn(' + args + ')'的输出:

    "context.fn(arguments[1],arguments[2])"是不是有点懵逼

    其实这里涉及到了隐式转换,举个栗子吧:

    'jawil'+[1,2]+[3,4]+3等于多少?'jawil'+[1,2]+[3,4]+3等于多少?
    等于"jawil1,23,43"
    其实这个相当于'jawil'+[1,2].toString()+[3,4].toString()+3等于"jawil1,23,43"

    其实这个相当于'jawil'+[1,2].toString()+[3,4].toString()+3

    篇幅有限,更多隐式转换,请参考我的这篇文章:从++[[]][+[]]+[+[]]==10?深入浅出弱类型JS的隐式转换

    说到这里,我把核心的都说了,你自己好好领悟领悟,原作者写这个东西估计也是参看别人的,很多东西没讲清楚,估计由于篇幅有限,所以一笔带过,看似很短的一段代码,其实包含了不少知识点的。🎜

    回复
    0
  • 曾经蜡笔没有小新

    曾经蜡笔没有小新2017-06-12 09:32:07

    args 是一个数组,context.fn(args) 只有一个参数,正常情况下可以用 apply 可以解决数组转参数,但这里模拟 call ,用 apply 就没意思了。所以它利用了数组的 toString() 把 context 以外的参数搬到 context.fn 上。

    回复
    0
  • 淡淡烟草味

    淡淡烟草味2017-06-12 09:32:07

    因为 arguments[0] 是 context
    你没看循环变量从1开始的么!

    回复
    0
  • 取消回复