前端面试题-类型判断

1. typeofinstanceof 类型判断

查看

typeof 是否能正确判断类型?instanceof能否正确判断对象的原理是什么?

typeof 对于原始类型来说,除了 null 都可以显示正确的类型。判断 null 对应的结果为 object

1
2
3
4
5
6
7
console.log(typeof 1); // number
console.log(typeof "1"); // string
console.log(typeof undefined); // undefined
console.log(typeof true); // boolean
console.log(typeof Symbol()); // symbol
console.log(typeof 10n); // bigint
console.log(typeof null); // object

typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 不能准确判断对象的类型。

1
2
3
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof function(){}); // function

所以 typeof 不能正确判断类型。

如果我们想判断一个对象的正确类型,我们可以考虑使用 instanceof,因为内部机制是通过原型链来判断的。但对于原始数据类型,使用 instanceof 是无法判断的。

1
2
3
4
5
6
function Person(){}
const p1 = new Person();
console.log(p1 instanceof Person); // true

var str = 'hello world'
console.log(str instanceof String); // false

注意:
instanceof 可以判断对象的正确类型,但由于原型链的关系,对于其类型的父类进行判断也是返回 true

手动实现 function isInstanceOf(child,Parent)

1
2
3
4
5
6
7
8
9
10
11
12
13
function isInstanceof(Child, Parent) {
// 取右表达式的 prototype 值
let ParentProto = Parent.prototype;
// 取左表达式的__proto__值
Child = Child.__proto__;
console.log(ParentProto);
console.log(Child);
while (true) {
if (Child === null) return false;
if (Child === ParentProto) return true;
Child = Child.__proto__;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 开始测试
var a = []
var b = {}

function Foo() { }
var c = new Foo()
function child() { }
function father() { }
child.prototype = new father()
var d = new child()

console.log(isInstanceof(a, Array)) // true
console.log(isInstanceof(b, Object)) // true
console.log(isInstanceof(b, Array)) // false
console.log(isInstanceof(a, Object)) // true
console.log(isInstanceof(c, Foo)) // true
console.log(isInstanceof(d, child)) // true
console.log(isInstanceof(d, father)) // true

2. =====Object.is() 的区别

==

查看 相等(==)运算符(宽松比较)检查其两个操作数是否相等,返回一个布尔值结果。当比较双方数据类型不同时,它会尝试转换不同类型的操作数,并进行比较

== 比较规则

  1. 如果操作数具有相同的类型,则按如下方式进行比较:

    • 对象(Object):仅当两个操作数引用同一个对象时返回 true。
    • 字符串(String):仅当两个操作数具有相同的字符且顺序相同时返回 true。
    • 数字(Number):如果两个操作数的值相同,则返回 true。+0 和 -0 被视为相同的值。如果任何一个操作数是 NaN,返回 false;所以,NaN 永远不等于 NaN
    • 布尔值(Boolean):仅当操作数都为 true 或都为 false 时返回 true。
    • 大整型(BigInt):仅当两个操作数的值相同时返回 true。
    • 符号(Symbol):仅当两个操作数引用相同的符号时返回 true。
    1
    2
    3
    let a = NaN;
    let b = NaN;
    console.log(a == b); // false
  2. 如果其中一个操作数为 null 或 undefined,另一个操作数也必须为 null 或 undefined 以返回 true。否则返回 false。(null 和 undefined 除了与null 和 undefined 比较为 true,其他值都返回 false)

    1
    2
    3
    4
    5
    6
    7
    8
    let a = null;
    let b = undefined;
    let c = null;
    let d = undefined;
    console.log(a == b); // true
    console.log(a == c); // true
    console.log(b == d); // true
    console.log(a == d); // true
  3. 如果其中一个操作数是对象,另一个是原始值,则将对象转换为原始值。

    • 在这一步,两个操作数都被转换为原始值(字符串、数字、布尔值、符号和大整型中的一个)。剩余的转换将分情况完成。
      • 如果是相同的类型,使用步骤 1 进行比较。
      • 如果其中一个操作数是符号(Symbol)而另一个不是,返回 false。
      • 如果其中一个操作数是布尔值而另一个不是,则将布尔值转换为数字:true 转换为 1,false 转换为 0。然后再次对两个操作数进行宽松比较。
      • 数字与字符串:将字符串转换为数字。转换失败将导致 NaN,这将保证相等比较为 false。
      • 数字与大整型:按数值进行比较。如果数字的值为 ±∞ 或 NaN,返回 false。
      • 字符串与大整型:使用与 BigInt() 构造函数相同的算法将字符串转换为大整型数。如果转换失败,返回 false。

对象转为原始值
对象将依次调用它的 [Symbol.toPrimitive]()(将 default 作为 hint 值)、valueOf() 和 toString() 方法,将其转换为原始值。注意,原始值转换会在 toString() 方法之前调用 valueOf() 方法
[Symbol.toPrimitive]() 方法,如果存在,则必须返回原始值,返回对象,会导致 TypeError。对于 valueOf() 和 toString(),如果其中一个返回对象,则忽略其返回值,从而使用另一个的返回值;如果两者都不存在,或者两者都没有返回一个原始值,则抛出 TypeError

示例

定义一个干净的对象

1
2
3
4
5
let a = {}
console.log(a); // {}
console.log(a[Symbol.toPrimitive]); // undefined
console.log(a.toString()); // [object Object]
console.log(a.valueOf()); // {}

实现该对象的[Symbol.toPrimitive]方法,使其返回一个原始值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let a = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return 42
}else if (hint === 'string') {
return 'abc'
}
return null
},
}

// 此处运用了隐式转换 导致使用强制数字类型转换算法 使得能满足 hint==='number' 条件
console.log(+a == 42); // true
// 此处 new String 导致使用强制字符串类型转换算法 使得能满足 hint==='string' 条件
console.log(new String(a) == 'abc'); // true
console.log(a == null); // false

注意
当未对对象使用强制类型转换时,会返回默认值。但Symbol.toPrimitive 返回的 null 或 undefined 与 null 和 undefined 进行宽松比较(==)时仍为 false,hint 值是 number 时,返回的必须是数字字符串或数字,否则与数字或数字字符串进行宽松比较时仍为 false

DateSymbol 对象是唯一重写 [Symbol.toPrimitive]() 方法的对象。

对于 Date,如果 hint 是 “string” 或 “default”,[Symbol.toPrimitive]() 将会调用 toString。如果 toString 属性不存在,则调用 valueOf。如果 valueOf 也不存在,则抛出一个TypeError。

对于 Symbol,hint 参数未使用时,Symbol 的 [Symbol.toPrimitive]() 方法返回 Symbol 对象的原始值作为数据类型返回。

1
2
const sym = Symbol("example");
sym === sym[Symbol.toPrimitive](); // true

当对象不存在Symbol.toPrimitive时,会根据 toString()valueOf() 的返回值作为原始值进行比较。

1
2
3
4
5
6
7
8
9
10
11
let a = {
valueOf() {
return 42
},
toString() {
return 'abc'
}
}
console.log(a == 42); // true
console.log(a == 'abc'); // false
console.log(new String(a) == 'abc'); // true

一般来说,valueOf 的优先级高于 toString。但当其使用强制字符串类型转换时,会根据 toString 的返回值作为原始值进行比较。

===

查看 严格相等(===)运算符与 == 运算符之间最显著的区别是,严格相等运算符不尝试类型转换。相反,严格相等运算符总是认为不同类型的操作数是不同的,即只要类型不同进行 `===` 操作时,结果即为 `false`。严格相等运算符本质上只执行第 1 步,然后对所有其他情况返回 `false`。

上面的算法有一个“故意违反”:如果其中一个操作数是 document.all,则它被视为 undefined。这意味着 document.all == null 是 true,但 document.all === undefined && document.all === null 是 false。 —— MDN文章描述

=== 比较规则

  • 如果操作数的类型不同,则返回 false。

  • 如果两个操作数都是对象,只有当它们指向同一个对象时才返回 true。

  • 如果两个操作数都为 null,或者两个操作数都为 undefined,返回 true。

  • 如果两个操作数有任意一个为 NaN,返回 false。

  • 否则,比较两个操作数的值:

    • 数字类型必须拥有相同的数值。+0 和 -0 会被认为是相同的值。
    • 字符串类型必须拥有相同顺序的相同字符。
    • 布尔运算符必须同时为 true 或同时为 false。
1
2
3
console.log(-0 === +0); // true
console.log(0 === -0); // true
console.log(NaN === NaN); // false

Object.is()

查看

Object.is() 静态方法确定两个值是否为相同值。

Object.is()的比较规则

如果出现以下几种情况,则返回 true,即比较的两个值相同

  • 都是 undefined

  • 都是 null

  • 都是 true 或者都是 false

  • 都是长度相同、字符相同、顺序相同的字符串

  • 都是相同的对象(意味着两个值都引用了内存中的同一对象)

  • 都是 BigInt 且具有相同的数值

  • 都是 symbol 且引用相同的 symbol 值

  • 都是数字且

    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都有相同的值,非零且都不是 NaN

Object.is() 与 == 运算符并不等价。== 运算符在测试相等性之前,会对两个操作数进行类型转换(如果它们不是相同的类型),这可能会导致一些非预期的行为,例如 “” == false 的结果是 true,但是 Object.is() 不会对其操作数进行类型转换。

Object.is() 也不等价于 === 运算符。Object.is() 和 === 之间的唯一区别在于它们处理带符号的 0 和 NaN 值的时候。=== 运算符(和 == 运算符)将数值 -0 和 +0 视为相等,但是会将 NaN 视为彼此不相等。

1
2
3
4
5
6
Object.is(0, -0); // false
Object.is(+0, -0); // false
Object.is(-0, -0); // true

Object.is(NaN, 0 / 0); // true
Object.is(NaN, Number.NaN); // true

总结

对于 ==,主要特点就是类型转换,而 ===Object.is() 则不进行类型转换。

对于 ===,主要特点就是严格相等,类型不同就是 false,与 == 类型相同时比较规则一致。

对于 Object.is(),主要特点就是严格相等,类型不同就是 false,与 == 类型相同时比较规则一致。但与 === 不同的是,Object.is() 对于 0 和 -0、NaN 的处理不同,Object.is() 将数值 -0 和 +0 视为彼此不相等,但是会将 NaN 视为彼此相等,=== 将数值 -0 和 +0 视为相等,但是会将 NaN 视为彼此不相等。

全局 isNaN()Number.isNaN()

查看
  • isNaN() 函数用来确定一个值是否为 NaN,若有必要,则首先将值转换为数字。

  • Number.isNaN() 静态方法判断传入的值是否为 NaN,如果输入不是数字类型,则返回 false。它是全局 isNaN() 函数更健壮的版本。

isNaN() 是全局对象的一个函数属性。对于数字值,isNaN() 检测该值是否为 NaN 值。当 isNaN() 函数的参数不是数字类型时,其会首先被转换为数字,然后将其结果值与 NaN 进行比较

isNaN() 对于非数字参数的行为可能会令人困惑!例如,空字符串被强制转换为 0,布尔值被强制转换为 0 或 1;直观上,两者均“不是数字”,仅因它们的运算结果不是 NaN,而使得 isNaN() 返回 false。因此,isNaN() 既不回答“输入是否为浮点数值 NaN”,也不回答“输入是否为数字”这两个问题。

Number.isNaN() 是检测一个值是否为数字值 NaN 的更可靠的方法。或者,也可以使用表达式 x !== x,这两种方法都不会产生全局 isNaN() 不可靠的误判。要检测一个值是否为数字,请使用 typeof x === “number”。

Number.isNaN() 和全局 isNaN() 之间的区别

Number.isNaN() 不会尝试将参数转换为数字,因此非数字总是返回 false。以下都返回 false:

1
2
3
4
5
6
7
8
9
10
Number.isNaN("NaN"); 
Number.isNaN(undefined);
Number.isNaN({});
Number.isNaN("blabla");
Number.isNaN(true);
Number.isNaN(null);
Number.isNaN("37");
Number.isNaN("37.37");
Number.isNaN("");
Number.isNaN(" ");

全局 isNaN() 函数会将参数强制转换为数字:

1
2
3
4
5
6
7
8
9
10
isNaN("NaN"); // true
isNaN(undefined); // true
isNaN({}); // true
isNaN("blabla"); // true
isNaN(true); // false,强制转换为 1
isNaN(null); // false,强制转换为 0
isNaN("37"); // false,强制转换为 37
isNaN("37.37"); // false,强制转换为 37.37
isNaN(""); // false,强制转换为 0
isNaN(" "); // false,强制转换为 0