基本概念

在javaScript中,可以使用 ==, ===,Object.is()进行类型的比较。两等号判等,会在比较时进行类型转换。三等号判等(判断严格),比较时不进行隐式类型转换,(类型不同则会返回false)。Object.is() 在三等号判等的基础上特别处理了 NaN 、-0 和 +0 ,保证 -0 和 +0 不再相同,但 Object.is(NaN, NaN) 会返回 true。Object.is() 应被认为有其特殊的用途,而不能用它认为它比其它的相等对比更宽松或严格。下面我们看一下javaScript的 ==。

  1. 1 == '1' // true
  2. 1 == true // true
  3. 1 == [] // false
  4. 1 == [1] // true
  5. 1 == {} // false
  6. true == "1" // true
  7. true == "true" // false
  8. true == [] // true
  9. true == [1] // true
  10. true == {} // false
  11. [] == "1"// false
  12. [] == true // false
  13. [] == false // true
  14. [] == [] // false
  15. [] == {} // false
  16. [] == ![] // true (注意!!!)
  17. {} == 1 // Unexpected token '=='
  18. {} == "1" // Unexpected token '=='
  19. {} == true // Unexpected token '=='
  20. {} == false // Unexpected token '=='
  21. {} == [] // Unexpected token '=='
  22. {} == {} // false
  23. "0" == null // false
  24. "0" == undefined // false
  25. "0" == false // true
  26. "0" == NaN // false
  27. "0" == 0 // true
  28. "0" == "" // false
  29. false == null // false
  30. false == undefined // false
  31. false == NaN // false
  32. false == 0 // true
  33. false == "" // true
  34. false == [] // true
  35. false == {} // false
  36. "" == null // false
  37. "" == undefined //false
  38. "" == NaN // false
  39. "" == 0 //true
  40. "" == [] // true
  41. "" == {} // false
  42. 0 == null // false
  43. 0 == undefined // false
  44. 0 == NaN // false
  45. 0 == [] // true
  46. 0 == {} // false
  47. +0 == -0 // true
  48. null == null // true
  49. null == undefined // true
  50. null == "" // false
  51. null == 0 // false
  52. undefined == "" // false
  53. undefined == 0 // false
  54. NaN == NaN // false

== 相等操作中,如果两边的操作数不同的话,都会进行类型转换,而且优先转为数字,再进行比较,如果转换后还不同则再次转换,直到相同为止。

  1. 字符串和数字之间的相等比较:
    1. 如果Type(x)是数字,Type(y)是字符串,则返回 x == ToNumber(y)的结果。
    2. 如果Type(x)是字符串,Type(y)是数字,则返回ToNumber(x) == y的结果。
  2. 其他类型值和布尔类型之间的相等比较:
    1. 如果Type(x)是布尔类型,这返回ToNumber(x) == y的结果
    2. 如果Type(y)是布尔类型,则返回x == ToNumber(y) 的结果
  3. null和undefined之间的相等比较
    1. 如果x为null,y为undefined,则结果为true
    2. 如果x为undefined,y为null,则结果为true
  4. 对象和非对象之间的相等比较:
    1. 如果Type(x)是字符串或者数字,Type(y)是对象,则返回x == ToPrimitive(y)的结果
    2. 如果Type(x)是对象,Type(y)是字符串或者数字,则返回toPrimitive(x) == y的结果
  5. 如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
  6. 如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。

44444444444444.jpg
在数据类型我们知道v8的数据的类型继承关系。在v8::Value中定义了两个函数。这两个函数只要是做数据类型的比较。

class V8_EXPORT Value : public Data {
 public:
  // 相对与javaScript的 == 
  V8_WARN_UNUSED_RESULT Maybe<bool> Equals(Local<Context> context,
                                           Local<Value> that) const;
  // 相对与javaScript的 ===                                          
  bool StrictEquals(Local<Value> that) const;
};

这就是javaScript在v8的入口。下面让我们看这两个函数的具体实现,为你解密javaScript的数据类型比较在javaScript中的实现。

Equals()

Maybe<bool> Value::Equals(Local<Context> context, Local<Value> that) const {
  i::Isolate* isolate = Utils::OpenHandle(*context)->GetIsolate();
  ENTER_V8(isolate, context, Value, Equals, Nothing<bool>(), i::HandleScope);  
  auto self = Utils::OpenHandle(this);
  auto other = Utils::OpenHandle(*that);
  // 正真执行类型的比较函数。  
  Maybe<bool> result = i::Object::Equals(isolate, self, other);
  has_pending_exception = result.IsNothing();
  RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
  return result;
}

在上面的实现中我们可以看到真的执行类型比较的是v8::internal::Object::Equals()函数。我们看一下具体的实现。我们把左边的作为X,右边作为Y。对下面的方法比较逐个解析。

Maybe<bool> Object::Equals(Isolate* isolate, Handle<Object> x,
                           Handle<Object> y) {
  while (true) {
    if (x->IsNumber()) {
      if (y->IsNumber()) {
        return Just(StrictNumberEquals(x, y));
      } else if (y->IsBoolean()) {
        return Just(
            StrictNumberEquals(*x, Handle<Oddball>::cast(y)->to_number()));
      } else if (y->IsString()) {
        return Just(StrictNumberEquals(
            x, String::ToNumber(isolate, Handle<String>::cast(y))));
      } else if (y->IsBigInt()) {
        return Just(BigInt::EqualToNumber(Handle<BigInt>::cast(y), x));
      } else if (y->IsJSReceiver()) {
        if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y))
                 .ToHandle(&y)) {
          return Nothing<bool>();
        }
      } else {
        return Just(false);
      }
    } else if (x->IsString()) {
      if (y->IsString()) {
        return Just(String::Equals(isolate, Handle<String>::cast(x),
                                   Handle<String>::cast(y)));
      } else if (y->IsNumber()) {
        x = String::ToNumber(isolate, Handle<String>::cast(x));
        return Just(StrictNumberEquals(x, y));
      } else if (y->IsBoolean()) {
        x = String::ToNumber(isolate, Handle<String>::cast(x));
        return Just(
            StrictNumberEquals(*x, Handle<Oddball>::cast(y)->to_number()));
      } else if (y->IsBigInt()) {
        return BigInt::EqualToString(isolate, Handle<BigInt>::cast(y),
                                     Handle<String>::cast(x));
      } else if (y->IsJSReceiver()) {
        if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y))
                 .ToHandle(&y)) {
          return Nothing<bool>();
        }
      } else {
        return Just(false);
      }
    } else if (x->IsBoolean()) {
      if (y->IsOddball()) {
        return Just(x.is_identical_to(y));
      } else if (y->IsNumber()) {
        return Just(
            StrictNumberEquals(Handle<Oddball>::cast(x)->to_number(), *y));
      } else if (y->IsString()) {
        y = String::ToNumber(isolate, Handle<String>::cast(y));
        return Just(
            StrictNumberEquals(Handle<Oddball>::cast(x)->to_number(), *y));
      } else if (y->IsBigInt()) {
        x = Oddball::ToNumber(isolate, Handle<Oddball>::cast(x));
        return Just(BigInt::EqualToNumber(Handle<BigInt>::cast(y), x));
      } else if (y->IsJSReceiver()) {
        if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y))
                 .ToHandle(&y)) {
          return Nothing<bool>();
        }
        x = Oddball::ToNumber(isolate, Handle<Oddball>::cast(x));
      } else {
        return Just(false);
      }
    } else if (x->IsSymbol()) {
      if (y->IsSymbol()) {
        return Just(x.is_identical_to(y));
      } else if (y->IsJSReceiver()) {
        if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y))
                 .ToHandle(&y)) {
          return Nothing<bool>();
        }
      } else {
        return Just(false);
      }
    } else if (x->IsBigInt()) {
      if (y->IsBigInt()) {
        return Just(BigInt::EqualToBigInt(BigInt::cast(*x), BigInt::cast(*y)));
      }
      return Equals(isolate, y, x);
    } else if (x->IsJSReceiver()) {
      if (y->IsJSReceiver()) {
        return Just(x.is_identical_to(y));
      } else if (y->IsUndetectable()) {
        return Just(x->IsUndetectable());
      } else if (y->IsBoolean()) {
        y = Oddball::ToNumber(isolate, Handle<Oddball>::cast(y));
      } else if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(x))
                      .ToHandle(&x)) {
        return Nothing<bool>();
      }
    } else {
      return Just(x->IsUndetectable() && y->IsUndetectable());
    }
  }
}

当X为数字

当Y为数字

当x和y都为数字类型的时候。执行 StrictNumberEquals(Handle x, Handle y)。从下面实现中我们看到,把x,y的值进行数字类型的转换。最后需要通过std::isnan()进行不是NAN(非数字类型)判断。

StrictNumberEquals(x,y);
bool StrictNumberEquals(Handle<Object> x, Handle<Object> y) {
  return StrictNumberEquals(*x, *y);
}
bool StrictNumberEquals(const Object x, const Object y) {
  return StrictNumberEquals(x.Number(), y.Number());
}
bool StrictNumberEquals(double x, double y) {
  if (std::isnan(x) || std::isnan(y)) return false;
  return x == y;
}

当Y是boolean类型

当Y为boolean的时候。对boolean类型进行数字的转换。Oddball 是一个描述 null, undefined, true,false。的类。

StrictNumberEquals(*x, Handle<Oddball>::cast(y)->to_number());
bool StrictNumberEquals(const Object x, const Object y) {
  return StrictNumberEquals(x.Number(), y.Number());
}
bool StrictNumberEquals(double x, double y) {
  if (std::isnan(x) || std::isnan(y)) return false;
  return x == y;
}

当Y是字符串

当Y为字符串的时候.对字符串强制转换成数字类型。再做比较。String::ToNumber是把字符串转成数字类型的方法

StrictNumberEquals(x, String::ToNumber(isolate, Handle<String>::cast(y)));

bool StrictNumberEquals(Handle<Object> x, Handle<Object> y) {  
  return StrictNumberEquals(*x, *y);
}
bool StrictNumberEquals(const Object x, const Object y) {
  return StrictNumberEquals(x.Number(), y.Number());
}
bool StrictNumberEquals(double x, double y) {
  if (std::isnan(x) || std::isnan(y)) return false;
  return x == y;
}

当Y为大整数的时候

当Y为大整数的时候,将X转成大整数,再通过igInt::EqualToNumber(Handle x, Handle y)函数进行大整数的数据比较。

BigInt::EqualToNumber(Handle<BigInt>::cast(y), x);

 // 如果 x,y其中一个为NaN,无穷大,无穷小。return false
bool BigInt::EqualToNumber(Handle<BigInt> x, Handle<Object> y) {
  // 如果Y是小整数  
  if (y->IsSmi()) {
    int value = Smi::ToInt(*y);
    if (value == 0) return x->is_zero();
    // 任何多位数的BigInt都大于Smi。
    return (x->length() == 1) && (x->sign() == (value < 0)) &&
           (x->digit(0) ==
            static_cast<digit_t>(std::abs(static_cast<int64_t>(value))));
  }
  // 把y转成double类型
  double value = Handle<HeapNumber>::cast(y)->value(); 
  return CompareToDouble(x, value) == ComparisonResult::kEqual;
}

当Y为对象类型

当Y为对象类型的时候。这个是一种特殊情况。如果Y不能执行基本类型的转换的时候,转换失败的时候。发返回一个空的 Maybe。

 if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y))
                 .ToHandle(&y)) {
          return Nothing<bool>();
 }

StrictEquals()