手写call实现

上一篇文章中介绍了手写实现一个 bind,本文介绍手写一个 call

callbind 有点类似,可以改变函数执行的上下文,不同的是, 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 强制转换成对象,那么,当传入 numberstring 的时候会被转为包装对象。

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

一切正常。

如果您觉得本文对您有用,欢迎捐赠或留言~
微信支付
支付宝

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注