JavaScript知识点2

143 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

6. intanceof 操作符的实现原理及实现

instanceof 运算符⽤于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

class Person { }
    class Teacher extends Person { }
    const p = new Teacher()
    console.log(p instanceof Person, p instanceof Teacher, p instanceof Object)
    // 手写 instanceof实现
    function myInstanceof(obj, Prototype) {
      const prototype = Prototype.prototype
      // 拿到对象的原型
      let proto = Object.getPrototypeOf(obj)
      while (proto) {
        if (proto === prototype) return true
        proto = Object.getPrototypeOf(proto)
      }
      return false
    }
    console.log(myInstanceof(p, Person), myInstanceof(p, Teacher), myInstanceof(p, Object))

为什么0.1+0.2 ! == 0.3,如何让其相等

在开发过程中遇到类似这样的问题:

const n1 = 0.1 , n2 = 0.2;
console.log(n1+n2) // 0.3000000000.....

这⾥得到的不是想要的结果,要想等于0.3,就要把它进⾏转化

(n1 + n2).toFixed(2) // 注意,toFixed为四舍五⼊

toFixed(num) 方法可把 Number 四舍五⼊为指定⼩数位数的数字。那为什么会出现这样的结果呢? 计算机是通过二进制的⽅式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进 制的和。0.1的二进制是0.0001100110011001100...(1100循环),0.2的二进制 是:0.00110011001100...(1100循环),这两个数的二进制都是⽆限循环的数。那JavaScript是 如何处理⽆限循环的二进制⼩数呢?

⼀般我们认为数字包括整数和⼩数,但是在 JavaScript 中只有⼀种数字类型:Number,它的实现遵 循IEEE 754标准,使⽤64位固定⻓度来表示,也就是标准的double双精度浮点数。在⼆进制科学表示 法中,双精度浮点数的⼩数部分最多只能保留52位,再加上前⾯的1,其实就是保留53位有效数字,剩余 的需要舍去,遵从“0舍1⼊”的原则。

根据这个原则,0.1和0.2的⼆进制数相加,再转化为⼗进制数就是:0.30000000000000004。 说了这么多,是时候该最开始的问题了,如何实现0.1+0.2=0.3呢?

对于这个问题,⼀个直接的解决⽅法就是设置⼀个误差范围,通常称为“机器精度”。对JavaScript来 说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,⽽它的值就是2-52,只要判断 0.1+0.2-0.3是否⼩于Number.EPSILON,如果⼩于,就可以判断为0.1+0.2 ===0.3

    function numberepsilon(arg1, arg2) {
      return Math.abs(arg1 - arg2) < Number.EPSILON;
    }
    console.log(numberepsilon(0.1 + 0.2, 0.3)) // true

8. 如何获取安全的 undefined 值?

老实说,在JavaScript中,没有把undefined作为关键字,是一个败笔。因为不是关键字,导致我们可以把undefined作为标识符,所以可能导致我们给某个变量赋值为undefinded的时候,可能取到的值是修改后的 undefined变量的值。

    function test(){
      const undefined = "123"
      const a = undefined
      console.log(a) // 123
    }
    test()

因此我们会使用一个void xx 表达式, 该void表达式的返回值是真正的undefined,而一般来说void 0占用的空间最小,且也是最美观的,我们会使用这个表达式来拿到真正的undefined

9. typeof NaN 的结果是什么?

NaN 指“不是⼀个数字”(not a number),NaN 是⼀个“警戒值”(sentinel value,有特殊⽤途 的常规值),⽤于指出数字类型中的错误情况,即“执⾏数学运算没有成功,这是失败后返回的结果”。

typeof NaN; // number

NaN 是⼀个特殊值,它和⾃身不相等,是唯⼀⼀个非自反(自反,reflexive,即 x === x 不成立) 的值。⽽ NaN !== NaN 为 true。

// 使用 isNaN 可以判断一个变量是否是NaN
console.log(Number.isNaN(NaN))
// 使用 is方法可以判断两个NaN为相等
console.log(Object.is(NaN, NaN))

10. isNaN 和 Number.isNaN 函数的区别?

  • 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传⼊也会返回 true ,会影响 NaN 的判断。
  • 函数 Number.isNaN 会首先判断传⼊参数是否为数字,如果是数字再继续判断是否为 NaN ,不会 进行数据类型的转换,这种⽅法对于 NaN 的判断更为准确。

11. == 操作符的强制类型转换规则?

对于 == 来说,如果对比双方的类型不⼀样,就会进行类型转换。假如对⽐ x 和 y 是否相同,就会 进行如下判断流程:

  1. 首先会判断两者类型是否相同,相同的话就⽐较两者的大小
  2. 类型不相同的话,就会进行类型转换;
  3. 会先判断是否在对⽐ null 和 undefined,是的话就会返回 true
  4. 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
  5. 判断其中⼀方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
  6. 判断其中⼀方是否为 object 且另⼀⽅为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进⾏判断

12. 其他值到字符串的转换规则?

  • Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined"
  • Boolean 类型,true 转换为 "true",false 转换为 "false"。
  • Number 类型的值直接转换,不过那些极⼩和极⼤的数字会使⽤指数形式。
  • Symbol 类型的值直接转换,但是只允许显式强制类型转换,使⽤隐式强制类型转换会产⽣错误。
  • 对普通对象来说,除非自行定义 toString() ⽅法,否则会调⽤ toString() (Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有⾃⼰的 toString() ⽅法,字符串化时就会调⽤该⽅法并使⽤其返回值。

image.png

image.png

其他值到数字值的转换规则?

  • Undefined 类型的值转换为 NaN。
  • Null 类型的值转换为 0。
  • Boolean 类型的值,true 转换为 1,false 转换为 0。
  • String 类型的值转换如同使⽤ Number() 函数进行转换,如果包含非数字值则转换为 NaN,空 字符串为 0。
  • Symbol 类型的值不能转换为数字,会报错。
  • 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循 以上规则将其强制转换为数字。

为了将值转换为相应的基本类型值,抽象操作ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有valueOf()⽅法。如果有并且返回基本类型值,就使⽤该值进⾏强制 类型转换。如果没有就使⽤ toString() 的返回值(如果存在)来进⾏强制类型转换。 如果 valueOf() 和 toString() 均不返回基本类型值,会产⽣ TypeError 错误。

其他值到布尔类型的值的转换规则?

以下这些是假值: • undefined • null • false • +0、-0 和 NaN • "" 假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。

Object.is() 与⽐较操作符 “===”、“==” 的区别?

  • 使⽤双等号(==)进⾏相等判断时,如果两边的类型不⼀致,则会进行强制类型转化后再进行⽐较。
  • 使⽤三等号(===)进⾏相等判断时,如果两边的类型不⼀致时,不会做强制类型准换,直接返回 false。
  • 使⽤ Object.is 来进⾏相等判断时,⼀般情况下和三等号的判断相同,它处理了⼀些特殊的情 况,⽐如-0 和+0 不再相等,两个 NaN 是相等的。

JavaScript 中如何进⾏隐式类型转换?

首先要介绍ToPrimitive⽅法,这是 JavaScript 中每个值隐含的⾃带的⽅法,⽤来将值 (⽆论是 基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象:

/**
    obj 期望转换的对象
    type 期望转换为的类型
*/
ToPrimitive(obj, type)

type的值为number或者string或者是默认值default

  1. type为number时,规则如下:
    • 调⽤obj的valueOf⽅法,如果为原始值,则返回,否则下⼀步;
    • 调⽤obj的toString⽅法,如果为原始值,则返回,否则下⼀步;
    • 抛出TypeError 异常。
  2. 当type为string时规则如下:
    • 调⽤obj的toString⽅法,如果为原始值,则返回,否则下⼀步
    • 调⽤obj的valueOf⽅法,后续同上;
    • 抛出TypeError 异常。

可以看出两者的主要区别在于调⽤toString和valueOf的先后顺序。默认情况下:

  • 如果对象为 Date 对象,则type默认为string;
  • 其他情况下,type默认为number。

总结上⾯的规则,对于 Date 以外的对象,转换为基本类型的大概规则可以概括为⼀个函数:

const objToNumber = value=>Number(value.valueOf().toString())

⽽ JavaScript 中的隐式类型转换主要发⽣在+、-、*、/以及==、>、<这些运算符之间。⽽这些运 算符只能操作基本类型值,所以在进⾏这些运算前的第⼀步就是将两边的值⽤ToPrimitive转换成基本 类型,再进行操作。 以下是基本类型的值在不同操作符的情况下隐式转换的规则 (对于对象,其会被ToPrimitive转换成 基本类型,所以最终还是要应⽤基本类型转换规则):

  1. +操作符

    • +操作符的两边有⾄少⼀个string类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边 的变量都会被转换为数字。
  2. -、*、\操作符

  3. 对于==操作符,操作符两边的值都尽量转成number:

  4. 对于<和>⽐较符

    • 如果两边都是字符串,则⽐较字⺟表顺序:
    • 其他情况下,转换为数字再⽐较: 以上说的是基本类型的隐式转换,而对象会被ToPrimitive转换为基本类型再进⾏转换:

+ 操作符什么时候⽤于字符串的拼接?

根据 ES5 规范,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进⾏拼接操 作。如果其中⼀个操作数是对象(包括数组),则⾸先对其调⽤ ToPrimitive 抽象操作,该抽象操作 再调⽤ [[DefaultValue]],以数字作为上下⽂。如果不能转换为字符串,则会将其转换为数字类型来 进行计算。

简单来说就是,如果 + 的其中⼀个操作数是字符串(或者通过以上步骤最终得到字符串),则执⾏字符串拼接,否则执行数字加法。 那么对于除了加法的运算符来说,只要其中⼀⽅是数字,那么另⼀⽅就会被转为数字。