最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 如何准确判断一个对象的类型?

    正文概述 掘金(字节前端)   2021-01-28   407

    作为前端的同学,我们应该都知道可以使用 typeof 和 instanceof 在运行时判断JavaScript对象的类型。

    对于原始类型(primitive type)的数据,大部分可使用 typeof。在 JavaScript 中,primitive 类型包括 Null、Undefined、Boolean、Number、String、Symbol,如果算上 Stage3 的,还有 BigInt。

    这里说大部分,是因为除了一个例外,那就是 Null。

    console.log(undefined); // undefined
    console.log(null); // object
    console.log(1.0); // number
    console.log(true); // boolean
    console.log('hello'); // string
    console.log(Symbol()); // symbol
    console.log(100n);  // bigint
    

    上述7种原始类型的数据中,除了typeof null返回"object"之外,其他的都返回对应类型名的小写字母。

    typeof null返回"object"是因为历史原因,这也不是我们讨论的重点,大家只要记住typeof null === 'object'这个例外就好。

    除了原始类型外,对象返回'object',函数返回'function'。那么我们如果要判断不同类型的对象,就不能用typeof了:

    const arr = [];
    const obj = {};
    const date = new Date();
    const regexp = /a/;
    
    console.log(typeof arr);    // object
    console.log(typeof obj);    // object
    console.log(typeof date);   // object
    console.log(typeof regexp); // object
    

    那么我们判断对象类型的时候,可以使用 instanceof:

    const arr = [];
    const obj = {};
    
    console.log(arr instanceof Array);   // true
    console.log(arr instanceof Object);  // true
    console.log(obj instanceof Array);   // false
    console.log(obj instanceof Object);  // true
    

    ? 注意 instanceof 是能匹配类型的父类的,所以arr instanceof Arrayarr instanceof Object都是 true,因为 Object 是 Array 的父类。

    满足class extends和原型链规则的父子类关系的对象都能被匹配:

    class Base {
    
    }
    
    class Current extends Base {
    
    }
    
    const obj = new Current();
    
    console.log(obj instanceof Current); // true
    console.log(obj instanceof Base); // true
    
    function Foo() {
    
    }
    
    function Bar() {
    
    }
    
    Bar.prototype = new Foo();
    
    const obj = new Bar();
    
    console.log(obj instanceof Bar); // true
    console.log(obj instanceof Foo); // true
    

    注意如果我们修改 obj 的原型链能改变instanceof的结果:

    function Other() {
    
    }
    obj.__proto__ = new Other();
    
    console.log(obj instanceof Other); // true
    console.log(obj instanceof Foo); // false
    

    实际上,只要一个类型 Type 的 prototype 在一个对象 obj 的原型链上,那么obj instanceof Type就是 true,否则就是 false。

    instanceof 的局限性

    如果在 realm 的情况下,比如页面上包含 iframe,将当前页面上的对象传给 iframe 执行,使用 instanceof 判断就会出问题,我们看一个简单的例子:

    var arr = [1, 2, 3];
    
    console.log(arr instanceof Array); // true
    
    var sandbox = document.createElement('iframe');
    document.body.append(sandbox);
    
    sandbox.contentDocument.open();
    sandbox.contentDocument.write(`<script>
    console.log(parent.arr);  // 1,2,3
    console.log(parent.arr instanceof Array); // false
    </script>`);
    sandbox.contentDocument.close();
    

    上面的例子里,在当前 window 中,arr instanceof Array是 true,但是到了 sandbox 里面,parent.arr instanceof Array变成 false。这是因为,两个 Array 类型在不同的 realm 中,实际上要使用:parent.arr instanceof parent.Array,这样返回的就是true。

    而 typeof 是字符串比较,自然不受此影响:

    var arr = [1, 2, 3];
    var str = 'hello';
    
    console.log(arr instanceof Array); // true
    
    var sandbox = document.createElement('iframe');
    document.body.append(sandbox);
    
    sandbox.contentDocument.open();
    sandbox.contentDocument.write(`<script>
    console.log(parent.arr);  // 1,2,3
    console.log(parent.arr instanceof Array); // false
    console.log(typeof str === 'string'); // true
    </script>`);
    sandbox.contentDocument.close();
    

    ??【冷知识】结论:使用 instanceof 判断的时候,在多 realm 环境中要小心使用。

    用 constructor 判断

    有时候我们不希望匹配父类型,只希望匹配当前类型,那么我们可以用 constructor 来判断:

    const arr = [];
    
    console.log(arr.constructor === Array); // true
    console.log(arr.constructor === Object); // false
    

    当然和 instanceof 的问题一样,遇到多 realm 的环境,constructor 判断要确保类型是和判断的对象在同一个 realm 下。不过我们如果想匹配不同 realm,在一些特殊情况下,我们可以使用 constructor 的只读属性 name:

    parent.arr.constructor.name === 'Array'
    

    ??对象的 constructor 会返回它的类型,而类型在定义的时候,会创建一个 name 只读属性,值为类型的名字。

    class Foo {
    
    }
    console.log(Foo.name); // Foo
    
    const foo = new Foo();
    console.log(foo.constructor === Foo); // true
    console.log(foo.constructor.name === 'Foo'); // true
    

    不过使用 constructor.name 有非常大的限制,如果使用定义匿名的 class,那么 name 就变成空的:

    const MyClass = (function() {
      return class {
    
      }
    }());
    
    console.log(MyClass.name); // ''
    

    另外如果使用 es-modules,我们 import 的类名不一定是包里面的类名。

    再者,如果我们使用脚本压缩工具,那么文件中的类名会被替换为短名,那样的话,name 属性的名字也随着改变了。

    所以依赖 constructor.name 来判断不是一个好的方案

    Array.isArray

    如果我们只是针对数组来判断,那么我们可以使用 Array.isArray

    这个方法能够判断一个对象是否是一个 Array 类型或者其派生类型。

    class MyArray extends Array {}
    const arr1 = [];
    const arr2 = new MyArray();
    
    console.log(Array.isArray(arr1), Array.isArray(arr2)); // true, true
    

    Array.isArray 在多 realm 中能正常判断:

    var arr = [1, 2, 3];
    
    var sandbox = document.createElement('iframe');
    document.body.append(sandbox);
    
    sandbox.contentDocument.open();
    sandbox.contentDocument.write(`<script>
    console.log(Array.isArray(parent.arr)); // true
    </script>`);
    sandbox.contentDocument.close();
    

    Array.isArray 给我们带来启发,既然在多 realm 环境中,使用 instanceof 不安全,那么我们可以构造类似 Array.isArray 的方法来实现我们自己的 isType 方法。

    class Foo {
      static isFoo(obj) {
        // ...
      }
    }
    

    那么我们需要给予类型的实例一个标志,以使得我们能够根据这一标志来判断:

    class Foo {
      static isFoo(obj) {
        return !!obj.isFooInstanceTag;
      }
      get isFooInstanceTag() {
        return true;
      }
    }
    

    为了避免暴露 isFooInstanceTag 这样的属性名,这篇文章使用了 Symbol.for,这样更好:

    const instanceTag = Symbol.for('check_is_Foo_instance_tag');
    class Foo {
      static isFoo(obj) {
        return !!obj[instanceTag];
      }
      get [instanceTag]() {
        return true;
      }
    }
    

    注意这里必须使用Symbol.for而不能直接使用Symbol,因为在不同的 realm 下,同样 key 的Symbol.for返回的是相同 ID。

    stringTag

    如果你看过一些库的早期实现,你会发现使用 Object.prototype.toString 来做类型判断的方式:

    var ostring = Object.prototype.toString;
    function isArray(it) {
      return ostring.call(it) === '[object Array]';
    }
    

    比如这是 requirejs 里面的代码片段。

    在早期的 JS 中,不支持 Array.isArray 时,很多库是利用这个方法来判断数组的,同样我们还可以判断其他类型:

    const ostring = Object.prototype.toString;
    console.log(ostring.call(/a/)); // [object RegExp]
    console.log(ostring.call(new Date())); // [object Date]
    

    不过注意不要使用 stringTag 判断 Number、Boolean 等 primitive 类型,因为它没法区分装箱的类型:

    const ostring = Object.prototype.toString;
    console.log(ostring.call(1.0)); // [object Number]
    console.log(ostring.call(new Number(1.0))); // [object Number]
    

    像上面的代码,1.0new Number(1.0)的stringTag都返回[object Number],但是我们一般认为1.0和new Number(1.0)是两个不同的类型。

    在 ES2015 之前,我们不能自定义类型的 stringTag,我们自己定义的任何类型实例的 stringTag 都返回[object Object]

    ?? 但是现在,我们可以通过实现Symbol.toStringTag的 getter 来自定义类型的 stringTag:

    class Foo {
      get [Symbol.toStringTag]() {
        return 'Foo';
      }
    }
    
    const foo = new Foo();
    console.log(Object.prototype.toString.call(f)); // [object Foo]
    

    好了,以上是类型判断相关的几种办法,如果你还有什么想要讨论的,欢迎在评论区评论呀~


    欢迎关注「 字节前端 ByteFE 」

    简历投递联系邮箱「 tech@bytedance.com 」


    起源地下载网 » 如何准确判断一个对象的类型?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元