JavaScript有一个强大的特性就是动态类型。使用者在声明变量不用指定变量类型,但这样往往会给开发者带来问题或困惑,尤其是在较大的项目中。因此,类型检查是任何JavaScript开发者的一项关键技能。
1.值类型
类型 | 描述 | 示例 |
undefined | 变量未被赋值。 | let x; |
null | 值为空,一个空的对象引用。 | const x = null; |
boolean | 逻辑值,true 或false 。 | const x = true; |
number | 数值。 | const x = 50; |
bigint | 任意精度的整数。 | const x = 9007199254740991n; |
string | 字符序列。 | const x = 'Hello!'; |
symbol | 可以用作对象属性的键的唯一值。 | const x = Symbol(); |
object | 属性的集合。 | const x = { a: 1, b: 2 }; |
function | 一个可调用的对象。 | const x = () => {}; |
2.原始类型检查
上表中除了object和function之外,其他类型都是原始类型。原始类型通常比对象和函数更容易进行类型检查。这是因为原始类型是不可变的,这意味着它们的值不能被改变。因此,我们只需将一个值的类型与我们想要检查的类型进行比较即可。使用typeof运算符就可以得出值的类型。
2.1 undefined
检查undefined的值可以直接将值与undefined进行比较。这与使用typeof运算符得到的结果完全相同。
const isUndefined = val => val === undefined;
isUndefined(undefined); // true
2.2 null
检查空值只能通过将值与null本身进行比较来完成。这是因为typeof null返回的是object。
const isNull = val => val === null;
isNull(null); // true
2.3 boolean
const isBoolean = val => typeof val === 'boolean';
isBoolean(true); // true
isBoolean(false); // true
isBoolean('true'); // false
isBoolean(null); // false
2.4 number
这里需要注意的是,使用typeof来检查数字时。NaN也会返回true,因此需要特殊处理下。
const isNumber = val => typeof val === 'number' && !Number.isNaN(val);
isNumber(1); // true
isNumber('1'); // false
isNumber(NaN); // false
2.5 bigint
const isBigInt = val => typeof val === 'bigint';
isBigInt(1n); // true
isBigInt(1); // false
2.6 string
const isString = val => typeof val === 'string';
isString('Hello!'); // true
isString(1); // false
2.7 symbol
const isSymbol = val => typeof val === 'symbol';
isSymbol(Symbol('x')); // true
isSymbol('x'); // false
2.8 原始值
检查一个值是否属于任何原始类型有点麻烦。不能简单地使用typeof,因为它不适用于null。 那么,我们可以根据值创建一个对象并将其与值本身进行比较。如果该值是原始值,则该对象将不等于该值。
const isPrimitive = val => Object(val) !== val;
isPrimitive(null); // true
isPrimitive(undefined); // true
isPrimitive(50); // true
isPrimitive('Hello!'); // true
isPrimitive(false); // true
isPrimitive(Symbol()); // true
isPrimitive([]); // false
isPrimitive({}); // false
3.非原始类型检查
对象和函数的行为与原始类型略有不同。虽然typeof可以解决部分问题,但对于对象,我们需要使用其他方法检查。
3.1 对象
typeof对于null的返回值是'object'。与判断是否原始值类似,我们可以使用Object构造函数为给定值创建一个对象包装器。如果该值是null或undefined,则返回值为一个空对象。否则,返回的对象将与输入对象相同。
const isObject = obj => obj === Object(obj);
isObject([1, 2, 3, 4]); // true
isObject([]); // true
isObject(['Hello!']); // true
isObject({ a: 1 }); // true
isObject({}); // true
isObject(true); // false
isObject(null); // false
isObject(undefined); // false
3.2 函数
函数可以直接使用 typeof 来检查。
const isFunction = val => typeof val === 'function';
isFunction(x => x); // true
isFunction('x'); // false
3.3 普通对象
当一个对象由Object构造函数创建时,它被认为是普通的。这与数组、函数或类的实例(如Map)等其他对象不同。 为了检查一个值是否是普通对象,我们需要确保该值是存在的(不是null或undefined),它是一个对象,并且它的构造函数等于Object.prototype.constructor。
const isPlainObject = val =>
!!val && typeof val === 'object' && val.constructor === Object;
isPlainObject({ a: 1 }); // true
isPlainObject(new Map()); // false
3.4 异步函数
使用async关键字声明的函数被视为异步函数。对异步函数的类型检查需要使用Object.prototype.toString()和Function.prototype.call()来检查结果是否为'[object AsyncFunction]'。
const isAsyncFunction = val =>
Object.prototype.toString.call(val) === '[object AsyncFunction]';
isAsyncFunction(function() {}); // false
isAsyncFunction(async function() {}); // true
3.5 生成器函数
生成器函数可以像异步函数一样进行类型检查。它的检查结果为是否为[object GeneratorFunction]。
const isGeneratorFunction = val =>
Object.prototype.toString.call(val) === '[object GeneratorFunction]';
isGeneratorFunction(function() {}); // false
isGeneratorFunction(function*() {}); // true
3.6 获取值类型
如果需要处理类和其他自定义类型,可以获取值的类型的字符串表示。这里可以通过使用Object.prototype.constructor和Function.prototype.name来实现。
const getType = v =>
v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name;
getType(undefined); // 'undefined'
getType(null); // 'null'
getType(true); // 'Boolean'
getType(1); // 'Number'
getType(1n); // 'BigInt'
getType('Hello!'); // 'String'
getType(Symbol()); // 'Symbol'
getType([]); // 'Array'
getType({}); // 'Object'
getType(() => {}); // 'Function'
getType(new Set([1, 2, 3])); // 'Set'
3.6.1 检查值是否属于类型
将上边获取值类型扩展下,我们还可以检查一个值是否为特定类型。与之前一样,对于undefined和null需要特殊处理下,因为它们没有构造函数。
const isOfType = (type, val) =>
([undefined, null].includes(val) && val === type) ||
v.constructor.name === type;
isOfType(undefined, undefined); // true
isOfType(null, null); // true
isOfType('Boolean', true); // true
isOfType('Number', 1); // true
isOfType('BigInt', 1n); // true
isOfType('String', 'Hello!'); // true
isOfType('Symbol', Symbol()); // true
isOfType('Array', []); // true
isOfType('Object', {}); // true
isOfType('Function', () => {}); // true
isOfType('Set', new Set([1, 2, 3])); // true