• 前端面试的那些事儿(1)~JavaScript 原始数据类型


    前言

    自我总结面试常问的一些细节,方便不断回顾与补充。第一次发表文章,如有问题或不足之处望及时指出。

    JavaScript 原始数据类型

    1.1 基础数据类型

    7大基础数据类型

    • boolean
    • null
    • undefined
    • number
    • string
    • symbol
    • BigInt (Stage阶段)

    1.2 boolean

    只有true、false两个值

    什么是falsy?

    转换成false的值称为假值(falsy value),这7个值包括undefined、null、+0、-0、NaN、false、""(空字符串)

    1.3 null 和 undefined

    • null值表示一个空对象指针
    • undefined是一个表示”无”的原始值

    比较:

    • null == undefined // true
    • null === undefined // false

    转为数值:

    • null -> 0
    • undefined -> NaN

    null 是对象吗?

    不是

    typeof null 为什么是object?

    原因是在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

    为什么有些地方要使用 void 0 代替 undefined

    undefined在JavaScript中并不属于保留字/关键字,因此在IE5.5~8中我们可以将其当作变量那样对其赋值,于是采用void方式获取undefined则成了通用方法。

    1.4 number

    javascript采用IEEE754格式来表示数字,不区分整数和浮点数,javascript中的所有数字都用浮点数值表示

    不让除以 0 出错,而引入了无穷大的概念

    • Infinity,无穷大; => 1/+0 === Infinity
    • -Infinity,负无穷大。=> 1/-0 === -Infinity

    number 中经典的 0.1 + 0.2 !=0.3

    原因:

    1. 采用 IEEE754格式的语言都有该问题
    2. 计算机将 0.1 0.2 表示为二进制,然后相加之后再转换成10进制
    3. 问题就是转换成2进制时,0.1 在二进制中是无限循环的一些数字(很多十进制小数用二进制表示都是无限循环的)
    4. JS 采用的浮点数标准却会裁剪掉我们的数字
    5. 那么这些循环的数字被裁剪了,就会出现精度丢失的问题,也就造成了 0.1 不再是 0.1 了,而是变成了 0.100000000000000002
    6. 0.2 在二进制也是无限循环的,被裁剪后也失去了精度变成了 0.200000000000000002
    7. 因此结果是 0.1 + 0.2 === 0.30000000000000004

    解决方案:

    检查左右两边差的绝对值是否小于最小精度

    Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON
    

    NaN

    NaN(not a number)表示非数字,NaN与任何值都不相等,包括NaN本身,且任何涉及NaN的操作都会返回NaN

    判断NaN更可靠的方法是,利用NaN是javascript之中唯一不等于自身的值这个特点,进行判断

    function myIsNaN(value) {
      return value !== value;
    }
    

    1.5 string

    字符串String类型是由引号括起来的一组由16位Unicode字符组成的字符序列

    Note:现行的字符集国际标准,字符是以 Unicode 的方式表示的,每一个 Unicode 的码点表示一个字符,理论上,Unicode 的范围是无限的。UTF 是 Unicode 的编码方式,规定了码点在计算机中的表示方法,常见的有 UTF16 和 UTF8。 Unicode 的码点通常用 U+??? 来表示,其中 ??? 是十六进制的码点值。 0-65536(U+0000 - U+FFFF)的码点被称为基本字符区域(BMP)

    JavaScript 字符串把每个 UTF16 单元当作一个字符来处理,所以处理非 BMP(超出 U+0000 - U+FFFF 范围)的字符时,你应该格外小心。

    JavaScript 这个设计继承自 Java,最新标准中是这样解释的,这样设计是为了“性能和尽可能实现起来简单”。因为现实中很少用到 BMP 之外的字符。

    JavaScript 中的字符串是永远无法变更的,一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。

    1.6 symbol

    Symbol的作用非常的专一,换句话说其设计出来就只有一个目的——作为对象属性的唯一标识符,防止对象属性冲突发生。

    除了自己创建的symbol,JavaScript还内建了一些在ECMAScript 5 之前没有暴露给开发者的symbol,它们代表了内部语言行为。

    Symbol 函数比较特殊,直接用 new 调用它会抛出错误,但它仍然是 Symbol 对象的构造器。

    Symbol.toPrimitive

    JavaScript 对象转换到基本类型值时,会使用 ToPrimitive 算法,这是一个内部算法,是编程语言在内部执行时遵循的一套规则。

    An object without Symbol.toPrimitive property.

    var obj1 = {};
    console.log(+obj1);     // NaN
    console.log(`${obj1}`); // "[object Object]"
    console.log(obj1 + ''); // "[object Object]"
    

    当我们创建一个普通对象时({} 或 new Object() 的方式等),对象上是不具备 [Symbol.toPrimitive] (方法)属性的。所以,对于普通对象的到基本类型值的运算,一般按照具体场景:

    1. hint 值为 "string" 时,先调用 toString,toString 如果返回一个基本类型值了,则返回、终止运算;否则接着调用 valueOf 方法。
    2. 否则先调用 valueOf,valueOf 如果返回一个基本类型值了,则返回、终止运算;
    3. 否则接着调用 toString 方法。

    An object with Symbol.toPrimitive property.

    var obj2 = {
      [Symbol.toPrimitive](hint) {
        if (hint == 'number') {
          return 10;
        }
        if (hint == 'string') {
          return 'hello';
        }
        return true;
      }
    };
    
    console.log(+obj2);     // 10        -- hint is "number"
    console.log(`${obj2}`); // "hello"   -- hint is "string"
    console.log(obj2 + ''); // "true"    -- hint is "default"
    

    如果存在Symbol.toPrimitive则优先调用否则安装上面的规则处理。

    ToPrimitive 算法在执行时,会被传递一个参数 hint,表示这是一个什么类型的运算(也可以叫运算的期望值),根据这个 hint 参数,ToPrimitive 算法来决定内部的执行逻辑。

    hint 参数的取值只能是下列 3 者之一:

    • string
    • number
    • default

    1.7 BigInt

    目前还在stage阶段

    • BigInt是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库。
    • 不能使用Number和BigInt操作数的混合执行算术运算,需要通过显式转换其中的一种类型。
    • 出于兼容性原因,不允许在BigInt上使用一元加号(+)运算符。

    1.8 类型检查

    有类型就势必会有类型检查

    • typeof 对于原始数据,都可以正确检测,但是null会显示object
    • typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型
    • instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链
    function Car(make, model, year) {
      this.make = make;
      this.model = model;
      this.year = year;
    }
    const auto = new Car('Honda', 'Accord', 1998);
    
    console.log(auto instanceof Car);
    // expected output: true
    
    console.log(auto instanceof Object);
    // expected output: true
    
    • so,使用instanceof判断类型就是胡扯,受限极大。
    • Object.prototype.toString.call(any anyType) // "[object anyType]" 这才是最准确的判断类型的方式

    Object.prototype.toString

    在早期版本的 JavaScript 中,“类”的定义是一个私有属性 [[class]],语言标准为内置类型诸如 Number、String、Date 等指定了[[class]]属性,以表示它们的类。语言使用者唯一可以访问[[class]]属性的方式是 Object.prototype.toString。

    var o = new Object;
    var n = new Number;
    var s = new String;
    var b = new Boolean;
    var d = new Date;
    var arg = function(){ return arguments }();
    var r = new RegExp;
    var f = new Function;
    var arr = new Array;
    var e = new Error;
    console.log([o, n, s, b, d, arg, r, f, arr, e].map(v => Object.prototype.toString.call(v))); 
    // ["[object Object]", "[object Number]", "[object String]", "[object Boolean]", "[object Date]", "[object Arguments]", "[object RegExp]", "[object Function]", "[object Array]", "[object Error]"]
    

    在 ES5 开始,[[class]] 私有属性被 Symbol.toStringTag 代替,Object.prototype.toString 的意义从命名上不再跟 class 相关。我们甚至可以自定义 Object.prototype.toString 的行为,以下代码展示了使用 Symbol.toStringTag 来自定义 Object.prototype.toString 的行为:

    var o = { [Symbol.toStringTag]: "MyObject" }
    console.log(o + ""); // [object MyObject]
    
    var d = new Date;
    d[Symbol.toStringTag] = "custom"
    Object.prototype.toString.call(d) // "[object custom]"
    

    1.9 类型转换

    JavaScript 中类型转换有三种情况

    1. 转换为布尔值
    2. 转换为数字
    3. 转换为字符串

    转换成布尔值

    方法:

    • !! 两次取反
    • if() 参数会自动转
    • Boolean() 构造函数转

    规则:

    • number -> 除 0 -0 NaN 为false ,其它都为true
    • string -> 除 空字符串 为false , 其它都为true
    • undefined -> false
    • null -> false
    • object、function、array -> true

    转换为数字

    方法:

    • Number()
    • ParseInt()
    • ParseFloat()
    • 一元运算符 +'1' -'1'

    规则:

    • string -> '1' => 1 'a' => NaN
    • array -> [] => 0 [1] => 1 [any] => NaN
    • null -> 0
    • 除数组的引用类型 -> NaN
    • Symbal -> 报错

    面试题:

    [] == ![]
    

    分析:

    1. 0 == false
    2. false == false

    转换为字符串

    方法:

    • String()
    • toString()
    • ''+

    规则:

    • number -> 转换成对应的字符串 1 =>'1'
    • Boolean -> 'true'
    • Object -> '[object,Object]'

    [题1]:+ 'b'= NaN --> 'a'+NaN = 'aNaN'

    'a' + + 'b' // -> "aNaN"
    

    1.10 装箱操作与拆箱操作

    1. 原始值本身是没有方法可以调用的,除非它的包装类型
    2. 除了 null 和 undefined,所有的原始值都有等价的、由对象包装原始值的形式表达,通过各自的构造函数可以手动进行包装,系统也会进行隐式转换

    装箱操作(原始类型转对象)

    console.log("abc".charAt(0)); //a
    

    . 运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。

    每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。

    我们定义一个函数,函数里面只有 return this,然后我们调用函数的 call 方法到一个 Symbol 类型的值上,这样就会产生一个 symbolObject。

        var symbolObject = (function(){ return this; }).call(Symbol("a"));
    
        console.log(typeof symbolObject); //object
        console.log(symbolObject instanceof Symbol); //true
        console.log(symbolObject.constructor == Symbol); //true
    

    [注意] 装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。

    使用内置的 Object 函数,我们可以在 JavaScript 代码中显式调用装箱能力。

        var symbolObject = Object(Symbol("a"));
    
        console.log(typeof symbolObject); //object
        console.log(symbolObject instanceof Symbol); //true
        console.log(symbolObject.constructor == Symbol); //true
    

    拆箱操作(对象转原始类型)

    在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即拆箱转换)。

    • valueOf()方法返回指定对象的原始值,如果对象没有原始值,则valueOf将返回对象本身。你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。
    • toString()方法返回一个表示该对象的字符串。
    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }
    
    o * 2
    // valueOf
    // toString
    // TypeError
    

    拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。

    到 String 的拆箱转换会优先调用 toString(这个不同浏览器还不一样,有的还是调用valueOf不纠结)。我们把刚才的运算从 o*2 换成 String(o),那么你会看到调用顺序就变了。

    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }
    
    String(o)
    // toString
    // valueOf
    // TypeError
    

    在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。

    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }
    
    o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
    
    
    console.log(o + "")
    // toPrimitive
    // hello
    
  • 相关阅读:
    Java的并发编程:创建线程的多种方式
    va_end (Variadic functions) – C 中文开发手册
    PHP headers_sent() 函数
    Java面试题:你用过的网站前端优化的技术有哪些?
    space-before-keywords (Rules) – Eslint 中文开发手册
    JavaScript 数据类型
    C 库函数 – free()
    屏幕 | screen (screen) – Electron 中文开发手册
    lrint (Numerics) – C 中文开发手册
    《《数据化风控》读书笔记分享》
  • 原文地址:https://www.cnblogs.com/shiyou00/p/12780199.html
Copyright © 2020-2023  润新知