【Web前端问题】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 + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn; //删除挂载在借用对象上的fn属性
return result;
}
是为了模拟call的实现,请问为什么要push一个字符串,下面再用eval?直接传入arguments[i],然后下面用context.fn(args)为什么不可以?
回答:
这里我相信你也已经明白了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一个字符串,下面再用eva
l?直接传入arguments[i]
,然后下面用context.fn(args)
为什么不可以?
首先你要明白上面模拟的函数对应栗子中变量的关系:
context => lulincontext.fn => jawil.sayHello
注意到这一步,我们只是把 jawil.sayHello
的引用地址给了lulin.fn
原来 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的隐式转换
说到这里,我把核心的都说了,你自己好好领悟领悟,原作者写这个东西估计也是参看别人的,很多东西没讲清楚,估计由于篇幅有限,所以一笔带过,看似很短的一段代码,其实包含了不少知识点的。
回答:
args 是一个数组,context.fn(args)
只有一个参数,正常情况下可以用 apply 可以解决数组转参数,但这里模拟 call ,用 apply 就没意思了。所以它利用了数组的 toString() 把 context 以外的参数搬到 context.fn 上。
回答:
因为 arguments[0] 是 context
你没看循环变量从1开始的么!
以上是 【Web前端问题】js中call方法的实现 的全部内容, 来源链接: utcz.com/a/143686.html