转从深入到通俗Object.prototype.toString.call()

原文地址:https://zhuanlan.zhihu.com/p/118793721

没有废话,直入主题。

一、Object.prototype.toString() 的调用

对于 Object.prototype.toString() 方法,会返回一个形如 "[object XXX]" 的字符串。

如果对象的 toString() 方法未被重写,就会返回如上面形式的字符串。

({}.toString()); // => "[object Object]"
Math.toString(); // => "[object Math]"

但是,大多数对象,toString() 方法都是重写了的,这时,需要用 call()Reflect.apply() 等方法来调用。

var x = {
  toString() {
    return "X";
  },
};

x.toString(); // => "X"

Object.prototype.toString.call(x); // => "[object Object]"

Reflect.apply(Object.prototype.toString, x, []); // => "[object Object]"

二、Object.prototype.toString() 的原理

对于 Object.prototype.toString.call(arg),若参数为 nullundefined,直接返回结果。

Object.prototype.toString.call(null); // => "[object Null]"

Object.prototype.toString.call(undefined); // => "[object Undefined]"

若参数不为 nullundefined,则将参数转为对象,再作判断。对于原始类型,转为对象的方法即装箱,此处不赘述。

转为对象后,取得该对象的 [Symbol.toStringTag] 属性值(可能会遍历原型链)作为 tag,如无该属性,或该属性值不为字符串类型,则依下表取得 tag, 然后返回 "[object " + tag + "]" 形式的字符串。

// Boolean 类型,tag 为 "Boolean"
Object.prototype.toString.call(true); // => "[object Boolean]"

// Number 类型,tag 为 "Number"
Object.prototype.toString.call(1); // => "[object Boolean]"

// String 类型,tag 为 "String"
Object.prototype.toString.call(""); // => "[object String]"

// Array 类型,tag 为 "String"
Object.prototype.toString.call([]); // => "[object Array]"

// Arguments 类型,tag 为 "Arguments"
Object.prototype.toString.call(
  (function () {
    return arguments;
  })()
); // => "[object Arguments]"

// Function 类型, tag 为 "Function"
Object.prototype.toString.call(function () {}); // => "[object Function]"

// Error 类型(包含子类型),tag 为 "Error"
Object.prototype.toString.call(new Error()); // => "[object Error]"

// RegExp 类型,tag 为 "RegExp"
Object.prototype.toString.call(/\d+/); // => "[object RegExp]"

// Date 类型,tag 为 "Date"
Object.prototype.toString.call(new Date()); // => "[object Date]"

// 其他类型,tag 为 "Object"
Object.prototype.toString.call(new (class {})()); // => "[object Object]"

下面为部署了 Symbol.toStringTag 的例子。可以看出,属性值期望是一个字符串,否则会被忽略。

var o1 = { [Symbol.toStringTag]: "A" };
var o2 = { [Symbol.toStringTag]: null };

Object.prototype.toString.call(o1); // => "[object A]"
Object.prototype.toString.call(o2); // => "[object Object]"

Symbol.toStringTag 也可以部署在原型链上:

class A {}
A.prototype[Symbol.toStringTag] = "A";
Object.prototype.toString.call(new A()); // => "[object A]"

新标准引入了 [Symbol.toStringTag] 属性,是为了把此方法接口化,用于规范新引入的对象对此方法的调用。但对于“老旧”的对象,就只能直接输出值,以保证兼容性。

三、部署了此属性的内置对象

下面,我列出所有部署了此属性的内置对象。

  1. 三个容器对象。这类对象用作命名空间,用于存储同一类方法。
JSON[Symbol.toStringTag]; // => "JSON"

Math[Symbol.toStringTag]; // => "Math"

Atomics[Symbol.toStringTag]; // => "Atomic"

这三个对象的 toString() 都没有重写,直接调用 toString() 方法也可以得到相同的结果。

JSON.toString(); // => "[object JSON]"

Math.toString(); // => "[object Math]"

Atomics.toString(); // => "[object Atomics]"

\2. 两个新引入的类型 BigIntSymbol

BigInt.prototype[Symbol.toStringTag]; // => "BigInt"

Symbol.prototype[Symbol.toStringTag]; // => "Symbol"

\3. 四个集合(Collection)对象

Set.prototype[Symbol.toStringTag]; // => "Set"

Map.prototype[Symbol.toStringTag]; // => "Map"

WeakSet.prototype[Symbol.toStringTag]; // => "WeakSet"

WeakMap.prototype[Symbol.toStringTag]; // => "WeakMap"

\4. ArrayBuffer 及其视图对象

ArrayBuffer.prototype[Symbol.toStringTag];       // => "ArrayBuffer"

SharedArrayBuffer.prototype[Symbol.toStringTag]; // => "SharedArrayBuffer"

DataView.prototype[Symbol.toStringTag];          // => "DataView"

%TypedArray%.prototype[Symbol.toStringTag];      // 返回各 %TypedArray% 的名称
/**
 * 各 %TypedArray%:
 *   Int8Array;
 *   Uint8Array;
 *   Uint8ClampedArray;
 *   Int16Array;
 *   Uint16Array;
 *   Int32Array;
 *   Uint32Array;
 *   BigInt64Array;
 *   BigUint64Array;
 *   Float32Array;
 *   Float64Array;
 */

其中,%TypedArray% 比较特殊。Symbol.toStringTag 并没有直接部署在各个 %TypedArray% 的原型对象上。

而是,各个 %TypedArray% 拥有同一个原型对象 %TypedArray%.prototypeSymbol.toStringTag 属性就部署在这个对象上。而且,该属性为一个只读访问器属性,访问该属性时,会根据 this 的类型来判断返回哪一个 %TypedArray% 的名称。

var p0 = Int8Array.prototype;
var p1 = Object.getPrototypeOf(Int8Array.prototype);
var p2 = Object.getPrototypeOf(BigUint64Array.prototype);

// 各个 %TypedArray% 具有同一个原型对象 %TypedArray%.prototype
p1 === p2; // => true
p1.constructor.name; // => "TypedArray"

// Symbol.toStringTag 即部署在 %TypedArray%.prototype 上
Object.getOwnPropertySymbols(p0).includes(Symbol.toStringTag); // => false
Object.getOwnPropertySymbols(p1).includes(Symbol.toStringTag); // => true

// 该属性为只读访问器属性
Object.getOwnPropertyDescriptor(p1, Symbol.toStringTag); // => { get: ...,
//      set: undefined,
//      ... }

// 调用该访问器属性时
// 如果 this 不为对象或 TypedArray,返回 undefined
// 否则返回该 TypedArray 的名称
var get = Object.getOwnPropertyDescriptor(p1, Symbol.toStringTag).get;
p1[Symbol.toStringTag]; // => undefined
get.call({}); // => undefined
get.call(null); // => undefined
get.call(new Set()); // => undefined
get.call(new Int8Array()); // => "Int8Array"
get.call(new BigInt64Array()); // => "BigInt64Array"

这个设计还是挺精妙的。

\5. Iterator 的一些实现。新标准实现了 Iterator 接口,以下实现也部署了该属性。

%ArrayIteratorPrototype%;         // => "Array Iterator"

%SetIteratorPrototype%;           // => "Set Iterator"

%MapIteratorPrototype%;           // => "Map Iterator"

%StringIteratorPrototype%;        // => "String Iterator"

%RegExpStringIteratorPrototype%;  // => "RegExp String Iterator"

例:

[][Symbol.iterator]()[Symbol.toStringTag]; // => "Array Iterator"

new Set().keys()[Symbol.toStringTag]; // => "Set Iterator"
new Set().values()[Symbol.toStringTag]; // => "Set Iterator"
new Set().entries()[Symbol.toStringTag]; // => "Set Iterator"

new Map().keys()[Symbol.toStringTag]; // => "Set Iterator"
new Map().values()[Symbol.toStringTag]; // => "Set Iterator"
new Map().entries()[Symbol.toStringTag]; // => "Set Iterator"

String.prototype[Symbol.iterator]()[Symbol.toStringTag]; // => "String Iterator"

"1a2b3c".matchAll(/\d/)[Symbol.toStringTag]; // => "RegExp String Iterator"

\6. 新引入的函数引型及其原型,包含 asyncgenerator 函数以及他们的组合

%GeneratorFunction%[Symbol.toStringTag];            // => "GeneratorFunction"

%GeneratorFunction%.prototype[Symbol.toStringTag];  // => "Generator"

%AsyncFunction%[Symbol.toStringTag];                // => "AsyncFunction"

%AsyncGenerator%[Symbol.toStringTag];               // => "AsyncGenerator"

%AsyncGeneratorFunction%[Symbol.toStringTag];       // => "AsyncGeneratorFunction"

例:

function* f1() {}

f1[Symbol.toStringTag]; // => "GeneratorFunction"

f1.prototype[Symbol.toStringTag]; // => "Generator"

async function f2() {}

f2[Symbol.toStringTag]; // => "AsyncFunction"

async function* f3() {}

f2[Symbol.toStringTag]; // => "AsyncGeneratorFunction"

f3.prototype[Symbol.toStringTag]; // => "AsyncGenerator"

实际上,Generator FunctionFunction 的子类;%GeneratorFunction%.prototypeIterator 的子类。

\7. 模块命名空间对象(Module Namespace Object)

新引入的模块命名空间对象(Module Namespace Object)也是部署了此属性的。

import * as module from "./export.js";

module[Symbol.toStringTag]; // => "Moduel"

\8. 另外,在不同的实现中,有些第三方对象也部署了此属性

比如在浏览器中:

Window.prototype[Symbol.toStringTag]; // => "Window"

HTMLElement.prototype[Symbol.toStringTag]; // => "HTMLElement"

Blob.prototype[Symbol.toStringTag]; // => "Blob"

在 Node.js 中:

global[Symbol.toStringTag]; // => "global"
posted @ 2021-10-11 13:34  __Bowen  阅读(147)  评论(0编辑  收藏  举报