手写call实现
上一篇文章中介绍了手写实现一个 bind
,本文介绍手写一个 call
。
call
和 bind
有点类似,可以改变函数执行的上下文,不同的是, call
是调用。
基于这个特性实现如下
function fnCall(fn, context, ...args) {
context = context || window;
context._fn = fn;
const result = context._fn(...args);
delete context._fn;
return result;
}
这里将传入的 fn
函数赋值给 context
下的 _fn
属性,context
可以看做是一个 object
对象,_fn
为这个对象的键,将 fn
赋值给这个对象后,fn
中的 this
将会指向的是 context
这个对象,所以就实现了执行上下文的变更。
虽然,这里使用 _fn
作为 context
的键(前面加了一个下划线),但是不能排除这个属性在本身的 context
中不存在,为了规避属性冲突的问题,可以使用 Symbol
,因为每一个 Symbol
都是唯一的。
function fnCall(fn, context, ...args) {
context = context || window;
const symbolKey = Symbol();
context[symbolKey] = fn;
const result = context[symbolKey](...args);
delete context[symbolKey];
return result;
}
OK,现在来测试一下
window.name = 'leevare';
function testFn() {
console.log(this.name);
}
fnCall(testFn); // leevare
fnCall(testFn, { name: 'Jason' }); // Jason
输出的结果和期望一致。可是,真的就结束了吗?我们再添加一些测试数据。
fnCall(testFn, 123); // 报错了
fnCall(testFn, '123'); // 报错了
testFn.call(123); // undefined
testFn.call('123'); // undefined
当我们传入一些数字或字符串的时候,我们的函数报错了,而使用 call
却是返回 undefined
。
我们知道,数字或字符串是不能直接在它的上面添加属性的,所以直接使用 context.xxx
的这种形式就行不通了。不过,可以变通一下,把 context
强制转换成对象,那么,当传入 number
或 string
的时候会被转为包装对象。
function fnCall(fn, context, ...args) {
context = context ? Object(context) : window;
const symbolKey = Symbol();
context[symbolKey] = fn;
const result = context[symbolKey](...args);
delete context[symbolKey];
return result;
}
再次测试上面的示例
fnCall(testFn, 123); // undefined
fnCall(testFn, '123'); // undefined
testFn.call(123); // undefined
testFn.call('123'); // undefined
一切正常。
- 本博客所有文章除特别声明外,均可转载和分享,转载请注明出处!
- 本文地址:https://www.leevii.com/?p=3063