Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

关于JavaScript中的“==”运算符

  看了V8的“==”运算符的实现,感觉很无语。我记得去年写过一篇文章(点击查看)来说明这个的,那时候还没搞这个博客。是发表在百度贴吧上的。不过当时使用的是简单化规则原理来理解的这个“==”运算符,没有源代码作为依据。今天看了V8中的实现,虽然逻辑有点复杂,但是描述的可以比较简单。虽然ECMA-262中对类型转换也有描述,但是我觉得自然语言的描述永远不会完美,只有代码是不会背叛我的。下面是V8里实现“==”运算符的代码 function EQUALS(y) {
  if (IS_STRING(this) && IS_STRING(y)) return %StringEquals(this, y);
  var x = this;

  while (true) {
    if (IS_NUMBER(x)) {
      while (true) {
        if (IS_NUMBER(y)) return %NumberEquals(x, y);
        if (IS_NULL_OR_UNDEFINED(y)) return 1;  // not equal
        if (!IS_SPEC_OBJECT(y)) {
          // String or boolean.
          return %NumberEquals(x, %ToNumber(y));
        }
        y = %ToPrimitive(y, NO_HINT);
      }
    } else if (IS_STRING(x)) {
      while (true) {
        if (IS_STRING(y)) return %StringEquals(x, y);
        if (IS_NUMBER(y)) return %NumberEquals(%ToNumber(x), y);
        if (IS_BOOLEAN(y)) return %NumberEquals(%ToNumber(x), %ToNumber(y));
        if (IS_NULL_OR_UNDEFINED(y)) return 1;  // not equal
        y = %ToPrimitive(y, NO_HINT);
      }
    } else if (IS_BOOLEAN(x)) {
      if (IS_BOOLEAN(y)) return %_ObjectEquals(x, y) ? 0 : 1;
      if (IS_NULL_OR_UNDEFINED(y)) return 1;
      if (IS_NUMBER(y)) return %NumberEquals(%ToNumber(x), y);
      if (IS_STRING(y)) return %NumberEquals(%ToNumber(x), %ToNumber(y));
      // y is object.
      x = %ToNumber(x);
      y = %ToPrimitive(y, NO_HINT);
    } else if (IS_NULL_OR_UNDEFINED(x)) {
      return IS_NULL_OR_UNDEFINED(y) ? 0 : 1;
    } else {
      // x is an object.
      if (IS_SPEC_OBJECT(y)) {
        return %_ObjectEquals(x, y) ? 0 : 1;
      }
      if (IS_NULL_OR_UNDEFINED(y)) return 1;  // not equal
      if (IS_BOOLEAN(y)) y = %ToNumber(y);
      x = %ToPrimitive(x, NO_HINT);
    }
  }
}
  V8的代码看上去很凌乱,但是这是为了效率而这样做的。不过在开头的地方来一句判断字符串并返回,我看到它有点蛋疼。虽然知道是在做效率优化,但是感觉这一句破坏了整个函数的美。后面的实现有点像表格查询,那些while语句是为了把对象类型(SPEC_OBJECT)转换成基础类型(Primitive)。代码中使用的那些函数,完全可以根据命名知道作用,就不需要我解释了。它的实现过程是先判断类型,如果是基础类型就做相应转换后再比较。如果是对象类型,就把对象类型转换为基础类型再重复原来的动作。ToPrimitive这个函数也有点凌乱,我也帖出来好了。 function ToPrimitive(x, hint) {
  // Fast case check.
  if (IS_STRING(x)) return x;
  // Normal behavior.
  if (!IS_SPEC_OBJECT(x)) return x;
  if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT;
  return (hint == NUMBER_HINT) ? %DefaultNumber(x) : %DefaultString(x);
}
function DefaultNumber(x) {
  var valueOf = x.valueOf;
  if (IS_SPEC_FUNCTION(valueOf)) {
    var v = %_CallFunction(x, valueOf);
    if (%IsPrimitive(v)) return v;
  }

  var toString = x.toString;
  if (IS_SPEC_FUNCTION(toString)) {
    var s = %_CallFunction(x, toString);
    if (%IsPrimitive(s)) return s;
  }

  throw %MakeTypeError('cannot_convert_to_primitive', []);
}
function DefaultString(x) {
  var toString = x.toString;
  if (IS_SPEC_FUNCTION(toString)) {
    var s = %_CallFunction(x, toString);
    if (%IsPrimitive(s)) return s;
  }

  var valueOf = x.valueOf;
  if (IS_SPEC_FUNCTION(valueOf)) {
    var v = %_CallFunction(x, valueOf);
    if (%IsPrimitive(v)) return v;
  }

  throw %MakeTypeError('cannot_convert_to_primitive', []);
}
  这个函数的功能判断valueOf和toString这两个函数的存在性,调用后还要判断返回值是不是基础类型,如果是则返回。不是抛出异常。还用了个参数给日期类型来了个特殊判断。一般对象是先判断valueOf再判断toString的,日期对象是顺序颠倒的。虽然一直知道日期对象直接输出的是字符串,不过从来没在意过这个,现在也是看了V8的代码才知道是这个地方在捣鬼的。
  如果按照这个代码,想记住这个“==”的实现过程还真不容易,因为为了效率它用表格模式对变量进行比较的,所以会有很多分支。虽然ECMA-262上有说明,但是我也总结下这个“==”运算符的实现规则:
    1. 基础类型和基础类型比较
      1.1 操作数都转换成数值型来比较,转换规则复参见ECMA-262第九章
      1.2 两个操作数都是字符串类型时直接比较不需要转换
    2. 对象类型和基础类型比较
      2.1 如果存在valueOf方法并且返回值是基础类型,则用这个返回值来做比较
      2.2 当valueOf不存在或返回不为基础类型时,使用toString做2.1中的处理
      2.3 日期类型是先判断toString再判断valueOf
    3. 对象类型和对象类型比较
      3.1 比较对象指针指向的地址是否相同。
    4. null和undefined的特殊处理
      4.1 null和undefined与任何值都不相等
      4.2 null和undefined互相比较可以相等
    *. 上面的规则中如果有矛盾,取序号大的规则
  还是感觉太乱了= =。果然还是看代码理解比较好。或者使用简单化规则原理来理解,也就是看上面链接出的那篇文章。不过得补充上2.3这条规则。
网名:
54.144.24.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^