New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Javascript进阶系列之类型检测 #2

Open
snailTJ opened this Issue Jul 3, 2018 · 1 comment

Comments

Projects
None yet
2 participants
@snailTJ
Copy link
Owner

snailTJ commented Jul 3, 2018

前言

在js中一共有7种原始数据类型,UndefinedNullBooleanStringNumberObjectSymbol。还有在ESMA-262中定义的本地对象(引用类型),比如DateRegExp(正则)Error(错误)等。更深入还有plainObject(纯对象)、isLikeArray(类数组)等等。
在应用中我们也经常经常需要对数据进行判断,判断数据属于哪种类型

typeof

引用《Javascript权威指南》一句话

typeof 是一元操作符,放在其单个操作数的前面,操作数可以是任意类型。返回值为表示操作数类型的一个字符串

说明typeof操作符中操作数可以是任意的类型,同时返回的也是一个字符串

当我们用typeof对原始数据类型进行检测的时候,返回结果分别为undefined、object、boolean、number、string、object、symbol。

对于函数,typeof也能检测出来

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

从以上结果可以发现除了Null和Object,其他通过typeof操作符都可以检测出其类型。但是js中很多内置对象,比如Date、正则等。

var  a = new Date()
var b = new RegExp()
console.log(typeof a) //object
console.log(typeof b) //object

所以,typeof还是远远不够的,所以还需要更好的判断方式

Object.prototype.toString

在规范ECMA-262/ES5中,详细讲解了该方法调用过程。原文如下:

  • 1.If the this value is undefined, return "[object Undefined]".
  • 2.If the this value is null, return "[object Null]".
  • 3.Let O be the result of calling ToObject passing the this value as the argument.
  • 4.Let class be the value of the [[Class]] internal property of O.
  • 5.Return the String value that is the result of concatenating the three Strings "[object ", class, and "]".

下面是翻译成中文的:

  • 1.如果this的值是undefined,就返回**[object Undefined]**
  • 2.如果this的值是null,就返回**[object Null]**
  • 3.让O成为**ToObject(this)**的结果
  • 4.让class成为 O 的内部属性 [[Class]] 的值
  • 5.最后返回由 "[object " 和 class 和 "]" 三个部分组成的字符串

通过以上发过程可以看出,所有的结构返回都是**"[object** " 和 class 和 "]" 组成的字符串,class是根据判断的对象的内部属性,其实这个class就是识别对象类型的一个属性

如下例子:

var number = 1;          // [object Number]
var string = '123';      // [object String]
var boolean = true;      // [object Boolean]
var und = undefined;     // [object Undefined]
var nul = null;          // [object Null]
var obj = {a: 1}         // [object Object]
var array = [1, 2, 3];   // [object Array]
var date = new Date();   // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g;          // [object RegExp]
var func = function a(){}; // [object Function]
var promise = new Promise((resolve,reject) => {}) //[object Promise]
var symbol = Symbol(); // [object Symbol]
var set = new Set(); // [object Symbol]
var map = new Map(); // [object Symbol]
var gentor = function*(){} //[object GeneratorFunction]
var asy = async function(){} //[object AsyncFunction]
function checkType() {
for (var i = 0; i < arguments.length; i++) {
        console.log(Object.prototype.toString.call(arguments[i]))
    }
}

checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func,promise,symbol,set,map, gentor, asy)

经检测,对于Proxy代理对象。上述返回的是**[object Object]**

type函数

其实除了上面几种还有很多种类型,但是基本上我们使用的情况下就会检测以上这几种,所以我们可以通过Object.prototype.toString写一个函数来实现通用的类型检测函数

// type函数是参照jquery实现
const type = (function(){
    var class2type = {};
    // 生成class2type映射
    "Boolean Number String Function Array Date RegExp Object Error Set Map Symbol Promise".split(" ").map(function(item, index) {
        class2type["[object " + item + "]"] = item.toLowerCase();
    })
    return function(obj){
        // 这里是为了兼容IE6,IE6下undefined和null返回的都是[object object]
        if(obj == null){
            return obj + ''
        }
        return typeof obj === 'object' || typeof obj === 'function' 
            ? class2type[Object.prototype.toString.call(obj)]
            : typeof obj
    }
})()

类型检测API

类型检测不止以上几种,还有很多其他不是常见类型,但是实现都是类似,下面我们就看看一些类型实现

isObject

如果一个对象不是null,并且typeof结果是object或者function就认为是object,代码如下:

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

数组

一般实现都是通过Array的原生方法isArray来实现的,下面实现兼容版本

var isArray = Array.isArray || function(obj){
    return type(obj)
}

isArrayLike

我们一般认为只要存在length属性的对象就认为是类数组

jquery中实现判断类数组如下:

function isArrayLike( obj ) {
    var length = !!obj && "length" in obj && obj.length,
        type = toType( obj );

    // 排除function和window对象
    if ( isFunction( obj ) || isWindow( obj ) ) {
        return false;
    }

    return type === "array" || length === 0 ||
        typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}

看代码返回值,只要三个条件满足其一,便返回true

  • 数组
  • 长度为0
  • length属性是大于0的数字类型,并且obj[length - 1]必须存在

第一个条件如果是数组,那么必然也是类数组。对于如果是length为0的,因为函数中的arguments对象,任何情况下它的kength属性值都为0,并且它确实是一个类数组

function arg(){
    console.log(arguments) //{length:0,...}
}
arg()

isPlainObject

isPlainObject按照字面意思,就是纯粹的对象,也就是通过字面量**{},或者new Object**创建的对象。

同时在实现中认为一个没有原型的对象也是纯对象,比如Object.create(null)创建的对象

实现如下(稍微改动一点点)

function isPlainObject(value) {
    if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
        return false;
    }
    var proto = getPrototype(value);
    if (proto === null) {
        return true;
    }
    var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
    return typeof Ctor == 'function' && Ctor instanceof Ctor &&
        Object.prototype.toString.call(Ctor) ===  Object.prototype.toString.call(Object)
}

总结

很多库中都实现了以上的方法,但是他们的实现都有所不同。因为使用场景不同,所以在各种库中都有所权衡

参考文章

@liuyib

This comment has been minimized.

Copy link

liuyib commented Oct 26, 2018

写的不错,不过有个地方打错了,第一段中,不是ESMA-262,应该是ECMA-262

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment