toString

toString 可以用来判断数据类型. 可以把它看作是 typeof 的加强版,因为它不仅可以检测原始类型,还可以区分 object。

[[Class]]

我们需要先来说一下 [[Class]] 这个内部属性(只要是 [[]] 都是 js的内部属性,是不能够直接访问的),你不要认为它是与类有关系的内部属性,你可以把它看作是一种分类的方法。我们可以间接地通过在这个值上借用默认的 Object.prototype.toString() 来调用展示。

Object.prototype.toString.call([1,2,3]); // [object Array];
Object.prototype.toString.call(//); // [object RegExp]

看,我们可以得到不同于 typeof 的 object。

其实,[[Class]] 对应的是关联这个值的内建的原生类型构造器。

null、undefined

注意了,上面说了 [[Class]] 对应的原生构造器,但是 null、undefined 是没有的。

Object.prototype.toString.call(null);  // [object Null];
Object.prototype.toString.call(undefined); // [object Undefined]

明明 null、undefined 是没有原生构造器的,那么为什么 [[Class]] 还能返回类型,这跟实现有关,在算法中其实是单独规定了 null、undefined 的行为。

string、number、boolean

Object.prototype.toString.call(1);  // [object Number];
Object.prototype.toString.call('1'); // [object String]
Object.prototype.toString.call(true); // [object Boolean]

除了 null、undefined 没有原生构造器,其他的类型都有。虽然这里使用的是 原始数据类型。但是在算法执行过程中,是会启用简单数据类型的封箱(看包装类型章节)操作,也就是说,它们实际上会用它们的构造器。

toString 算法

上面的疑惑你讲会在这里得到解答。在 toString 内部是使用的 this(this章节) ,从这里我们也能看出,真正的使用 this 是多么的强大,而不是传入对象环境(题外话)。

  1. 如果 this 值是 undefined, 就返回 [object Undefined].
  2. 如果 this 值是 null, 就返回 [object Null].
  3. 让 O 成为 ToObject(this).
  4. 让 class 成为 O 的内部属性 [[Class]] 的值。
  5. 最后返回由 ‘[object’ 和 class 和 ‘]’ 三部分组成的字符串。

看到了吗 1. 2点的处理。 以及 3 封箱的一个操作。

Symbol.toStringTag

上面的都是原生类型。String、Number、Boolean、Array、Object、Function、RegExp…

我们可以自定义类型。我们需要借助 Symbol.toStringTag. 原生类型上是没有这个属性的,但是对于大多数的特定环境的对,都会有这么一个属性. 比如:window、XMLHttpRequest。

let person = {
    [Symbol.toStringTag]: 'Person'
}
Object.prototype.toString.call(person); // [object Person];

小问题。

我们借用 Object.prototype.toString 可以获取类型。我们也可以使用 {}.toString 这种获取。但是注意:如果我们重写了 {} 上的 toString 方法,就会遮蔽 Object.prototype 上的 toString 属性。也就不能正确的返回类型

let obj = {
    toString() {
        return '[object Number]';
    }
};
obj.toString();  // '[object Number]'; 错了。

但是我们使用 Object.prototype 上的 toString, 因为是内置的,所以一定遮蔽不了。