总结:类型检查方法
| 用于 | 返回值 |
|-----------------------------------------------------------------------|-------------------------------------------|------------|
| typeof | 原始数据类型 | string |
| {}.toString | 原始数据类型,内建对象,包含 Symbol.toStringTag
属性的对象 | string |
| instanceof | 对象 | true/false |
| 使用类的层次结构(hierarchy),并想要对该类进行检查,同时还要考虑继承时,这种场景下 instanceof
操作符确实很出色。 | |
instanceof 操作符用于检查一个对象是否属于某个特定的 class。同时,它还考虑了继承。 instanceof
在检查中会将原型链考虑在内, 还可以在静态方法 Symbol.hasInstance
中设置自定义逻辑。
在 JavaScript 中,Symbol.hasInstance
是一个特殊的内置方法。 所有的函数(在 JavaScript 中,类就是特殊的函数)默认都会包含这个方法 ,它决定了 instanceof
操作符的行为。 默认情况下,如果您有一个对象 o
和一个类(也就是函数) C
,那么表达式 o instanceof C
会检查 o
的原型链是否包含 C.prototype
,如果包含,就返回 true
,否则返回 false
。所以默认情况下,instanceof
是检查一个对象是否是一个类的实例。
但 Symbol.hasInstance
方法提供了一个方式,允许我们自定义 instanceof
的行为。具体来说,就是我们可以在一个类上定义一个标记为 Symbol.hasInstance
的方法,这个方法接受一个参数,参数就是 instanceof
左侧的对象,然后返回一个布尔值个表示这个对象是否应该被视为这个类的实例。
js
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
};
console.log([] instanceof MyArray); // 输出:true
console.log([1, 2, 3] instanceof MyArray); // 输出:true
console.log({} instanceof MyArray); // 输出:false
- MyArray类定义了静态方法
[Symbol.hasInstance]
,用于自定义instanceof。 - 当执行[] instanceof MyArray时,空数组[]被传入了
[Symbol.hasInstance]
的instance参数。 - 在方法内部,对instance调用了Array.isArray进行判断。
- 空数组[]确实是一个数组,所以Array.isArray返回true。
- 因此静态方法也返回true,该表达式最终结果为true。
- 判断过程充分利用了
[Symbol.hasInstance]
和instance参数,自定义了instanceof的逻辑。 (1)问题[]
instanceof MyArray会自动调用静态方法?
- 当使用 instanceof 操作符时,语言内部会自动检查被操作对象的类型,以确定 instanceof 的行为。 具体来看,对于 [] instanceof MyArray 这一行
[]
是一个空数组,它的类型为 Array。- MyArray 是一个类。
- 语言内部发现 MyArray 是一个函数/类。
- 语言内部会自动检查 MyArray 是否有定义 Symbol.hasInstance 方法。
- 发现 MyArray 确实定义了该方法。
- 所以语言内部会自动调用这个方法,以定制 instanceof 的执行逻辑。
- 方法的返回值会成为 instanceof 表达式的结果。 参考词法环境分析: 当 JavaScript 解析器处理这样一个表达式时:
- 首先会看到
[]
是一个数组字面量,分析为一个数组对象。 - 然后看到
instanceof
是一个二元运算符。 - 根据运算符语法,它的右操作数通常是一个类构造函数。
- 这里右操作数是
MyArray
,解析器会分析这是一个类。 - 对于类,根据
instanceof
运算符的==语言规范==,如果类定义了Symbol.hasInstance
方法,则会自动调用该方法。 - 这里
MyArray
类确实定义了Symbol.hasInstance
。 - 因此根据实例运算符的==词法环境==,解析器会自动调用这个方法。 自动调用的根本原因是:
instanceof
语法规定了这种自动调用行为- 解析器会分析这种语法结构并调用 (2)为什么所有函数默认都有
[Symbol.hasInstance]
方法?
js
// 默认的函数和类行为
function Foo() {}
// 内部默认包含这个静态方法
Foo[Symbol.hasInstance] = function(instance) {
return instance instanceof Foo;
}
这个默认行为是在语言规范层面定义的:
- 函数和类会自动包含这个静态方法
- 确保自定义 instanceof 逻辑可用 (3)是否可以重写所有函数默认
[Symbol.hasInstance]
方法? ------参考[[3.静态属性和静态方法]]
js
class MyClass {
static [Symbol.hasInstance](obj) {
// 自定义逻辑
return true;
}
}
let obj = {};
obj instanceof MyClass; // true
[Symbol.hasInstance]
的重写步骤是:
- 在类上定义
[Symbol.hasInstance]
静态方法 - 方法参数为要检查的对象
- 方法内部编写自定义判断逻辑
- 返回 true 或 false
- 该方法会在 instanceof 时被调用 适用场景:
- 需要调整 instanceof 的判断规则时
- 当默认的原型链检查不足以判断实例时
- 想要 instanceof 在特定类上具有不同的语义时
原文资料
instanceof 操作符
js
// test 类
class Rabbit {}
let rabbit = new Rabbit();
// rabbit 是 Rabbit class 的对象吗?
alert( rabbit instanceof Rabbit ); // true
// 检验是否为实例
语法:
- 1.B instanceof A,B is A's instance ? true : false
-
- 在静态方法
Symbol.hasInstance
中==设置自定义逻辑。==
- 在静态方法
js
// test 构成函数
// 声明一个构造函数 Rabbit
function Rabbit() {}
// 使用 new 关键字创建 Rabbit 的一个实例
let rabbit = new Rabbit();
// instanceof 运算符检查 rabbit 是否是 Rabbit 的实例
// 如果是,返回 true。不是则返回 false
alert( rabbit instanceof Rabbit ); // true
instanceof Object
js
// test 原生
let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true
// 因为在 JavaScript 中,Array 是继承自 Object 的
// 所以 instanceof Object 对任何数组、对象、函数等都会返回 true。
// instanceof 检查整个原型链,只要出现在实例的原型链上,就会返回 true。
静态方法 Symbol.hasInstance
,那就直接调用这个方法:
js
// 设置 instanceOf 检查
// 并假设具有 canEat 属性的都是 animal
class Animal {
static [Symbol.hasInstance](obj) {
if (obj.canEat) return true;
}
}
let obj = { canEat: true };
alert(obj instanceof Animal);
// true:Animal[Symbol.hasInstance](obj) 被调用
第一部分
js
class Animal {
static [Symbol.hasInstance](obj) {
if (obj.canEat) return true;
}
}
- static Symbol.hasInstance]{...} 是静态方法
- Symbol.hasInstance 是一个内置的 Symbol 值
- Symbol.hasInstance - 一个内置Symbol值
[Symbol.hasInstance]
将这个 Symbol 值作为方法名,定义实例判断逻辑- Symbol.hasInstance用来自定义instanceof运算符的行为 第二部分
js
let obj = { canEat: true };
alert(obj instanceof Animal);
当执行alert(obj instanceof Animal); obj这个对象会被传入[Symbol.hasInstance]
方法的obj参数 最终返回true
涉及:数据类型Symbol
js
let mySymbol = Symbol();
// 第一种写法,属性方括号操作表示法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
注意,Symbol 值作为对象属性名时,不能用点运算符。
js
// 示例
const mySymbol = Symbol();
const a = {};
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
内置的 Symbol 值: Symbol.hasInstance 对象的Symbol.hasInstance
属性,指向一个内部方法。当其他对象使用instanceof
运算符,判断是否为该对象的实例时,会调用这个方法。
js
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
[1, 2, 3] instanceof new MyClass() // true
另一个例子:
js
class Even {
static [Symbol.hasInstance](obj) {
return Number(obj) % 2 === 0;
}
}
// 等同于
const Even = {
[Symbol.hasInstance](obj) {
return Number(obj) % 2 === 0;
}
};
1 instanceof Even // false
2 instanceof Even // true
12345 instanceof Even // false
Symbol.hasInstance是一个内置的Symbol值,它的作用是自定义instanceof运算符的行为。 使用方式是:
- 在类或对象上定义
[Symbol.hasInstance]
方法 - 当其他对象使用instanceof时,会自动调用这个方法
- 在方法内部可以自定义判断逻辑,返回true或false
- 这样就实现了自定义的instanceof行为
objA.prototype.isPrototypeOf(objB)
isPrototypeOf()
方法用于检查一个对象是否存在于另一个对象的原型链中。 理解为:"objA对象是否是objB的原型" ------【isPrototypeOf()】
js
class Animal {}
class Rabbit extends Animal {}
let rabbit = new Rabbit();
alert(rabbit instanceof Animal); // true
Animal.prototype.isPrototypeOf(rabbit); // true
Rabbit.prototype.isPrototypeOf(rabbit); // true
// 对比
// 语句1;
Animal.isPrototypeOf(Rabbit); // true
// 语句2;
Animal.prototype.isPrototypeOf(Rabbit.prototype); // true
JavaScript的原型继承机制来分析 为什么Animal.prototype.isPrototypeOf(Rabbit)
会返回false?
- Rabbit 通过
extends
关键字继承自 Animal。 - ==在继承时,会将 Animal.prototype 赋值给 Rabbit.prototype。==
- 所以 Rabbit.prototype 的原型是 Animal.prototype。
- 但是 Rabbit 自身和 Animal.prototype 没有直接的原型关系。
- Rabbit 只是 Animal 的一个子类/实例。 构造函数示例
js
function Animal() {}
function Rabbit() {}
Rabbit.__proto__ = Animal;
Rabbit.prototype = Object.create(Animal.prototype);
Animal.isPrototypeOf(Rabbit); // true
Animal.prototype.isPrototypeOf(Rabbit.prototype); // true
Rabbit.__proto__ = Animal;
改为Rabbit.__proto__ = Animal.prototype;
js
// 一样的
Animal.isPrototypeOf(Rabbit); // true
Animal.prototype.isPrototypeOf(Rabbit); // true
分化讨论:
js
Rabbit.__proto__ = Animal; // 继承静态成员
Rabbit.prototype.__proto__ = Animal.prototype; // 继承实例成员
改为:
js
Rabbit.__proto__ = Animal.prototype;
结果:
- Animal.isPrototypeOf(Rabbit) -> true
- Animal.prototype.isPrototypeOf(Rabbit) -> true
- Animal.prototype.isPrototypeOf(Rabbit.prototype) -> true 原因:
- 现在Rabbit函数的原型直接设置为了Animal.prototype,跳过了Animal函数。
- Animal.prototype的构造函数constructor属性指向Animal函数。
- 所以Rabbit的原型链上依然存在Animal函数。
- isPrototypeOf按原型链查找,只要链上存在即可。 所以修改后,Rabbit函数仍然通过原型链继承自Animal函数。 isPrototypeOf检查原型链,所以结果不变。
使用 Object.prototype.toString 方法来揭示类型
js
// 方便起见,将 toString 方法复制到一个变量中
let objectToString = Object.prototype.toString;
// 它是什么类型的?
let arr = [];
alert( objectToString.call(arr) ); // [object Array]
装饰器模式和转发,call/apply 一章中讲过的 call 方法来在上下文 this=arr 中执行函数 objectToString。
js
let s = Object.prototype.toString;
alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]
Symbol.toStringTag
使用特殊的对象属性 Symbol.toStringTag
自定义对象的 toString
方法的行为。
js
let user = {
[Symbol.toStringTag]: "User"
};
alert( {}.toString.call(user) ); // [object User]