0
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)为什么不可以?

ZrdZz 254
2017-06-07 提问
3 个回答
5

已采纳

这里我相信你也已经明白了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)为什么不可以?

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

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要重组参数,你不能拿到一个参数执行一次函数吧,或者把参数存到一起一次执行吧,唯一的想到的做法就是把所有参数拼成字符串,然后用eval执行,

类似这种:“lulin.sayHello(arg1,arg2,arg3,arg4)”这才是我们想要的方式,而不是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等于多少?
等于"jawil1,23,43"
其实这个相当于'jawil'+[1,2].toString()+[3,4].toString()+3

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

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

1

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

0

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

撰写答案

你可能感兴趣的

推广链接