为何要用Function.prototype.apply.call

1,857 阅读1分钟

某些源码中会有这个Function.prototype.apply.call方法,刚开始看特别不理解,为何要写的这么绕~看起来头好疼...仔细研究发现这个方法~很有意思

回忆下apply,call的基本用法,其目的是改变调用方法中的this指向,将其指向为传入的对象

下面来分析下Sentry源码中的一段用到Function.prototype.apply.call方法的代码:

/** JSDoc */
function instrumentConsole(): void {
  if (!('console' in global)) {
    return;
  }

  ['debug', 'info', 'warn', 'error', 'log', 'assert'].forEach(function(level: string): void {
    if (!(level in global.console)) {
      return;
    }

    fill(global.console, level, function(originalConsoleLevel: () => any): Function {
      return function(...args: any[]): void {
        triggerHandlers('console', { args, level });

        // this fails for some browsers. :(
        if (originalConsoleLevel) {
          Function.prototype.apply.call(originalConsoleLevel, global.console, args);
        }
      };
    });
  });
}

先分析下该如何理解Function.prototype.apply.call(originalConsoleLevel, global.console, args);

首先可以将Function.prototype.apply看成一个整体-->FunctionApply

FunctionApply.call(originalConsoleLevel, global.console, args);

那么将此句翻译一下

originalConsoleLevel.FunctionApply(global.console, args);

然后再翻译一下就是一个普通方法调用了

global.console.originalConsoleLevel(args);

如图中,此次运行调用的就是console的info方法

可能很多人根我一样都有一个疑问,为何不直接使用apply方法,如下所示: originalConsoleLevel.apply(global.console, args),看到react源码中给到的解释是在IE9中有bug

function printWarning(level, format, args) {
  // When changing this logic, you might want to also
  // update consoleWithStackDev.www.js as well.
  if (__DEV__) {
    const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
    const stack = ReactDebugCurrentFrame.getStackAddendum();
    if (stack !== '') {
      format += '%s';
      args = args.concat([stack]);
    }

    const argsWithFormat = args.map(item => '' + item);
    // Careful: RN currently depends on this prefix
    argsWithFormat.unshift('Warning: ' + format);
    // We intentionally don't use spread (or .apply) directly because it
    // breaks IE9: https://github.com/facebook/react/issues/13610
    // eslint-disable-next-line react-internal/no-production-logging
    Function.prototype.apply.call(console[level], console, argsWithFormat);
  }
}

欢迎探讨~