我们知道,Object.prototype.toString.call 可以正确的判断 js 中数据类型,那么是啥原理呢?

V8(3.14.5) src/api.cc 第 2988 行,我们可以发现:

Local<String> v8::Object::ObjectProtoToString() {
  i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate();
  ON_BAILOUT(isolate, "v8::Object::ObjectProtoToString()",
             return Local<v8::String>());
  ENTER_V8(isolate);
  i::Handle<i::JSObject> self = Utils::OpenHandle(this);

  i::Handle<i::Object> name(self->class_name());

想必最后这个 self->class_name() 应该就是我们想要的。

点开看一下,来到了 src/object.cc:

String* JSReceiver::class_name() {
  if (IsJSFunction() && IsJSFunctionProxy()) {
    return GetHeap()->function_class_symbol();
  }
  if (map()->constructor()->IsJSFunction()) {
    JSFunction* constructor = JSFunction::cast(map()->constructor());
    return String::cast(constructor->shared()->instance_class_name());
  }
  // If the constructor is not present, return "Object".
  return GetHeap()->Object_symbol();
}

那么这样大概就清楚了。笼统而不严谨地说,原理就是当前返回实例的v8对象对应的C++构造对象类名(class)。

首先判断此对象是不是函数,这里有个注意的点,Object.prototype.toString.call(Array) 的结果是 [object Function],所以不仅对于Function,而且包括Array这些返回的都是函数;其次,判断此对象的constructor的类型,这里就是我们每次都会走到的地方。如果它的constructor确实是个函数,代表它是这个构造函数创建的实例,比如 [1,2,3] 是 Array 的实例,那么创建一个临时变量,从它的 field 中取出实例名称。最后,如果constructor指向有问题的话直接返回 'Object',最后的结果就是 [object Object]

其实这篇有点水了感觉,如果哪里说错了请不要骂我= =