typeof
一般用来判断一个变量的类型
如:number string undefined boolean function symbol bigint object
缺点是在判断一个对象的时候,只能告诉我们这个数据是 object,没有具体区别是哪一种 object
如:
let ss = '2good'
typeof ss // string
let s = new String('2good')
typeof s // object
s instanceof String // true
原理:
js在底层的存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息。
- 000: 对象
- 010: 浮点数
- 100: 字符串
- 110: 布尔
- 1: 整数
但是,对于 undefined 和 null 来说,这两个值的信息存储有点特殊。
null:所有机器码均为 0 ,由于 null 代表的是空指针(大多数平台下值为 0x00)
undefined:用 -2^30 整数来表示
所以 typeof 在判断 null 的时候就会出现问题,由于 null 的机器码均为 0,因此直接将其当做 对象 来看待。
然而用 instanceof 来判断的话
null instanceof null // TypeError: Right-hand side of 'instanceof' is not an object
由于使用 instanceof 的话。左边必须是 object,但是这个又直接被判断为不是 object,这也是 JavaScript 的历史遗留 bug
因此在用 typeof 来判断变量类型的时候,我们需要注意,最好是用 typeof 来判断基本数据类型(包括symbol),避免对 null 的判断。因为 typeof null 结果为 "object"。
typeof 1 === 'number' // true
typeof Infinity === 'number'; // true
typeof NaN === 'number'; // true, 尽管它是 "Not-A-Number" (非数值) 的缩写
typeof '123' === 'string' // true
typeof (typeof 1) === 'string'; // typeof 总是返回一个字符串
typeof String(1) === 'string'; // String 将任意值转换为字符串,比 toString 更安全
typeof null === 'object' // true,注意 null 不是对象,这里是特殊的。
typeof [1, 2, 4] === 'object'; // true
typeof true === 'boolean' // true
typeof function a() {} === 'function'
typeof Symbol(2) === 'symbol'
typeof undefined === 'undefined'
//使用new操作符
// 除 Function 外的所有构造函数的类型都是 'object'
typeof new Number(100) === 'object'
typeof new String('123') === 'object'
typeof new Date() === 'object'
var func = new Function();
typeof func === 'function'
//NaN
typeof Number("123z") === "number" // 123z 在转数字的是时候遇到z出错就是NaN了。NaN为number类型
typeof 1/0; //NaN(这个NaN不是字符串类型,是数值类型)
typeof (typeof 1/0) === 'number'; //NaN(这个NaN不是字符串类型,是数值类型)
instanceof
instanceof 用于检测构造函数的 prototype 属性是否出现在某个示例对象上的原型链上。MDN
let Person = function () {}
let xxx = new Person()
xxx instanceof Person // true
注意:左侧必须为对象。
let a = 1
a.__proto__ === Number.prototype // true
a instanceof Number // false
let b = new Number(1)
b.__proto__ === Number.prototype // true
b instanceof Number // true
当然,instanceof 也可以判断一个实例是否是其父类型或者祖先类型的实例。
let Person = function () {}
let Programmer = function () {}
Programmer.prototype = new Person()
let nicole = new Programmer()
nicole instanceof Person // true
nicole instanceof Programmer // true
原理:
instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false。
实现 instanceof
function myInstanceof(left, right) {
let r = right.prototype
let l = left.__proto__
// 若找不到就到一直循环到父类型或祖类型
while(true) {
if (l === null) {
return false
}
if (l === r) {
return true
}
l = l.__proto__ // 获取祖类型的__proto__
}
}
let P = function () {}
let a = new P()
let b = 2
myInstanceof(a, P) // true
myInstanceof(b, P) // false
注意:可以通过修改对象的 __proto__ 属性,或者修改构造函数的 prototype 属性,那么有可能找不到原型链中对应的原型了。
let P = function () {}
let P1 = function () {}
let a = new P()
a instanceof P // true
a.__proto__ = {}
a instanceof P // false
P.prototype // {constructor: ƒ}
P.prtotype = P1.prototype // 更改P的原型指向
a instanceof P // false
还有一种方式可以了解一下。
Symbol.hasInstance 用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。
class Array1 {
static [Symbol.hasInstance](instance) {
console.log('执行了这个方法啦', instance)
return Array.isArray(instance)
}
}
[] instanceof Array1 // true
({} instanceof Array1) // false
以上,我们使用 typeof 来判断基本类型就可以了,不过要注意 typeof 来判断 null 类型的问题。如果要判断一个对象的具体类型可以考虑用 instanceof,但是也看判断不准确,比如一个数组,他可以被判断为 object,所以我们要想比较准确的判断对象实例的类型时,可以采取 Object.prototype.toString 方法。
Object.prototype.toString()
toString() 方法返回一个表示该对象的字符串
toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中 type 是对象的类型。
new Object().toString() // "[object Object]"
而类似 Array、Date 等没有按照这种格式 "[object " + tag + "]" 返回是因为该对象他们内部自定义了返回格式。
[].toString() // ""
(new Date()).toString() // "Fri Apr 09 2021 09:51:07 GMT+0800 (GMT+08:00)"
原理:
调用 toString(O) 方法时,将执行以下步骤:tc39.es/
- If the this value is undefined, return
"[object Undefined]". - If the this value is null, return
"[object Null]". - Let O be ! ToObject(this value).
- Let isArray be ? IsArray(O).
- If isArray is true, let builtinTag be "Array".
- Else if O has a
[[ParameterMap]]internal slot, let builtinTag be"Arguments". - Else if O has a
[[Call]]internal method, let builtinTag be"Function". - Else if O has an
[[ErrorData]]internal slot, let builtinTag be"Error". - Else if O has a
[[BooleanData]]internal slot, let builtinTag be"Boolean". - Else if O has a
[[NumberData]]internal slot, let builtinTag be"Number". - Else if O has a
[[StringData]]internal slot, let builtinTag be"String". - Else if O has a
[[DateValue]]internal slot, let builtinTag be"Date". - Else if O has a
[[RegExpMatcher]]internal slot, let builtinTag be"RegExp". - Else, let builtinTag be
"Object". - Let tag be ? Get(O,
@@toStringTag). - If Type(tag) is not String, set tag to builtinTag.
- Return the string-concatenation of
"[object ", tag, and "]".
// 以下是根据步骤翻译,为方便理解而写的伪代码。若有不足之处,还望指正。
// 包装对象
function ToObject(O) {
if (O === undefined || O === null) {
throw new Error('Throw a TypeError exception')
}
let res = null;
switch(typeof O) {
case 'Boolean': res = new Boolean(O); break;
case 'Number': res = new Number(O); break;
case 'String': res = new String(O); break;
case 'Symbol': res = new Object(Symbol(O)); break;// 注意这里的 Symbol 不支持 new 语法
case 'BigInt': res = new Object(BigInt(O)); break;
default: res = O; break;
}
return res;
}
// 判断是不是数组
function IsArray (O) {
return Array.isArray(O)
}
// toString 内部执行步骤
function toString(O) {
let bulitinTag = ''
if (O === undefined || O === null) {
return `[object ${ O }]`
}
O = ToObject(O)
if (IsArray(O)) {
bulitinTag = "Array"
}
let slotName = O[[class]]; // 此处为对象的内部插槽class
switch(slotName) {
case '[[ParameterMap]]': bulitinTag = "Arguments"; break;
case '[[Call]]': bulitinTag = "Function"; break;
case '[[ErrorData]]': bulitinTag = "Error"; break;
case '[[BooleanData]]': bulitinTag = "Boolean"; break;
case '[[NumberData]]': bulitinTag = "Number"; break;
case '[[StringData]]': bulitinTag = "String"; break;
case '[[DateValue]]': bulitinTag = "Date"; break;
case '[[RegExpMatcher]]': bulitinTag = "RegExp"; break;
default: bulitinTag = "Object"; break;
}
let tag = O[Symbol.toStringTag] // 获取对象自定义的[Symbol.toStringTag]方法。
if (typeof tag !== 'String') {
tag = bulitinTag
}
return `[object ${ tag }]`
}
下面是自定义 Symbol.toStringTag 的例子。可以看出,属性值期望是一个字符串,否则会被忽略。
var test = {
[Symbol.toStringTag]: "Good"
}
test.toString() // "[object Good]"
var test1 = {
[Symbol.toStringTag]: 2
}
test1.toString() // "[object Object]"
Symbol.toStringTag 也可以部署在原型链上:
class A {}
A.prototype[Symbol.toStringTag] = "A";
new A().toString() // "[object A]"
Object.prototype.toString.call(new A) // "[object A]"
为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为 thisArg。
// 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] 属性,是为了把此方法接口化,用于规范新引入的对象对此方法的调用。但对于“老旧”的对象,就只能直接输出值,以保证兼容性。
总结
- 基本数据类型可以用 typeof 来判断,要注意的是
typeof null为 "object" 的情况。 - instanceof 只能用来判断引用数据类型,基本数据类型不可以。缺点是原型链指向改动的话,这个的判断就可能会出错。
- Object.prototype.toString 适合判断任意类型数据。
最后,谢谢你读完本篇文章,希望对你能有所帮助,如有问题欢迎各位指正。