JavaScript内核与高级编程之:`JavaScript` 的 `Symbol.toStringTag`:其在 `Object.prototype.toString` 中的作用。

各位老铁,双击666!咱们今天唠唠 Symbol.toStringTag 这玩意儿!

大家好,我是你们的老朋友,今天咱们不聊妹子,不聊游戏,就来聊聊 JavaScript 里一个比较隐蔽,但又有点意思的小家伙 —— Symbol.toStringTag。别怕,虽然名字里带 Symbol,听起来好像很高大上,但其实理解起来很简单,保证听完之后,你也能对着 Object.prototype.toString 喊一声 "666"!

啥是 Object.prototype.toString

首先,我们先来复习一下 Object.prototype.toString。这玩意儿是 JavaScript 里所有对象都继承的一个方法。简单来说,它能把任何对象都“变成”一个字符串,但是这个字符串不是你想怎么变就怎么变,而是有一定格式的:"[object Type]"

  • [object 是固定的前缀。
  • Type 是对象的“类型”。
  • ] 是固定的后缀。

举个例子:

const arr = [1, 2, 3];
console.log(Object.prototype.toString.call(arr)); // "[object Array]"

const date = new Date();
console.log(Object.prototype.toString.call(date)); // "[object Date]"

const obj = { a: 1 };
console.log(Object.prototype.toString.call(obj)); // "[object Object]"

const num = 123;
console.log(Object.prototype.toString.call(num)); // "[object Number]"

const str = "hello";
console.log(Object.prototype.toString.call(str)); // "[object String]"

const bool = true;
console.log(Object.prototype.toString.call(bool)); // "[object Boolean]"

const nul = null;
console.log(Object.prototype.toString.call(nul)); // "[object Null]"

const undef = undefined;
console.log(Object.prototype.toString.call(undef)); // "[object Undefined]"

function Person() {}
const person = new Person();
console.log(Object.prototype.toString.call(person)); // "[object Object]"

看到了吧?通过 Object.prototype.toString.call(),我们可以拿到一个对象的类型字符串。 注意,我们用的是 call 方法,因为 toStringObject.prototype 上的方法,我们需要指定 this 的指向,也就是要检查的对象。

Type 是怎么决定的?

那么问题来了,这个 Type 到底是怎么决定的? 答案是:这取决于对象的内部属性 [[Class]](注意是双中括号,表示内部属性,JavaScript 代码无法直接访问)。

在 ES5 之前,Object.prototype.toString 就是通过读取对象的 [[Class]] 属性来确定 Type 的。但是,ES6 引入了 Symbol.toStringTag,改变了这一机制。

Symbol.toStringTag 登场!

Symbol.toStringTag 是一个 ES6 引入的 Symbol 类型的属性。它可以让你自定义 Object.prototype.toString 返回的类型字符串里的 Type 值。 也就是说,你可以控制 [object Type] 中的 Type 是什么。

简单来说,如果一个对象有 Symbol.toStringTag 属性,那么 Object.prototype.toString 就会优先使用这个属性的值,而不是去读取内部的 [[Class]] 属性。

代码演示,安排!

光说不练假把式,咱们直接上代码:

class MyClass {
  constructor() {
    this.name = "My Instance";
  }

  get [Symbol.toStringTag]() {
    return 'MyCoolClass';
  }
}

const instance = new MyClass();
console.log(Object.prototype.toString.call(instance)); // "[object MyCoolClass]"

看明白了吗? 我们给 MyClass 定义了一个 Symbol.toStringTag 的 getter,返回了字符串 'MyCoolClass'。 所以,当我们调用 Object.prototype.toString.call(instance) 的时候,返回的字符串就变成了 "[object MyCoolClass]",而不是默认的 "[object Object]"

再来一个例子,用普通对象:

const myObject = {
  [Symbol.toStringTag]: 'MyCustomObject'
};

console.log(Object.prototype.toString.call(myObject)); // "[object MyCustomObject]"

是不是很简单? 只要给对象添加一个 Symbol.toStringTag 属性,就能控制 Object.prototype.toString 的输出了。

Symbol.toStringTag 的应用场景

那么,Symbol.toStringTag 有什么用呢? 主要有以下几个场景:

  1. 自定义类的类型标识: 正如上面的例子,你可以用 Symbol.toStringTag 来区分不同的类,让 Object.prototype.toString 返回更具意义的类型字符串。这在调试和日志记录的时候非常有用。

  2. 模拟内置类型: 有时候,你想让你的对象看起来像内置的类型,比如 ArrayDateSymbol.toStringTag 可以帮你做到这一点。

  3. 类型判断: 虽然不推荐直接使用 Object.prototype.toString 来进行类型判断(因为有更好的方法,比如 instanceoftypeof),但在某些特殊情况下,它可以作为一种辅助手段。

模拟内置类型,玩的就是心跳!

咱们来玩一个更有趣的,用 Symbol.toStringTag 模拟 Array 类型:

const myArrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.toStringTag]: 'Array' // 关键!
};

console.log(Object.prototype.toString.call(myArrayLike)); // "[object Array]"
console.log(Array.isArray(myArrayLike)); // false

看到没? 虽然 myArrayLike 看起来很像一个数组,但它本质上只是一个普通对象。 但是,由于我们给它设置了 Symbol.toStringTag'Array',所以 Object.prototype.toString.call(myArrayLike) 返回了 "[object Array]"。 需要注意的是,Array.isArray(myArrayLike) 仍然会返回 false,因为 Array.isArray 是通过原型链来判断的,而不是通过 Symbol.toStringTag

Symbol.toStringTag 与内置对象

其实,JavaScript 的很多内置对象都使用了 Symbol.toStringTag。 你可以通过以下方式查看:

console.log(Symbol.toStringTag in Array.prototype); // true
console.log(Symbol.toStringTag in Map.prototype);   // true
console.log(Symbol.toStringTag in Set.prototype);   // true
console.log(Symbol.toStringTag in Promise.prototype); // true
// ... 还有很多

这意味着,这些内置类型的 Object.prototype.toString 行为,也是通过 Symbol.toStringTag 来控制的。

一个更复杂的例子,挑战一下!

假设我们要创建一个自定义的集合类 MySet,并且希望它的 Object.prototype.toString 返回 "[object MySet]"

class MySet {
  constructor(iterable) {
    this._data = new Set(iterable);
  }

  add(value) {
    this._data.add(value);
    return this;
  }

  delete(value) {
    return this._data.delete(value);
  }

  has(value) {
    return this._data.has(value);
  }

  get size() {
    return this._data.size;
  }

  [Symbol.iterator]() {
    return this._data[Symbol.iterator]();
  }

  get [Symbol.toStringTag]() {
    return 'MySet';
  }
}

const mySet = new MySet([1, 2, 3]);
console.log(Object.prototype.toString.call(mySet)); // "[object MySet]"
console.log(mySet instanceof MySet); // true

在这个例子中,我们定义了一个 MySet 类,它内部使用 Set 来存储数据,并实现了 adddeletehas 等方法。 关键在于,我们给 MySet 类添加了一个 Symbol.toStringTag 的 getter,返回了字符串 'MySet'。 这样,当我们调用 Object.prototype.toString.call(mySet) 的时候,返回的字符串就是 "[object MySet]",而不是默认的 "[object Object]"

Symbol.toStringTag 的注意事项

虽然 Symbol.toStringTag 很强大,但也需要注意以下几点:

  1. 只能是字符串: Symbol.toStringTag 的值必须是一个字符串,否则会被忽略。

  2. 只影响 Object.prototype.toString Symbol.toStringTag 只影响 Object.prototype.toString 的输出,不会影响其他的类型判断方法,比如 instanceoftypeof

  3. 不要滥用: 虽然你可以用 Symbol.toStringTag 来模拟内置类型,但要谨慎使用,避免造成混淆。 一般来说,只有在确实需要自定义类型标识的时候才使用 Symbol.toStringTag

总结

Symbol.toStringTag 是一个 ES6 引入的 Symbol 类型的属性,它可以让你自定义 Object.prototype.toString 返回的类型字符串里的 Type 值。 它主要用于自定义类的类型标识、模拟内置类型和辅助类型判断。 但是,要谨慎使用,避免滥用。

为了方便大家理解,我把今天讲的内容整理成一个表格:

特性 描述
Object.prototype.toString JavaScript 里所有对象都继承的一个方法,它能把任何对象都“变成”一个字符串,格式为 "[object Type]"
[[Class]] 对象的内部属性,用于确定 Object.prototype.toString 返回的 Type 值。 ES5 之前,Object.prototype.toString 就是通过读取对象的 [[Class]] 属性来确定 Type 的。 JavaScript 代码无法直接访问。
Symbol.toStringTag ES6 引入的 Symbol 类型的属性,可以让你自定义 Object.prototype.toString 返回的类型字符串里的 Type 值。 如果一个对象有 Symbol.toStringTag 属性,那么 Object.prototype.toString 就会优先使用这个属性的值,而不是去读取内部的 [[Class]] 属性。
应用场景 1. 自定义类的类型标识 2. 模拟内置类型 3. 类型判断(辅助手段)
注意事项 1. Symbol.toStringTag 的值必须是一个字符串,否则会被忽略。 2. Symbol.toStringTag 只影响 Object.prototype.toString 的输出,不会影响其他的类型判断方法,比如 instanceoftypeof。 3. 不要滥用,一般来说,只有在确实需要自定义类型标识的时候才使用 Symbol.toStringTag

好了,今天的分享就到这里。 希望大家有所收获,下次再见! 记得点赞关注哦!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注