Skip to content

类型检测

  • typeof

    javascript
    /**
     * 返回一个字符串,表示未经计算的操作数的类型。
     *
     * @param {exp} operand - 一个表示对象或原始值的表达式
     * @returns {string} 返回对应类型,可能值有:
     *  string | number | boolean | undefined |
     *  symbol | bigint | object | function
     */
    typeof 'Lorem' // output: 'string'
    typeof '1' // output: 'string'
    typeof '' // output: 'string'
    typeof (typeof 1) // output: 'string'
    typeof String(1) // output: 'string'
    
    typeof 3.14 // output: 'number'
    typeof NaN // output: 'number'
    typeof Infinity // output: 'number'
    typeof +Infinity // output: 'number'
    typeof -Infinity // output: 'number'
    typeof Number(1) // output: 'number'
    
    typeof true // output: 'boolean'
    typeof !!(1) // output: 'boolean'
    typeof Boolean(1) // output: 'boolean'
    
    let declaredButUndefinedVariable
    typeof undefined // output: 'undefined'
    typeof declaredButUndefinedVariable // output: 'undefined'
    typeof undeclareVariable // output: 'undefined'
    
    typeof Symbol() // output: 'symbol'
    typeof Symbol('foo') // output: 'symbol'
    typeof Symbol.iterator // output: 'symbol'
    
    typeof 1n // output: 'bigint'
    typeof -1n // output: 'bigint'
    
    typeof function foo () {} // output: 'function'
    typeof class Foo {} // output: 'function'
    
    typeof {} // output: 'object'
    typeof [] // output: 'object'
    typeof /''/ // output: 'object'
    typeof Math // output: 'object'
    
    // new 操作符,除 Function 以外的所有构造函数返回的类型都是 object
    typeof new Function() // output: 'function'
    typeof new Number(1) // output: 'object'
    typeof new String(1) // output: 'object'
    
    // 括号
    typeof 'Lorem' + ' Lorem' // output: 'string Lorem'
    typeof ('Lorem' + 'Lorem') // output: 'string'
    
    // null
    typeof null // output: 'object'
    • typeof null

      https://2ality.com/2013/10/typeof-null.html

      这个错误是JS第一版的遗留物,在这个版本中,JS中的值用 32 位存储。由类型标签(1-3 位) 和实际数据值组成,类型标签存储在单元的低位中。

      • 000:object,实际数据值是对象的引用

      • 1:int,实际数据值是 31 位有符号整数

      • 010:double,实际数据值是对双浮点数的引用

      • 100:string,实际数据值是对字符串的引用

      • 110:boolean,实际数据值是布尔值

      undefined 和 null 是特殊的。undefined 是整数 -2 ^ 30,即整数范围外的数字,null是 机器码 null 指针,它的 32位 都是 0 ,可以理解成一个对象类型标签,实际数据值全是 0 的引用。

      typeof 的引擎代码

      C
      JS_PUBLIC_API(JSType)
          JS_TypeOfValue(JSContext *cx, jsval v)
          {
              JSType type = JSTYPE_VOID;
              JSObject *obj;
              JSObjectOps *ops;
              JSClass *clasp;
      
              CHECK_REQUEST(cx);
              if (JSVAL_IS_VOID(v)) {  // (1)
                  type = JSTYPE_VOID;
              } else if (JSVAL_IS_OBJECT(v)) {  // (2)
                  obj = JSVAL_TO_OBJECT(v);
                  if (obj &&
                      (ops = obj->map->ops,
                      ops == &js_ObjectOps
                      ? (clasp = OBJ_GET_CLASS(cx, obj),
                          clasp->call || clasp == &js_FunctionClass) // (3,4)
                      : ops->call != 0)) {  // (3)
                      type = JSTYPE_FUNCTION;
                  } else {
                      type = JSTYPE_OBJECT;
                  }
              } else if (JSVAL_IS_NUMBER(v)) {
                  type = JSTYPE_NUMBER;
              } else if (JSVAL_IS_STRING(v)) {
                  type = JSTYPE_STRING;
              } else if (JSVAL_IS_BOOLEAN(v)) {
                  type = JSTYPE_BOOLEAN;
              }
              return type;
          }

      在(1)处,首先检查值是不是 undefined(void),通过比较值是否相等来判断

      C
      #define JSVAL_IS_VOID(v)  ((v) == JSVAL_VOID)

      在(2)处,检查值是否有对象类型标签,如果有对象类型标签,会通过有没有call判断是不是 可调用的(3)或者通过其内部属性 [[Class]] 判断有没有将其标记为函数(4),如果是可调用 的或者被标记位函数,那么它的类型是 function,否则,是object。 随后检查数字、字符串和布尔值,没有对 null 进行显式检查,所以对于 null 来说,typeof 认为它的类型标签是 object。可以通过C marco执行

      C
      #define JSVAL_IS_NULL(v)  ((v) == JSVAL_NULL)

      来判断 null,这似乎是一个非常明显的错误,但不要忘记完成 JavaScript 的第一个版本的 时间很短。

    • 暂存死区

      ES6 之前,typeof 总能保证对任何所给的操作数返回一个字符串,即使是未声明的变量, 使用 typeof 永远不会抛出错误。在加入了块级作用域的 let 和 const 之后,在其被声 明之前对块中的 let 和 const 变量使用 typeof 会抛出一个 ReferenceError。块作用 域变量在块的头部处于“暂存死区”,直至其被初始化,在这期间,访问变量将会引发错误。

      javascript
      typeof undeclaredVariable // output: undefined
      
      typeof newLetVariable; // output: ReferenceError: Cannot access 'newLetVariable' before initialization
      typeof newConstVariable // output: ReferenceError: Cannot access 'newLetVariable' before initialization
      typeof newClass // output: ReferenceError: Cannot access 'newLetVariable' before initialization
      
      let newLetVariable;
      const newConstVariable = 'foo'
      class newClass {}
    • document.all

      当前所有浏览器都暴露了一个类型为 undefined 的非标准宿主对象 document.all。

      javascript
      typeof document.all // output: 'undefined'
      
      Object.prototype.toString.call(document.all) // output: '[object HTMLAllCollection]'
  • instanceof

    javascript
    /**
     * 用于检测右边的 prototype 属性是否出现在左边的原型链上。 
     *
     * @param {Object} object - 某个实例对象
     * @param {Function} constructor - 某个构造函数
     */
    class Foo {}
    
    const foo = new Foo()
    foo instanceof Foo // output: true, because foo.__proto__ === Foo.prototype
    foo instanceof Object // output: true, because Foo.prototype.__proto__ === Object.prototype
    
    // 手动修改原型链指向导致结果变化
    foo.__proto__ = {}
    foo instanceof Foo // output: false
    
    // 检测不了基本数据类型,因为 instanceof 要求检测目标必须是实例对象
    const simpleStr = 'Lorem' // 非对象实例,类型是 string
    simpleStr.__proto__ === String.prototype // output: true,包装对象
    simpleStr instanceof String // output: false
    
    const simpleStr2 = String('Lorem') // 非对象实例,类型是 string
    simpleStr2.__proto__ === String.prototype // output: true,包装对象
    simpleStr2 instanceof String // output: false
    
    const newStr = new String('Lorem') // 实例对象,类型是 Object
    newStr instanceof String // output: true, because newStr.__proto__ === String.prototype
    newStr instanceof Object // output: true, because String.prototype.__proto__ === Object.prototype
    
    const nullProtoObj = Object.create(null)
    nullProtoObj.prototype // output: undefined
    nullProtoObj.__proto__ // output: undefined
    nullProtoObj instanceof Object // output: false, because nullProtoObj.__proto_ === undefined
    
    // 如果实例对象不是构造器的实例
    if (!(foo instanceof Foo)) {
      // ...
    }
    
    // 错误写法,!foo 在 instanceof 前被处理,所以总是在验证一个布尔值是不是 Foo 的实例
    if (!foo instanceof Foo) {}
  • constructor

所有对象(除Object.create(null)以外)原型上都具有 constructor 属性,指向该对象的构 造函数。

javascript
const o = {}
o.constructor === Object // true
const simpleStr = 'Lorem'
simpleStr.constructor === String // true,包装对象
const simpleStr2 = String('Lorem')
simpleStr2.constructor === String // true,包装对象
const newStr = new String('Lorem')
newStr.constructor === String // true
const o2 = Object.create(null)
o2.constructor === Object // false
o2.constructor === null // false
o2.constructor === undefined // true
  • Object.prototype.toString()

返回 "[object type]",其中 type 是对象的类型,可以通过这个方法来获取每个对象的类型。

javascript
const o = {}
o.toString() // output: [object Object]
Object.prototype.toString.call('Lorem') // output: [object String]
Object.prototype.toString.apply(1) // output: [object Number]
Object.prototype.toString.call(null) // output: [object Null]
Object.prototype.toString.call(undefined) // output: [object Undefined]
  • Array.isArray
javascript
/**
 * 用于确定传递的值是否是一个 Array 
 *
 * @param {Object} obj - 需要检测的值
 * @returns {boolean} 如果值是 Array,返回 true,否则返回 false。
 */
Array.isArray('Lorem') // output: false
Array.isArray([]))// output: true
Array.isArray(new Array()) // output: true
Array.isArray(new Array('foo', 'bar')) // output: true
Array.isArray(Array.prototype) // output: true
Object.prototype.toString.call(Array.prototype) // output: [object Array]
Array.prototype.__proto__ === Array.prototype // output: false
Array.prototype.__proto__ === Object.prototype // output: true
  • arrayLike
javascript
// JavaScript 权威指南
function isArrayLike(target) {
  if (
    target && // target 非 null | undefined 等
    typeof target === 'object' && // target是对象
    target.nodeType !== 3 && // DOM文本节点也有length属性, 需要排除
    isFinite(target.length) && // length是有限数值
    target.length >= 0 && // length是非负数
    target.length === Math.floor(target.length) && // length是整数
    target.length < Math.pow(2, 32) // length < 2 ^ 32
  ) {
    return true
  } else {
    return false
  }
}

// Vue.js
function isArrayLike (val) {
  return val != null && 
    isLength(val.length) && 
    !/^function$/.test(typeof val)
}

function isLength (val) {
  return typeof val === 'number' &&
    val > -1 &&
    val % 1 === 0 &&
    val <= Number.MAX_SAFE_INTEGER
}