1. JS 数据类型
最新的 ECMAScript 标准定义了 9 种数据类型:
- 7 种基本数据类型(新增了 Symbol 和 BigInt),使用 typeof 运算符进行检查
string
number
boolean
null
undefined
Symbol
BigInt
- 引用数据类型:
Object
和Function
2. typeof 操作符
- 检查数据类型,返回一个表示类型的字符串
- string:
typeof instance; // 'string'
- number:
typeof instance; // 'number'
- boolean:
typeof instance; // 'boolean'
- null:
typeof instance; // 'object'
(特殊) - undefined:
typeof instance; // undefined'
- Symbol:
typeof instance; // 'symbol'
- BigInt:
typeof instance; // 'bigint'
- 除 Function 外的所有构造函数通过 typeof 判断都返回
object
- Object:
typeof instance; // "object"
- Function:
typeof instance; // "function"
typeof []; // "object"
typeof {}; // "object"
var str = new String("String");
var num = new Number(100);
typeof str; // 返回 'object'
typeof num; // 返回 'object'
var func = new Function();
typeof func; // 返回 'function'
- 特殊
typeof NaN === 'number'
,尽管它是Not-A-Number (非数值)
的缩写typeof null === 'object'
,因为计算机底层是二进制表示的类型标签,对象的类型标签是 0,而空指针 null 类型标签也是 0。
- 块级作用域与暂时性死区
- 在 ECMAScript 2015 之前,使用 typeof 进行类型判断总是安全的。
- 但 ES6 开始,在变量被声明之前对块中的 let 和 const 变量使用 typeof 会抛出一个 ReferenceError。块作用域变量在块的头部处于“暂存死区”,直至其被初始化,在这期间,访问变量将会引发错误。
3. instanceof 运算符
用于检测构造函数的 prototype 是否出现在某个实例对象的原型链上。(也就是判断实例对象的 __proto__ 属性
是否指向了构造函数的 prototype 原型对象
,或者是否在一条原型链上!)
语法: object instanceof constructor
- object 是实例对象
- constructor 是构造函数
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
const auto = new Car("Honda", "Accord", 1998);
auto instanceof Car; // true, auto.__proto__ == Car.prototype
auto instanceof Object; // true, Car.prototype.__proto__ == Object.prototype,所以 auto.__proto__.__proto__ == Object.prototype
tips: 原型链相关可以参考这篇文章~
再来几个 🌰
var str = "abc";
str instanceof String; // false!! 非实例对象
var myString = new String();
myString instanceof String; // true
myString instanceof Object; // true
var myDate = new Date();
myDate instanceof Date; // true
myDate instanceof Object; // true
myDate instanceof String; // false
var myObj = {};
var myNonObj = Object.create(null);
myObj instanceof Object; // true
myNonObj instanceof Object; // false!! 创建非 Object 实例的对象
Symbol.hasInstance 属性
ES6 提供了 11 个 内置的 Symbol 值,指向语言内部使用的方法来实现
。ES6 之前并没有暴露给开发者。
instanceof
运算符,其实底层调用的就是 Symbol.hasInstance
属性,用于判断某对象是否为某构造器的实例。
- 当使用
instanceof
运算符时,会调用内部的Symbol.hasInstance属性
,然后执行语言内部的方法来判断某对象是否为某构造器的实例。 - 比如执行
foo instanceof Foo
,在语言内部实际调用的是Foo[Symbol.hasInstance](foo)
方法
// Demo1
function func() {
this.a = 123;
}
let f = new func();
f instanceof func; // true
func[Symbol.hasInstance](f); // true
// Demo2
class MyFunc {}
MyFunc[Symbol.hasInstance]; // ƒ [Symbol.hasInstance]() { [native code] }
// Demo3
let obj = {};
obj instanceof Object; // true
Object[Symbol.hasInstance]; // ƒ [Symbol.hasInstance]() { [native code] }
实现一个自定义的 instanceof
// 重写Even[Symbol.hasInstance]的静态方法
class Even {
// 静态方法,直接在类上调用 Even[Symbol.hasInstance],而不是在实例上调用。不会被实例继承,但是会被子类继承
static [Symbol.hasInstance](obj) {
return Number(obj) % 2 === 0;
}
}
// 自动调用 Even[Symbol.hasInstance]()
1 instanceof Even; // false
let e = new Even();
e instanceof Even; // false 因为被重写了 !
4. Object.prototype.toString.call()
为了每个对象都能通过 Object.prototype.toString()
来检测,需要以 call()
的形式来调用,传递要检查的对象作为第一个参数,它的返回值代表该对象的 [object 数据类型]
字符串表示。
Object.prototype.toString.call("foo"); // "[object String]"
Object.prototype.toString.call(3); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(new Array()); // "[object Array]"
Object.prototype.toString.call(new Map()); // "[object Map]"
Object.prototype.toString.call(new Function()); // [object Function]
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call(new RegExp()); // [object RegExp]
Object.prototype.toString.call(new Error()); // [object Error]
Object.prototype.toString.call(function* () {}); // "[object GeneratorFunction]"
Object.prototype.toString.call(Promise.resolve()); // "[object Promise]"
Object.prototype.toString.call(Math); // [object Math]
Object.prototype.toString.call(document); // [object HTMLDocument]
Object.prototype.toString.call(window); //[object global]
// ...
那么,为什么 Object.prototype.toString.call() 可以判断类型?
回顾 Object.prototype.toString()
该方法返回 表示该对象的字符串
。所有对象都有 toString()
方法,因为它是 Object
原型上的方法,被每个 Object
对象继承。
toString()
默认返回 [object type]
,其中 type 是对象的类型。
// 先来看下 toString() 方法的效果
Number(123).toString(); // "123"
[(1, 2, 3)].toString(); // "1,2,3"
new Date().toString(); // "Thu Mar 25 2021..."
let obj = { a: 1 };
obj.toString(); // "[object Object]"
function Dog(name) {
this.name = name;
}
let dog = new Dog("Gabby");
dog.toString(); // "[object Object]"
Dog.toString(); // "function Dog(name) {this.name = name;}"
// 可以重写 toString 来获取我们想要的值
Dog.prototype.toString = function dogToString() {
return `${this.name}`;
};
dog.toString(); // "Gabby"
为什么 obj.toString() 和 Object.prototype.toString.call(obj) 的结果不一样?
var a = {};
var b = [];
var c = 1;
Object.prototype.toString.call(a); //[object,Object]
Object.prototype.toString.call(b); //[object,Array]
Object.prototype.toString.call(c); //[object,Number]
Object.prototype.toString.call(a) === "[object Object]"; // true
因为 toString
是 Object
的原型方法,Array
、Function
等类型作为 Object
的实例,都 重写的了 toString 方法
。
因此在调用时,是调用了重写后的方法,而不是原型链上的 toString()方法
。 举个例子验证一下 ⬇️
let arr = [1, 2, 3];
Array.prototype.hasOwnProperty("toString"); // true
arr.toString(); // "1,2,3"
delete Array.prototype.toString; // delete 操作符可以删除实例属性
Array.prototype.hasOwnProperty("toString"); // false
arr.toString(); // "[object Array]"
如果 toString
方法没有被重写的话,会返回 [Object type]
,是可以判断出类型的。但目前除了 Object 类型的对象外,其他类型的 toString 方法都会直接返回都是内容的字符串了。所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文,来达到调用 Object 原型上面 toString() 方法的目的
。
Object.prototype.toString.call() 原理分析
那么 toString
方法又是如何获取到不同数据的类型的呢?
这就涉及到了 Symbol
的内置属性 Symbol.toStringTag
!
Symbol.toStringTag 属性
对象的 Symbol.toStringTag 属性
,指向一个方法。在该对象上面调用 Object.prototype.toString()
时,如果这个属性存在,它的返回值会出现在 toString
方法返回的字符串之中,表示对象的类型。也就是 [object xxx]
的 xxx
部分。
- 通常只有内置的
Object.prototype.toString()
方法会去读取这个标签,并把它包含在自己的返回值里。 - 也就是说,当调用
Object
原型上面的toString()
方法时,会默认读取Symbol.toStringTag 属性
,给属性指向的语言内置方法可以用来判断不同的数据类型。
参考这张图是 toString
内部定义的规则,来判断不同的数据类型。
- 也可以通过重写
Symbol.toStringTag
,来自定义的类型标签
class Test {
get [Symbol.toStringTag]() {
return "my type";
}
}
let t = new Test();
t.toString(); // "[object my type]"
对比 Object.prototype.toString.call() 和 instanceof
-
Object.prototype.toString.call()
- 优点:对于所有基本的数据类型都能进行判断,包括
null
、undefined
,也可以检测出iframes
- 缺点:
不能精准判断自定义对象
,对于自定义对象只会返回[object Object]
- 优点:对于所有基本的数据类型都能进行判断,包括
-
instanceof
- 优点:可以弥补
Object.prototype.toString.call()
不能判断自定义实例化对象的缺点 - 缺点:只能用来判断对象类型,原始类型不可以;并且所有对象类型 instanceof Object 都是 true,不能检测出
iframes
- 优点:可以弥补
总的来说就是,Object.prototype.toString.call()
常用于判断浏览器内置对象,而instanceof
常用来判断对象实例。
END!!! 撒花✿✿ヽ(°▽°)ノ✿ ❀❀❀❀