JavaScript 模拟 call 的实现
作者:Seiya
时间:2019年08月08日
call
方法
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
它提供新的 this 值给当前调用的函数/方法。我们可以使用 call 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。
注意:
该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
模拟实现
若要模拟 call 方法的实现,需要注意两点:
修改 this 的指向;
执行调用的函数;
同时,我们需要解决实现 call 方法过程中的三个问题:
问题一
:call 方法的整体思路;问题二
:解决参数传递的问题;问题三
:解决 this 参数为 null 的问题,以及函数返回值的问题;
我们拿下面的例子来模拟 call 方法的实现:
var foo = { value: 1 }
function bar() { console.log(this.value) }
bar.callFn(foo) // 1
问题一:call 方法的整体思路
第一步:将函数设为对象的属性;
第二步:执行该函数;
第三步:删除该函数;
根据上面的思路,我们可以得到初步代码:
Function.prototype.callFn = function(context) {
context.fn = this
context.fn()
delete context.fn
}
问题二:解决参数传递的问题
我们可以从 Arguments 对象中取值,取出第二个到最后一个参数,然后放到一个数组里。具体实现如下:
Function.prototype.callFn = function(context) {
context.fn = this
var args = []
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('argument[' + i + ']')
}
/* 使用 eval 函数拼一个函数,解决参数传递的问题 */
eval('context.fn(' + args + ')')
delete context.fn
}
也可通过使用 ES6 中提供的新特性实现:
Function.prototype.callFn = function(context) {
context.fn = this
var args = [...arguments].slice(1)
context.fn(...arg)
delete context.fn
}
当然,我们也可以这样实现:
Function.prototype.callFn = function(context, ...args) {
context.fn = this
args = args ? args : []
context.fn(...args)
delete context.fn
}
问题三:解决 this 参数为 null 的问题,以及函数返回值的问题
Function.prototype.callFn = function(context) {
/* 判断 context 是否为 null */
var context = context || window
context.fn = this
var args = []
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('argument[' + i + ']')
}
/* 接受函数执行的返回值 */
var result = eval('context.fn(' + args + ')')
delete context.fn
return result
}
最终版
Function.prototype.callFn = function(context, ...args) {
var context = context || window
args = args ? args : []
/* 给 context 新增一个独一无二的属性以免覆盖原有属性 */
const fn = Symbol()
context.fn = this
/* 通过隐式绑定的方式调用函数 */
var result = context.fn(...args)
delete context.fn
return result
}