likes
comments
collection

深入理解js类型判断

作者站长头像
站长
· 阅读数 33

前言

  • 在开始本篇讲解之前,你可能需要一些原型的前置知识,如果还不是很懂,可以通过 原型链 这篇文章进行学习或者巩固。我们先来了解一下JavaScript的数据结构,我们很快就会被用到。

JavaScript 7种内置类型

  • 空值(null)、未定义(undefined)、布尔值(boolean)、数字(number)、字符串(string)、对象(object)、符号(symbol)。

JavaScript 7种原始类型

  • 空值(null)、未定义(undefined)、布尔值(boolean)、数字(number)、字符串(string)、 任意精度整数(Bigint)、符号(symbol)。

什么是 typeof

  • typeof 用于判断一个变量的是什么类型,通过 typeof 判断可以返回numberstringobject,boolean,functionundefinedsymbol这七种基本类型
  • 其基本语法是以下这样的形式:

// operand 表示要返回类型的对象或基本类型的表达式
typeof operand

typeof (operand)

typeof 基本用法

  • 接下来我们使用 typeof 运算符来查看值的类型,它返回的是类型的字符串值。有意思的是,这七种类型和它们的字符串值并不一一对应:


console.log(typeof 777); // number
console.log(typeof 3.14); // number
console.log(typeof 0); // number
console.log(typeof Infinity); // number
console.log(typeof Number("moment")); // number

console.log(typeof 77n); // bigint

console.log(typeof "1"); // string
console.log(typeof typeof 1); // string typeof 返回一个字符串
console.log(typeof String(777)); // string

console.log(typeof true); // boolean
console.log(typeof false); // boolean
console.log(typeof Boolean(5)); // boolean  // Boolean() 会基于参数是真值还是虚值进行转换
typeof !!1 === "boolean"; // 两次调用 !(逻辑非)运算符相当于 Boolean()

console.log(typeof Symbol()); // symbol
console.log(typeof Symbol("foo")); // symbol
console.log(typeof Symbol.iterator); // symbol

console.log(typeof { a: 1 }); // object
console.log(typeof [1, 2, 4]); // object
console.log(typeof new Date()); // object
console.log(typeof /regex/); // object
console.log(typeof null); // object

console.log(typeof function () {}); // function
console.log(typeof class T {}); // function

  • 你可能注意到 null 类型比较特殊,typeof null==='object',正确的返回应该是'null',但是这个bug由来已久,在JavaScript中已经存在了将近二十年,也许永远不会修复。

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"


console.log(typeof function () {}); // function

  • 在上面的代码中,输出结果为 'function',说明 function 也是JavaScript 的一个内置类型。然而规范里,它实际上是 object 的一个 子类型。具体来说,函数是 可调用对象,它有一个内部属性 [[call]] ,该属性使其可以被调用。

function f() {}
console.log(f.__proto__.constructor === Function); // true
console.log(f.__proto__.__proto__.constructor === Object); // true

// 函数不仅是对象,还可以拥有属性
console.log(f.name); // f
console.log(f.arguments);  // 因为没有传参数,所以是一个 null

  • 通过原型的方法去判别,函数确实是 Object的子类。
  • 再来看看数组,JavaScript支持数组,数组也是对象,确切涞水,它也是 object 的一个 子类型,数组的元素按数字顺序来进行索引,其 length 属性是元素的个数。

const foo = [];
console.log(foo.__proto__.constructor === Array); // true
console.log(foo.__proto__.__proto__.constructor === Object); // true

new 操作符

  • 所有使用 new 调用的构造函数都将返回非基本类型,返回的不是object 类型,就是 function。大多数返回对象,但值得注意的例外是 Function,它返回一个函数。

const str = new String("777");
const num = new Number(777);

const func = new Function();

console.log(typeof str); // object
console.log(typeof num); // object
console.log(typeof func); // function

undefined 和 undeclared

  • 变量在未持有值的时候为 undefined。此时 typeof 返回 undefined:

var a;
console.log(tyoeof a); // undefined

  • 大多数的开发者倾向于将 undefined 等同于 undeclared(未声明),但在 JavaScript中它们完全是两回事。在作用域中声明但是还没有赋值的变量,是 undefined。相反,还没有在作用域中声明过的变量,是undeclared 的。

深入理解js类型判断

  • 在上列中, bar is not defined容易让人误以为是 bar is undefined。但是 undefinedis undefined 是两码事,但是 typeof 处理 undeclared 返回的结果竟然是 undefined,例如:

var foo;

console.log(typeof foo); // undefined
console.log(typeof bar); // undefined

  • 它们两个原样返回 "undefined",并且 typeof bar 并没有报错,这是因为 typeof 有一个特殊的安全防范机制。

内部属性[[class]]

  • 在前面的例子中,使用 typeof 进行判断,无论是 nullObjectArray等类型,都返回的是 "object",那么是否有一种机制可以判断它具体为什么类型的值呢?答案是有的。
  • 所有 typeof 返回值为 object 的对象(如数组)都包含一个内部属性[[class]] ,我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类。这个属性无法直接访问,一般通过 Object.prototype.toString(...) 来查看。例如:

console.log(Object.prototype.toString.call([1, 2, 3])); // [object Array]
console.log(Object.prototype.toString.call(1)); // [object Number]
console.log(Object.prototype.toString.call("moment")); //[object String]
console.log(Object.prototype.toString.call(true)); //[object Boolean]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(function f() {})); // [object Function]
console.log(Object.prototype.toString.call(class C {})); // [object Function]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(Symbol())); // [object Symbol]
console.log(Object.prototype.toString.call(new Boolean(1))); // [object Boolean]
console.log(Object.prototype.toString.call(new RegExp())); // [object RegExp]

  • 上例中,数组内部[[class]]属性值是 Array,正则表达式的值是 RegExp。多数情况下,对象的内部[[class]]属性和创建该对象的内建原生构造函数相对应,但并不是所有的情况都是这样,例如一些基本类型,例如 nullundefined ,虽然 Null()undefined() 这样的原生构造函数并不存在,但是内部[[class]]属性值仍然是 NullUndefined
  • 其他基本类型,例如 字符串、数值和布尔值 的情况有所不同,由于基本类型值没有 .length.toString() 这样的属性和方法,需要通过封装对象才能访问,此时 JavaScript 会自动为基本类型封装为一个对象,例如 var foo = 'moment';,实际上进行的是 var foo =new String('moment'); ,使其变成一个对象,让其拥有自己的属性和方法,如果想要得到封装对象中的基本类型值,可以使用 valueOf() 函数,例如:

var foo = new String("moment");

console.log(foo); // [String: 'moment']
console.log(foo.valueOf()); // moment
console.log(typeof foo.valueOf()); // string

手写 typeof

  • typeof 是非常有用的,但它不像需要的那样万能。例如,typeof [] 是 "object",以及 typeof new Date()typeof /abc/ 等。
  • 为了明确地检查类型, mdn 上提供了一个自定义的 type(value) 函数,它主要模仿 typeof 的行为,但对于非基本类型(即对象和函数),它在可能的情况下返回更详细的类型名。

function type(value) {
  // 如果传入的值是 null ,则返回 null
  if (value === null) {
    return "null";
  }

  const baseType = typeof value;
  // 如果是基本类型
  if (!["object", "function"].includes(baseType)) {
    return baseType;
  }

  // Symbol.toStringTag 通常指定对象类的“display name”
  const tag = value[Symbol.toStringTag];
  if (typeof tag === "string") {
    return tag;
  }

  // 如果他是一个函数,其源代码以 class 关键字开头的
  if (
    baseType === "function" &&
    Function.prototype.toString.call(value).startsWith("class")
  ) {
    return "class";
  }

  // 构造函数的名称;例如 `Array`、`GeneratorFunction`、`Number`、`String`、`Boolean` 或 `MyCustomClass`
  const className = value.constructor.name;
  if (typeof className === "string" && className !== "") {
    return className;
  }

  // 没有合适的方法来获取值的类型,直接返回
  return baseType;
}


参考文献