在运行时向控制台方法添加动态值,同时保留原始呼叫位置和行号

the*_*der 16 javascript typescript ecmascript-6 console.log es6-proxy

我做了下面的课来"劫持"这个console.log功能.这背后的原因是我想动态添加和删​​除值 .它将用于调试目的,因此函数调用起源console.log()很重要.在下面的代码中,我将在评论中解释我的逻辑.

export class ConsoleLog {
  private _isActive = false;
  private _nativeLogFn: any;

  constructor() {

    // ----------------------
    // Store the native console.log function, so it can be restored later 
    // ----------------------

    this._nativeLogFn = console.log;

  }

  public start() {
    if (!this._isActive) {

      // ----------------------
      // Create a new function as replacement for the native console.log 
      // function. *** This will be the subject of my question ***
      // ----------------------

      console.log = console.log.bind(console, Math.random());

      this._isActive = true;
    }
  }

  public stop() {
    if (this._isActive) {
      // Restore to native function
      console.log = this._nativeLogFn;
      this._isActive = false;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

此设置的问题是,新功能以静态形式分配.

// Function random() generates a number at the moment I assign the function. 
// Let's say it's the number *99* for example sake. 

console.log.bind(console, Math.random());
Run Code Online (Sandbox Code Playgroud)

每次console.log(...)调用时,它将输出99.所以它几乎是静态的.(要领先于你:我的目标不是输出一个随机数,大声笑,但我只是用它来测试输出是否是动态的.).

烦人的部分是,使用函数与 console.log.bind我发现实际上保留原始调用者和行号的唯一方法.

我写了以下简单的测试.

    console.log('Before call, active?', 'no'); // native log

    obj.start();  // Calls start and replaces the console.log function

    console.log('foo'); // This will output 'our' 99 to the console.
    console.log('bar'); // This will output 'our' 99 again.

    obj.stop(); // Here we restore the native console.log function

    console.log('stop called, not active'); // native log again

    // Now if I call it again, the random number has changed. What is 
    // logical, because I re-assign the function.

    obj.start();  // Calls start and replaces the console.log function
    console.log('foo'); // This will output N to the console.
    // But then I have to call start/log/stop all the time. 
Run Code Online (Sandbox Code Playgroud)

问题:如何在运行时向console.log添加值而不会丢失原始调用者文件名和行号...并且在使用start()启动此类后,不会打扰库使用者.

编辑:添加了一个plkr:https : //embed.plnkr.co/Zgrz1dRhSnu6OCEUmYN0

the*_*der 6

让我花费周末的大部分时间和大量的阅读和摆弄,但我终于利用ES6代理对象解决了它.我可能会添加相当强大的东西.解释在代码中.请不要犹豫,改进或提出问题.

(根据@ Bergi的评论编辑)以下是课程:

export class ConsoleLog {
  private _isActive = false;
  private _nativeConsole: any;
  private _proxiedConsole: any;

  /**
   * The Proxy constructor takes two arguments, an initial Object that you
   * want to wrap with the proxy and a set of handler hooks.
   * In other words, Proxies return a new (proxy) object which wraps the
   * passed in object, but anything you do with either effects the other.
   *
   * ref: https://www.keithcirkel.co.uk/metaprogramming-in-es6-part-3-proxies
   * ref: http://exploringjs.com/es6/ch_proxies.html#_intercepting-method-calls
   */

  /**
   * Challenge:
   * When we intercept a method call via a proxy, you can intercept the
   * operation 'get' (getting property values) and you can intercept the
   * operation 'apply' (calling a function), but there is no single operation
   * for method calls that you could intercept. That’s why we need to treat
   * them as two separate operations:
   *
   * First 'get' to retrieve a function, then an 'apply' to call that
   * function. Therefore intercepting 'get' and return a function that
   * executes the function 'call'.
   */

  private _createProxy(originalObj: Object) {

    const handler = {

      /**
       * 'get' is the trap-function.
       * It will be invoked instead of the original method.
       * e.a. console.log() will call: get(console, log) {}
       */
      get(target: object, property: string) {

        /**
         * In this case, we use the trap as an interceptor. Meaning:
         * We use this proxy as a sort of pre-function call.
         * Important: This won't get invoked until a call to a the actual
         * method is made.
         */

        /**
         * We grab the native method.
         * This is the native method/function of your original/target object.
         * e.a. console.log = console['log'] = target[property]
         * e.a. console.info = console['info'] = target[property]
         */
        const nativeFn: Function = target[property];

        /**
         * Here we bind the native method and add our dynamic content
         */
        return nativeFn.bind(
          this, `%cI have dynamic content: ${Math.random()}`, 'color:' +
          ' #f00;'
        );
      }
    };
    return new Proxy(originalObj, handler);
  }

  constructor() {
    // Store the native console.log function so we can put it back later
    this._nativeConsole = console;
    // Create a proxy for the console Object
    this._proxiedConsole = this._createProxy(console);
  }

  // ----------------------
  // (Public) methods
  // ----------------------

  public start() {
    if (!this._isActive) {
      /**
       * Replace the native console object with our proxied console object.
       */
      console = <Console>this._proxiedConsole;
      this._isActive = true;
    }
  }

  public stop() {
    if (this._isActive) {
      // Restore to native console object
      console = <Console>this._nativeConsole;
      this._isActive = false;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这里是自己看的代码:

const c: ConsoleLog = new ConsoleLog();

console.log('Hi, I am a normal console.log', ['hello', 'world']);

c.start(); // Start - replaces the console with the proxy

console.log('Hi, I am a proxied console.log');
console.log('I have dynamic content added!');
console.log('My source file and line number are also intact');

c.stop(); // Stop - replaces the proxy back to the original.

console.log('I am a normal again');
Run Code Online (Sandbox Code Playgroud)

干杯!