最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 面试快回快答系列(二):基本数据类型和类型检测

    正文概述 掘金(鼠子的前端CodeLife)   2021-02-11   502

    读前必看

    本次快文快答的主题是基本数据类型和类型检测

    在阅读本文之前,为了节约大家时间,可以先通过下方介绍了解一下鼠子的面试快问快答系列,如果觉得该系列不适合您或有什么不妥之处,欢迎给我留言。

    Q1:谈谈基本数据类型

    题目分析

    首先基本数据类型有几种?5种?7种!

    最常见的五种基本数据类型:stringnumberbooleanundefinednull

    以及ES6后新增的symbol

    ES10草案提出的BigInt

    但是这个题一般只是个引子,答这么多已经OK,但是为了防止面试官深挖,我们来对每个基本数据类型来加深一下理解。

    加深理解

    本节主要谈谈symbolBigInt,剩下五种会在Q2中讲解。

    symbol介绍与衍生问题

    下面选取MDN的一段说明

    我们可以把symbol当成对象的属性名,避免覆盖对象原属性,因为每调用一次Symbol()都会得到一个不重复的独一无二的值。

    同时Symbol提供了两个函数Symbol.for(key)Symbol.keyFor(sym)分别用于在全局Symbol注册表进行注册和检索。

    来做些简单的代码示例:

    var sym1 = Symbol();
    var sym2 = Symbol('foo');
    var sym3 = Symbol('foo');
    console.log(sym1 === sym2); // false 
    console.log(sym2 === sym3); // false 即使入参相同也是不同的值
    
    var sym4 = Symbol.for('foo'); // 在全局注册表注册,key值为foo
    var sym5 = Symbol.for('foo'); // 重复注册
    console.log(sym2 === sym4); // false 入参都是foo,但是值不相等
    console.log(sym4 === sym5); // true 重复注册会返回注册过的值
    
    // 从全局注册表中根据symbol取键值
    var key2 = Symbol.keyFor(sym2)
    var key5 = Symbol.keyFor(sym5); 
    console.log(key2); // undefined
    console.log(key5); // foo
    

    衍生问题symbol有什么应用?

    1. 用来作常量

      const CASE_1 = Symbol();
      const CASE_2 = Symbol();
      let type = CASE_1;
      switch( type) {
          case CASE_1:
              break
          case CASE_2:
              break
          default:
      }
      

      好处在于,保证绝对不会污染其他常量。

    2. 用来作对象属性名

      const FUNC = Symbol()
         
      let obj = {
          [FUNC]:function(){}
      }
      

      好处在于,可以防止原有对象属性被覆盖。

      但是值得一题的是,symbol命名的属性只能通过Object.getOwnPropertySymbols()Reflect.ownKeys()来获取到。

    3. 用来作类的私有属性/方法

      // 在文件a.js中
         
      const PASSWORD = Symbol()
         
      class Login {
        constructor(username, password) {
          this.username = username
          this[PASSWORD] = password
        }
         
        checkPassword(pwd) {
            return this[PASSWORD] === pwd
        }
      }
         
      export default Login
      
      //在文件 b.js 中
         
      import Login from './a'
         
      const login = new Login('admin', '123456')
         
      login.checkPassword('123456')  // true
         
      login.PASSWORD  // 无法访问到
      login[PASSWORD] // 无法访问到
      login["PASSWORD"] // 无法访问到
      
    4. 特别的,在window嵌套的情况下(如iframe),可用于共享传值

      // 外层
      let gs1 = Symbol.for('global_symbol_1')  //注册一个全局Symbol
      // iframe中
      let gs2 = Symbol.for('global_symbol_1')  //获取全局Symbol
         
      gs1 === gs2  // true
      

    BigInt

    BigInt了解得不多,就简单理解成它的目的是支持比Number数据类型支持的范围更大的整数值

    因为BigInt只是作为ES10的草案,很多环境不支持,所以下面的问题也不再考虑它,大家作为前沿了解即可。

    Q2:谈谈用typeof做类型检测

    题目分析

    这题坑不多,直接上代码片段看看结果。

    var num  = 1;
    var str = '1';
    var bool = true;
    var a = undefined;
    var b = null;
    var symbol = Symbol();
    console.log(typeof(num)); //"number"
    console.log(typeof(str)); //"string"
    console.log(typeof(bool)); //"boolean"
    console.log(typeof(a)); //"undefined"
    console.log(typeof(b)); //"object"
    console.log(typeof(symbol));//"symbol"
    

    在这里唯一不太符合直觉的就是null,它返回的是**“object”**。对此,MDN的解释为

    加深理解

    这个仅仅是个人的理解,为了方便记忆,我从语义上把null看成是表示空对象,所以typeof null==='object'是一个很好理解的事情。

    Q3:如何检测引用类型

    题目分析

    一般Q1~Q3是这三个问题是一起抛出来的,引用类型当然指的就是在栈内存中用指针指向的存放在堆内存中的对象(当然js中没有明确使用指针这个概念,一般提及类似的概念会用reference即引用的说法)。

    万物起源于Object(除非你借助Object.create(null)去创建一个原型对象为null的对象)。

    对于引用类型,我们最常见的手段是用instanceof,从原型链的角度去进行判断,左侧一般是实例对象,右侧一般是构造函数,若构造函数的原型对象在实例对象的原型链上,那么就返回true

    除此之外,我们也可以用Object.prototype.toString(),这个函数也可以检测到很多内置对象,当然我们需要借助call来显式绑定一下this指向。

    // 内置Date对象
    console.log(new Date instanceof Date); // true
    console.log(new Date instanceof Object); // true
    console.log(Object.prototype.toString.call(new Date));//"[object Date]"
    
    // 内置Function对象
    console.log(new Function instanceof Function); // true
    console.log(new Function instanceof Object); // true
    console.log(Object.prototype.toString.call(new Function));//"[object Function]"
    
    // 内置Array对象
    console.log([] instanceof Array); // true
    console.log([] instanceof Object); // true
    console.log(Object.prototype.toString.call([]));//"[object Array]"
    
    // 普通的Object
    console.log({} instanceof Object); // true
    console.log(Object.prototype.toString.call({}));//"[object Object]"
    

    加深理解

    特别地,在做类型检测的时候有两个比较特殊的对象需要我们关注下,一个是Function.prototype即函数的原型对象,一个是Array.prototype即数组的原型对象。

    Function.prototype特别之处在于,它可以用typeof去检测。

    console.log(typeof function(){});// "function"
    

    Array.prototype特别之处在于,它还可以用Array.isArray()去判断,针对数组这个特殊的对象,我们下面的加餐环节还会进行具体的讲解。

    console.log(Array.isArray([]));// true
    

    白话总结版

    对于引用类型,我们最常用instanceof来进行检测,它是基于原型链的一种检测方法,同时我们也能用Object.prototype.toString()函数去检测,一些比较特殊的对象比如函数,它可以用typeof去检测,返回的是function。数组的话,我更推荐用Array.isArray()去判断。(具体原因可看下面加餐2)

    加餐1:instanceof的实现

    如何去实现instanceof是面试的热门考题之一,它的实现也非常简单,让我们回忆一下MDN中对instanceof的描述就找到思路。

    思路很明确,沿着原型链上找就OK,下面我们用迭代的思想去实现(当然递归也可)。

    function myInstanceof(object,constructor) {
        const prototype = constructor.prototype;
        let proto = object.__proto__;
        while(true) {
            if(proto === null) return false;
            if(proto === prototype) return true;
            proto = proto.__proto__;
        }
    }
    

    加餐2:检测数组的多种方法与利弊

    检测数组一共有三种方法,下面我将这三种方法都列出来。

    console.log([] instanceof Array); //true
    console.log(Object.prototype.toString.call([])); // "[object Array]"
    console.log(Array.isArray([])); //true
    

    先说一个结论,最佳实践Array.isArray(),但是你知道前两种方法的坑吗?

    instanceof 检测Array的坑

    这点其实在红宝书上有提到过,当页面中嵌套了iframe的时候,假设我们把父级窗口中创建的一个数组array,传到iframe中后,在iframe中用array instanceof Array来判断时,返回的竟然是false!不必惊讶,我们通过比较父级窗口和iframe的数组原型对象就可以发现,其实它们虽然”内容“相同,但是它们其实存放在不同内存空间中,所以它们不是严格相等的。所以此Array非彼Array

    另外一个坑,提到的比较少,也是我偶然发现的,严格意义上来说也不算坑。其实instanceof的行为是可以被重写的。

    让我们来看看MDN上提供的例子

    class Array1 {
      static [Symbol.hasInstance](instance) {
        return Array.isArray(instance);
      }
    }
    
    console.log([] instanceof Array1);
    // expected output: true
    

    Symbol.hasInstance作为一个静态方法,我们可以重写他的行为,使得我们的Array1类在面对instanceof检测时也能表现得和数组一样,虽然他的原型链上没有相应的数组原型对象。

    下面我们尝试一下,能不能以此来改写数组的Symbol.hasInstance规则,以此来欺骗instanceof

    console.log(Array[Symbol.hasInstance]([])); //true 
    Array[Symbol.hasInstance] = function(){return false} //强行修改会怎么样
    console.log(Array[Symbol.hasInstance]([])); //true 修改并没有成功
    

    虽然我们没法直接修改Array的行为,但是我们可以自己伪造一个对象去欺骗instanceof,所以我勉强把它算作一个坑,大家可以了解一下。

    Object.prototype.toString的坑

    这个坑解释起来非常简单,仅仅是因为它有被重写的风险。

    console.log(Object.prototype.toString.call([])); // "[object Array]"
    Object.prototype.toString = function() {return "重写"};
    console.log(Object.prototype.toString.call([])); // "重写"
    

    加餐3:当Object.prototype.toString碰上基本数据类型

    大家先看看下面的例子

    console.log(Object.prototype.toString.call(null)); //"[object Null]"
    console.log(Object.prototype.toString.call(undefined)); //"[object Undefined]"
    console.log(Object.prototype.toString.call(1)); //"[object Number]"
    console.log(Object.prototype.toString.call("str")); //"[object String]"
    console.log(Object.prototype.toString.call(true)); //"[object Boolean]"
    console.log(Object.prototype.toString.call(Symbol()));//"[object Symbol]"
    

    我们发现,似乎它比typeof更好用?连typeof检测不出的null它都能搞定?别着急下结论,让我们再举个例子。

    console.log(Object.prototype.toString.call(new Number)); //"[object Number]"
    console.log(Object.prototype.toString.call(new String)); //"[object String]"
    console.log(Object.prototype.toString.call(new Boolean)); //"[object Boolean]"
    

    对于numberstringboolean来说,我们单靠Object.prototype.toString是无法区分它们与NumberStringBoolean对象区别的。

    如果想更进一步了解Object.prototype.toString的实现机理,推荐大家阅读下面的文章,本文就不再多多赘述。

    ​ 【数据类型】JavaScript数据类型&聊聊Object.prototype.toString

    加餐4:一种解决类型的检测的参考方案

    下面,我将给出一种较为通用的解决类型检测的参考方案。该方案参考了type.js ,由颜海镜编写的用于判断数据类型的方法库。为了精简,我去掉了一些兼容性相关的代码,大家可以着重关注一下本文讲的一些知识点

    function type(x) {
        // 1. 在使用typeof检测时,先把特殊情况null检测了
        if(x === null) {
            return 'null';
        }
        const t = typeof x;
        if(t !== 'object'){
            return t;
        }
        
        // 2. 使用Object.prototype.toString检测,截取部分字符
        const cls = Object.prototype.toString.call(x).slice(8, -1);
        const clsLow = cls.toLowerCase();
        if(clsLow !== 'object'){
            // 区分Number Boolean String对象和number boolean string基本数据类型
            // 即前者大写,后者小写
            if (clsLow === 'number' || clsLow === 'boolean' || clsLow === 'string') {
                return cls;
            }
            return clsLow;
        }
        
        // 3. 如果该对象的构造函数为Object,则直接返回‘object'
        if(x.constructor === Object) {
            return clsLow;
        }
        
        // 4. 处理Object.create(null)创建的没有原型对象的情况
        if (x.__proto__ === null) {
            return 'object'
        }
        
        // 5. 处理其他非内置对象
        const cname = x.constructor.name;
        if (typeof cname === 'string') {
           return cname;
        }
        // 6. 未知的对象类型
        return 'unknown';	
    }
    

    测试一下

    console.log(type(1)); //number
    console.log(type(new Number)); //Number
    console.log(type({})); //Object
    console.log(Object.create(null)); //object
    function Person(){}
    console.log(type(new Person)); //Person
    

    写在最后

    至此,鼠子认为已经比较详尽的列出了与基本数据类型和类型检测相关的知识点,若有疏漏请大家提出来。本文将长期更新和补充类似问题,欢迎大家点赞收藏。


    起源地下载网 » 面试快回快答系列(二):基本数据类型和类型检测

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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