V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
supermao
V2EX  ›  程序员

不吹不黑,来聊聊驳驳驳《我不是很懂 Node.js 社区的 DRY 文化》背后蛋疼的 JavaScript 类型判断

  •  
  •   supermao ·
    hongmaoxiao · 2018-04-20 13:58:56 +08:00 · 2127 次点击
    这是一个创建于 2169 天前的主题,其中的信息可能已经有所发展或是发生改变。

    GitHub 地址:JavaScript 类型判断知多少

    博客地址:JavaScript 类型判断知多少

    水平有限,欢迎批评指正

    getType

    Returns the native type of a value.

    Returns lowercased constructor name of value, "undefined" or "null" if value is undefined or null.

    const getType = v => v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();
    

    返回值的元类型。

    返回值的 constructor 名的小写字母。undefined 或者 null 将会返回 undefinednull

    ➜  code git:(master) cat getType.js
    const getType = v => v === undefined ? 'undefined' : v === 'null' ? 'null' : v.constructor.name.toLowerCase();
    
    console.log(getType(new Set([1, 2, 3])));
    console.log(getType(new Array(1, 2, 3)));
    console.log(getType(Object.create({a: 1})));
    ➜  code git:(master) node getType.js
    set
    array
    object
    

    字面意思很好理解,不多说。

    is

    Checks if the provided value is of the specified type.

    Ensure the value is not undefined or null using Array.includes(), and compare the constructor property on the value with type to check if the provided value is of the specified type.

    const is = (type, val) => ![, null].includes(val) && val.constructor === type;
    

    检测提供的 val 是否属于指定的类型 type

    运用 Array.includes() 确保 undefinednull 被排除在外,并且比较 valconstructor 属性和指定的类型 type 是否相等。

    ➜  code git:(master) ✗ cat is.js
    const is = (type, val) => ![, null].includes(val) && val.constructor === type;
    
    console.log(is(Array, [1]));
    console.log(is(ArrayBuffer, new ArrayBuffer()));
    console.log(is(Map, new Map()));
    console.log(is(RegExp, /./g));
    console.log(is(Set, new Set()));
    console.log(is(WeakMap, new WeakMap()));
    console.log(is(WeakSet, new WeakSet()));
    console.log(is(String, ''));
    console.log(is(String, new String('')));
    console.log(is(Number, 1));
    console.log(is(Number, new Number(1)));
    console.log(is(Boolean, true));
    console.log(is(Boolean, new Boolean(true)));
    
    ➜  code git:(master) ✗ node is.js
    true
    true
    true
    true
    true
    true
    true
    true
    true
    true
    true
    true
    true
    

    MDNconstructor ,你值得拥有。

    isArrayLike

    Checks if the provided argument is array-like (i.e. is iterable).

    Use the spread operator (...) to check if the provided argument is iterable inside a try... catch block and the comma operator (,) to return the appropriate value.

    const isArrayLike = val => {
      try {
        return [...val], true;
      } catch (e) {
        return false;
      }
    };
    

    检测变量是否是类数组(比如是可迭代对象)。

    结合 try... catch 使用 扩展运算表达式对提供的变量进行是否可迭代的检测,同时使用 , 运算表达式返回适当的结果。

    ➜  code git:(master) ✗ cat isArrayLike.js
    const isArrayLike = val => {
      try {
        return [...val], true;
      } catch (e) {
        return false;
      }
    };
    
    console.log(isArrayLike('abc'));
    console.log(isArrayLike(null));
    
    ➜  code git:(master) ✗ node isArrayLike.js
    true
    false
    

    在这里类数组判断的依据是变量可迭代,所以对应的检测方法就是可以用扩展运算表达式 进行展开,如果能正确展开,返回 true ,否则返回 false

    return [...val], true ,这里如果能展开,会能执行到 , 表达式,返回 true ,否则将进入 catch 流程而返回 false

    lodash 对于 isArrayLike (类数组)的判断依据是变量不是 undefined 或者 null ,也不是 function ,同时含有 length 属性且该属性值是一个整数并且大于等于 0 且小于等于 Number.MAX_SAFE_INTEGER( 9007199254740991 )

    const MAX_SAFE_INTEGER = 9007199254740991
    
    function isLength(value) {
      return typeof value == 'number' &&
        value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER
    }
    
    function isArrayLike(value) {
      return value != null && typeof value != 'function' && isLength(value.length)
    }
    

    代码和逻辑一一对应,不细讲。不过换我写的话,我会把 == 都写成 === 。即使 value != null 写成 value !== null && value !== undefined 会变得很长。

    isBoolean

    Checks if the given argument is a native boolean element.

    Use typeof to check if a value is classified as a boolean primitive.

    const isBoolean = val => typeof val === 'boolean';
    

    检测提供的变量是否是布尔类型。

    typeof 来检测 val 是否应该归为布尔原型。

    ➜  code git:(master) ✗ cat isBoolean.js
    const isBoolean = val => typeof val === 'boolean';
    
    console.log(isBoolean(null));
    console.log(isBoolean(false));
    
    ➜  code git:(master) ✗ node isBoolean.js
    false
    true
    

    布尔型直接用 typeof 就能判断。

    isEmpty

    Returns true if the a value is an empty object, collection, map or set, has no enumerable properties or is any type that is not considered a collection.

    Check if the provided value is null or if its length is equal to 0.

    const isEmpty = val => val == null || !(Object.keys(val) || val).length;
    

    如果 value 是一个空的 objectcollectionmap 或者 set ,或者没有任何可枚举的属性以及任何没有被当做 collection 的类型都返回 true

    检测提供的变量是否为 null 或者变量的 length 属性是否等于 0。

    ➜  code git:(master) ✗ cat isEmpty.js
    const isEmpty = val => val == null || !(Object.keys(val) || val).length;
    
    console.log(isEmpty(new Map()));
    console.log(isEmpty(new Set()));
    console.log(isEmpty([]));
    console.log(isEmpty({}));
    console.log(isEmpty(''));
    console.log(isEmpty([1, 2]));
    console.log(isEmpty({ a: 1, b: 2 }));
    console.log(isEmpty('text'));
    console.log(isEmpty(123));
    console.log(isEmpty(true));
    
    ➜  code git:(master) ✗ node isEmpty.js
    true
    true
    true
    true
    true
    false
    false
    false
    true
    true
    

    这里注意的是 val == null 用的是 == 而不是 === ,也就是说 undefined 也会返回 true

    isFunction

    Checks if the given argument is a function.

    Use typeof to check if a value is classified as a function primitive.

    const isFunction = val => typeof val === 'function';
    

    检测提供的变量的类型是否是 function

    使用 typeof 进行判断给定的变量是否是 function 原型。

    ➜  code git:(master) ✗ cat isFunction.js
    const isFunction = val => typeof val === 'function';
    
    console.log(isFunction('x'));
    console.log(isFunction(x => x));
    
    ➜  code git:(master) ✗ node isFunction.js
    false
    true
    

    类型为 function 的判断比较简单,只需要用 typeof 就可以区分。

    isNil

    Returns true if the specified value is null or undefined, false otherwise.

    Use the strict equality operator to check if the value and of val are equal to null or undefined.

    const isNil = val => val === undefined || val === null;
    

    指定的变量是 null 或者 undefined ,返回 true ,否则返回 false

    使用严格相等运算符去对变量进行是否等于 null 或者 undefined 的检测。

    ➜  code git:(master) ✗ cat isNil.js
    const isNil = val => val === undefined || val === null;
    
    console.log(isNil(null));
    console.log(isNil(undefined));
    
    ➜  code git:(master) ✗ node isNil.js
    true
    true
    

    这还真没啥好说的了,我觉得名字起得非常好,go 也用 nil

    isNull

    Returns true if the specified value is null, false otherwise.

    Use the strict equality operator to check if the value and of val are equal to null.

    const isNull = val => val === null;
    

    如果变量是 null ,返回 true ,否则返回 false

    使用严格相等运算符判断变量是否为 null

    ➜  code git:(master) ✗ cat isNull.js
    const isNull = val => val === null;
    
    console.log(isNull(null));
    
    ➜  code git:(master) ✗ node isNull.js
    true
    

    isUndefined

    Returns true if the specified value is undefined, false otherwise.

    Use the strict equality operator to check if the value and of val are equal to undefined.

    const isUndefined = val => val === undefined;
    

    如果变量是 undefined ,返回 true ,否则返回 false

    使用严格相等运算符判断变量是否为 undefined

    ➜  code git:(master) ✗ cat isUndefined.js
    const isUndefined = val => val === undefined;
    
    console.log(isUndefined(undefined));
    
    ➜  code git:(master) ✗ node isUndefined.js
    true
    

    isNumber

    Checks if the given argument is a number.

    Use typeof to check if a value is classified as a number primitive.

    const isNumber = val => typeof val === 'number';
    

    检测提供的变量是否为 number 型。

    使用 typeof 检测给定变量是否是 number 原型。

    ➜  code git:(master) ✗ cat isNumber.js
    const isNumber = val => typeof val === 'number';
    
    console.log(isNumber('1'));
    console.log(isNumber(1));
    console.log(isNumber(NaN));
    
    ➜  code git:(master) ✗ node isNumber.js
    false
    true
    true
    

    这里注意的是 NaN 也是一个 number 类型。

    isObject

    Returns a boolean determining if the passed value is an object or not.

    Uses the Object constructor to create an object wrapper for the given value. If the value is null or undefined, create and return an empty object. Οtherwise, return an object of a type that corresponds to the given value.

    const isObject = obj => obj === Object(obj);
    

    检测给定的变量是否为 object 类型。

    使用 Objectconstructor 对给定的变量构造一个对象。如果变量是 null 或者 undefined ,将会生成一个空对象。否则生成一个类型和变量本身相等的对象。

    ➜  code git:(master) ✗ cat isObject.js
    const isObject = obj => obj === Object(obj);
    
    console.log(isObject([1, 2, 3, 4]));
    console.log(isObject([]));
    console.log(isObject(['Hello!']));
    console.log(isObject({ a: 1 }));
    console.log(isObject({}));
    console.log(isObject(x => x));
    console.log(isObject(true));
    
    ➜  code git:(master) ✗ node isObject.js
    true
    true
    true
    true
    true
    true
    false
    

    数组、对象、方法都会返回 true 。这里跟 lodashisObject 有点不太一样:

    function isObject(value) {
      const type = typeof value
      return value != null && (type == 'object' || type == 'function')
    }
    

    对于 null 来说 typeof value === 'object' ,所以这是必须要排除掉的, 但是直接用 value != null 进行判断比 typeof 运行的效率高。

    对于数组和对象来说,typeof 都会返回 object 。所以 type == 'object' 就能包含两者。

    另外 typeof 值为 function 也满足,所以加上一个 || 即可。

    其实本质上用构造函数和 lodash 的判断方法一样,但是 lodash 没有涉及原型链的操作。所以效率高,虽然写法上比较费事。

    isObjectLike

    Checks if a value is object-like.

    Check if the provided value is not null and its typeof is equal to 'object'.

    const isObjectLike = val => val !== null && typeof val === 'object';
    

    检测一个变量是否是类对象。

    只需要判断给定变量不是 nulltypeof 结果与 object 相等即可。

    ➜  code git:(master) ✗ cat isObjectLike.js
    const isObjectLike = val => val !== null && typeof val === 'object';
    
    console.log(isObjectLike({}));
    console.log(isObjectLike([1, 2, 3]));
    console.log(isObjectLike(x => x));
    console.log(isObjectLike(null));
    
    ➜  code git:(master) ✗ node isObjectLike.js
    true
    true
    false
    false
    

    这里判断方法和 lodashisObjectLike 一样。

    isPlainObject

    Checks if the provided value is an object created by the Object constructor.

    Check if the provided value is truthy, use typeof to check if it is an object and Object.constructor to make sure the constructor is equal to Object.

    const isPlainObject = val => !!val && typeof val === 'object' && val.constructor === Object;
    

    检测提供的变量是否是一个由对象的 constructor 创建的对象。

    先判断变量的布尔运算是否为 true ,然后使用 typeof 判断变量是否为 object ,最后判断变量的 constructor 是否是 object 。这三个步骤的运算都为 true 才返回 true

    ➜  code git:(master) ✗ cat isPlainObject.js
    const isPlainObject = val => !!val && typeof val === 'object' && val.constructor === Object;
    
    console.log(isPlainObject({ a: 1 }));
    console.log(isPlainObject(new Map()));
    console.log(isPlainObject(Object.create(null)));
    
    ➜  code git:(master) ✗ node isPlainObject.js
    true
    false
    false
    

    代码正如注解一样一一对应,但意外的是它和 lodashisPlainObject 是不一样的,唯一差别是 lodashObject.create(null) 创建的对象也归为 plainObject 。但对应上各自的解释都是没错的。

    lodashisPlainObject 代码实现如下:

    const objectProto = Object.prototype
    const hasOwnProperty = objectProto.hasOwnProperty
    const toString = objectProto.toString
    const symToStringTag = typeof Symbol != 'undefined' ? Symbol.toStringTag : undefined
    
    // baseGetTag
    function baseGetTag(value) {
      if (value == null) {
        // undefined 和 null 直接用这种判断方式比较 toString 调用要快
        return value === undefined ? '[object Undefined]' : '[object Null]'
      }
      // 排除 Symbol 时直接使用 toString 调用即可
      if (!(symToStringTag && symToStringTag in Object(value))) {
        return toString.call(value)
      }
      const isOwn = hasOwnProperty.call(value, symToStringTag)
      const tag = value[symToStringTag]
      let unmasked = false
      try {
        // 我猜这里是尝试把它的自有属性赋值为 undefined 是为了不干扰下面 toString 的调用
        value[symToStringTag] = undefined
        unmasked = true
      } catch (e) {}
    
      const result = toString.call(value)
      // 如果 try 成功,需要还原
      if (unmasked) {
        if (isOwn) {
          // 如果是自有属性,需要重新把值给回去
          value[symToStringTag] = tag
        } else {
          // 如果不是自有属性,需要删除掉
          delete value[symToStringTag]
        }
      }
      return result
    }
    
    // isObjectLike
    function isObjectLike(value) {
      return typeof value == 'object' && value !== null
    }
    
    // isPlainObject
    function isPlainObject(value) {
      if (!isObjectLike(value) || baseGetTag(value) != '[object Object]') {
        return false
      }
      if (Object.getPrototypeOf(value) === null) {
        // 这里的 value 就是通过 Object.create(null) 来创建的
        return true
      }
      let proto = value
      while (Object.getPrototypeOf(proto) !== null) {
        // 我猜是递归获取继承的 prototype
        proto = Object.getPrototypeOf(proto)
      }
      return Object.getPrototypeOf(value) === proto
    }
    

    lodash 的代码我认为应该加注释的都加上了,不清楚的可以 MDN 自查:

    1. Symbol.toStringTag
    2. Object.getPrototypeOf()

    isPrimitive

    Returns a boolean determining if the passed value is primitive or not.

    Use Array.includes() on an array of type strings which are not primitive, supplying the type using typeof. Since typeof null evaluates to 'object', it needs to be directly compared.

    const isPrimitive = val => !['object', 'function'].includes(typeof val) || val === null;
    

    检测变量是否是基本数据类型。

    使用 Array.includes() 结合 typeof 把不是基本数据类型的排除掉。由于 typeof null 返回的是 object ,需要直接对它进行单独判断。

    MDN 上关于 primitive 的解释如下:

    A primitive (primitive value, primitive data type) is data that is not an object and has no methods. In JavaScript, there are 6 primitive data types: string, number, boolean, null, undefined, symbol (new in ECMAScript 2015).

    primitive ( primitive 数值, primitive 数据类型) 是指不是一个 object 并且不包含方法的数据。在 JavaScript 中,属于 primitive 的是 stringnumberbooleannullundefinedsymbol ( ECMAScript2015 新增)。

    ➜  code git:(master) ✗ cat isPrimitive.js
    const isPrimitive = val => !['object', 'function'].includes(typeof val) || val === null;
    
    console.log(isPrimitive(null));
    console.log(isPrimitive(50));
    console.log(isPrimitive('Hello!'));
    console.log(isPrimitive(false));
    console.log(isPrimitive(Symbol()));
    console.log(isPrimitive([]));
    
    ➜  code git:(master) ✗ node isPrimitive.js
    true
    true
    true
    true
    true
    false
    

    !['object', 'function'].includes(typeof val) 这里就是把 typeof 运算结果为 object 或者 function 都排除掉,由于 nulltypeofobject ,而 includes 会把它也排除了,需要用 || 把它加回来。

    如果你还有印象的话, isObject 正好是 isPrimitive 的对立面,所以其实我觉得 !isObject 也行。

    lodash 暂时没有提供 isPrimitive 的计划,但在 issues 1406 中提到了可以用 !_.isObject(value) 或者 _.negate(_.isObject) 代替。

    因为有字数限制,无法把全文发完,想看完整文章请看篇首的 Github 链接或者博客链接

    3 条回复    2018-04-21 11:33:23 +08:00
    congeec
        1
    congeec  
       2018-04-21 01:24:20 +08:00 via iPhone
    Python 的优势就体现出来了
    supermao
        2
    supermao  
    OP
       2018-04-21 01:37:36 +08:00
    @congeec 哈哈,只要做前端相关,js 躲不过去
    congeec
        3
    congeec  
       2018-04-21 11:33:23 +08:00 via iPhone
    @supermao typescript 能缓解好多吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1339 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 23:37 · PVG 07:37 · LAX 16:37 · JFK 19:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.