为什么闭包编译器更改了 'this'?

Why has closure compiler changed 'this'?

在我的压缩代码中,在高级编译下,编译器更改了我函数的调用上下文。我在对原因和方法进行一些推理之后,所以我可以弄清楚如何修复它。

背景故事

我已经将我的代码生成到模块中,并且在过去的几天里我一直在转换 react js material ui library into a closure style provide/require syntax. I couldn't get CommonJS to play nicely with my modular approach and I couldn't get goog.module to work with the debug tool I use 'plovr'。快到了,但我遇到了这个问题。

我编译的代码有 sourcemaps,所以我可以看到哪里出了问题,但对我来说似乎没有任何意义。

错误在这里抛出。请注意,这是压缩代码,但您看到它通过 sourcemaps 映射到原始代码。 decomposeColor 不存在,因为 this 等于 window 对象。

如果我在控制台中输入 this

然后我进入堆栈的上一层并在控制台中键入 this,这是我希望看到的下一层的正确对象。

这是同样的事情,但实际代码看起来像是压缩的

知道什么会导致编译器这样做吗?

更新:

在评论中提出一些建议后(感谢 Jan),我应该寻找的东西是有意义的,似乎编译器已经从我的对象方法转换而来

goog.provide('mui.utils.colorManipulator');
mui.utils.colorManipulator = {
  //...
  /**
   * @this {mui.utils.colorManipulator}
   */
  fade: function fade(color, amount) {
    color = this._decomposeColor(color);
    if (color.type === 'rgb' || color.type === 'hsl') color.type += 'a';
    return this._convertColorToString(color, amount);
  }
  //...
}

进入在全局范围内声明的函数。

function kc(f, a) {
  f = this.nd(f);
  if ("rgb" === f.type || "hsl" === f.type)
    f.type += "a";
  return this.md(f, a)
}

所以 'this' 上下文会有所不同,我只需要弄清楚为什么编译器会那样做。

更新:

这是 colorManipulator 的所有代码。它几乎是从 this

移植过来的
goog.provide('mui.utils.colorManipulator')

mui.utils.colorManipulator = {

  /**
   * The relative brightness of any point in a colorspace, normalized to 0 for
   * darkest black and 1 for lightest white. RGB colors only. Does not take
   * into account alpha values.
   *
   * TODO:
   * - Take into account alpha values.
   * - Identify why there are minor discrepancies for some use cases
   *   (i.e. #F0F & #FFF). Note that these cases rarely occur.
   *
   * Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
   */
  _luminance(color) {
    color = this._decomposeColor(color);

    if (color.type.indexOf('rgb') > -1) {
      let rgb = color.values.map((val) => {
        val /= 255; // normalized
        return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
      });

      return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];

    }
    else {
      let message = 'Calculating the relative luminance is not available for ' +
                    'HSL and HSLA.';
      console.error(message);
      return -1;
    }
  },

  /**
   * @params:
   * additionalValue = An extra value that has been calculated but not included
   *                   with the original color object, such as an alpha value.
   */
  _convertColorToString(color, additonalValue) {
    let str = color.type + '(' +
              parseInt(color.values[0]) + ',' +
              parseInt(color.values[1]) + ',' +
              parseInt(color.values[2]);

    if (additonalValue !== undefined) {
      str += ',' + additonalValue + ')';
    }
    else if (color.values.length === 4) {
      str += ',' + color.values[3] + ')';
    }
    else {
      str += ')';
    }

    return str;
  },

  // Converts a color from hex format to rgb format.
  _convertHexToRGB(color) {
    if (color.length === 4) {
      let extendedColor = '#';
      for (let i = 1; i < color.length; i++) {
        extendedColor += color.charAt(i) + color.charAt(i);
      }
      color = extendedColor;
    }

    let values = {
      r:    parseInt(color.substr(1,2), 16),
      g:    parseInt(color.substr(3,2), 16),
      b:    parseInt(color.substr(5,2), 16),
    };

    return 'rgb(' + values.r + ',' +
                    values.g + ',' +
                    values.b + ')';
  },

  // Returns the type and values of a color of any given type.
  _decomposeColor(color) {
    if (color.charAt(0) === '#') {
      return this._decomposeColor(this._convertHexToRGB(color));
    }

    let marker = color.indexOf('(');
    let type = color.substring(0, marker);
    let values = color.substring(marker + 1, color.length - 1).split(',');

    return {type: type, values: values};
  },

  // Set the absolute transparency of a color.
  // Any existing alpha values are overwritten.
  /**
   * @this {mui.utils.colorManipulator}
   */
  fade(color, amount) {
    color = this._decomposeColor(color);
    if (color.type === 'rgb' || color.type === 'hsl') color.type += 'a';
    return this._convertColorToString(color, amount);
  },

  // Desaturates rgb and sets opacity to 0.15
  lighten(color, amount) {
    color = this._decomposeColor(color);

    if (color.type.indexOf('hsl') > -1) {
      color.values[2] += amount;
      return this._decomposeColor(this._convertColorToString(color));
    }
    else if (color.type.indexOf('rgb') > -1) {
      for (let i = 0; i < 3; i++) {
        color.values[i] *= 1 + amount;
        if (color.values[i] > 255) color.values[i] = 255;
      }
    }

    if (color.type.indexOf('a') <= -1) color.type += 'a';

    return this._convertColorToString(color, '0.15');
  },

  darken(color, amount) {
    color = this._decomposeColor(color);

    if (color.type.indexOf('hsl') > -1) {
      color.values[2] += amount;
      return this._decomposeColor(this._convertColorToString(color));
    }
    else if (color.type.indexOf('rgb') > -1) {
      for (let i = 0; i < 3; i++) {
        color.values[i] *= 1 - amount;
        if (color.values[i] < 0) color.values[i] = 0;
      }
    }

    return this._convertColorToString(color);
  },


  // Calculates the contrast ratio between two colors.
  //
  // Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
  contrastRatio(background, foreground) {
    let lumA = this._luminance(background);
    let lumB = this._luminance(foreground);

    if (lumA >= lumB) {
      return ((lumA + 0.05) / (lumB + 0.05)).toFixed(2);
    }
    else {
      return ((lumB + 0.05) / (lumA + 0.05)).toFixed(2);
    }
  },

  /**
   * Determines how readable a color combination is based on its level.
   * Levels are defined from @LeaVerou:
   * https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/contrast-ratio.js
   */
  contrastRatioLevel(background, foreground) {
    let levels = {
      'fail': {
        range: [0, 3],
        color: 'hsl(0, 100%, 40%)',
      },
      'aa-large': {
        range: [3, 4.5],
        color: 'hsl(40, 100%, 45%)',
      },
      'aa': {
        range: [4.5, 7],
        color: 'hsl(80, 60%, 45%)',
      },
      'aaa': {
        range: [7, 22],
        color: 'hsl(95, 60%, 41%)',
      },
    };

    let ratio = this.contrastRatio(background, foreground);

    for (let level in levels) {
      let range = levels[level].range;
      if (ratio >= range[0] && ratio <= range[1]) return level;
    }
  },
};

this 不应该真正与对象文字一起使用,因为它们可以被视为命名空间之外的静态函数。 this 应与使用 .bind.call.apply 调用的函数或使用构造函数和 new 创建的实例一起使用。按说是支持的,但是closure一定不能完全理解是怎么回事:How does "this" keyword work within a function?

我会更改代码以直接引用 mui.utils 的其他函数。

fade 在您的代码中是一个对象的方法,而在压缩版本中它不是,因此对 this 的引用从方法的对象更改为全局对象。

解决此问题的另一种方法是使用 new function 单例模式来实例化您的对象。

mui.utils.colorManipulator = new function() { 
    this.fade = function() { /*...*/ }
};

无论是那个还是像 jfriend00 建议的那样,在你的方法中指定完整的命名空间,这样引用就不会在编译器上丢失。

Implications of object property flattening

In Advanced mode the Compiler collapses object properties to prepare for name shortening. For example, the Compiler transforms this:

var foo = {}; foo.bar = function (a) { alert(a) };
foo.bar("hello"); into this:

var foo$bar = function (a) { alert(a) }; foo$bar("hello"); This property flattening allows the later renaming pass to rename more efficiently. The Compiler can replace foo$bar with a single character, for example.

But property flattening also makes the following practice dangerous:

Using this outside of constructors and prototype methods:

Property flattening can change meaning of the keyword this within a function. For example:

var foo = {}; foo.bar = function (a) { this.bad = a; }; // BAD
foo.bar("hello"); becomes:

var foo$bar = function (a) { this.bad = a; }; foo$bar("hello"); Before the transformation, the this within foo.bar refers to foo. After the transformation, this refers to the global this. In cases like this one the Compiler produces this warning:

"WARNING - dangerous use of this in static method foo.bar" To prevent property flattening from breaking your references to this, only use this within constructors and prototype methods. The meaning of this is unambiguous when you call a constructor with the new keyword, or within a function that is a property of a prototype.

来源:https://developers.google.com/closure/compiler/docs/limitations?hl=en#implications-of-object-property-flattening]

换句话说,闭包编译器不支持在对象字面量中使用this,因为属性扁平化。

因此,一个简单的解决方案是引用您要访问的 属性 对象的完整命名空间。

mui.utils.colorManipulator.name_of_function(args);