米娜桑,哦哈哟~

本章讲解关于 JavaScript 奇妙的 Bug,与其说是Bug,不如说是语言本身隐藏的奥秘。接下来就看看可能会影响到我们编程的那些Bug吧。

typeof null === "object"


官方自带的Bug,typeof 操作符会返回对应操作数类型的字符串 表示,唯独 null,返回object文档解释说:

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"

指针是一个变量,其储存的值是一个地址,一般空指针储存的地址为 0x00;对象的引用也是一个变量,储存的值也是一个地址,比如:0x000001、0x000002。

而JavaScript会根据实际数据储存值得到标签类型,那么 typeof null === 'object'

NaN !== NaN


如果 var a = NaN , 那么 a !== a

这是 NaN 本身的一个特点,也只有NaN,比较之中不等于它自己。

所以判定是否为NaN有三种方式

isNaN(a) //为NaN或者强制转换为数字后是NaN时,则返回true
Number.isNaN(a) //仅当为NaN时为true
a !== a //成立的唯一情况是 a 的值为 NaN

[ ]![ ] , [ ]![ ]


对于前半段 []!==[]

一个变量在内存中都需要一个空间来存储,而内存会根据其类型分配到栈内存(stack)或堆内存(heap)。

最新的 ECMAScript标准 定义了 8 种数据类型:

  • Null:空指针对象
  • Undefined:未定义
  • Boolean:布尔值
  • Number:整数、浮点数、特殊值(InfinityNaN
  • String:字符串
  • Symbol:一种实例是唯一且不可改变的数据类型
  • BigInt:一种用于表示任意精度格式的整数的数据类型
  • Object:对象

    除了前面7种原始数据类型在声明变量时是储存在栈内存,而对象类型则是在栈内存中存储一个引用地址,该地址指向堆内存的值。
let num = 1,
arr1 = [],
arr2 = [],
arr3 = arr2 console.log(arr1 === arr2) //false
console.log(arr2 === arr3) //true



内存分配如图,虽然它们在堆内存中分配在不同位置的储存的值是等价的。但进行 arr1 === arr2 判断时会对比它们的引用地址,故不为真。

arr2 === arr3 其实本质就是 arr2 === arr2,因此,该改变arr3,其实就是改变arr2,这就引申出浅拷贝深拷贝的话题

对于后半段 []==![]

以相等操作符 == 比较两个值是否相等,在比较前将两个被比较的值转换为相同类型,即隐式转换。转换规则如下,详情请查看 相等性判断 文档

根据上述规则,转换过程如下:

[] == ![]
[] == false //优先执行逻辑非操作,返回false
''== 0 //[]使用toString转换;false转换为数字
0 === 0 //''转化为数字,进行全等判定,比较完毕 。

所以,大多数情况下,不建议使用 == 判定,使用 === 结果更容易预测。由于没有进行隐式转换,=== 评估更快(虽然影响微小)

0.1 + 0.2 !== 0.3


这不仅是JavaScript的Bug,也是计算机语言的“通病”。

从最底层的电路来讲,一般电路通过给电子器件施加电压,根据其电压高低状态(高电平、低电平),也就是所谓的电子器件开关,来实现二值数字逻辑,即0和1。而正是这种方便快捷的状态,决定了计算机采用二进制进行运算。

而小数的二进制大多为无限循环的,如果用每个电子器件开关的状态对应记录这些无限循环的二进制数字,这显然是不可能的。

最终根据IEEE 754标准,0舍1入,使用64位固定长度来表示(ECMAScript®语言规范有所提及),所以有如下结果:

(0.1).toString(2)
//"0.0001100110011001100110011001100110011001100110011001101" (0.2).toString(2)
//"0.001100110011001100110011001100110011001100110011001101" (0.3).toString(2)
//"0.010011001100110011001100110011001100110011001100110011"

而0.1、0.2进行二进制加运算得到的结果应为

//0.0100110011001100110011001100110011001100110011001100111
//对应十进制为 0.30000000000000004
//因此 0.1 + 0.2 !== 0.3

a === 1 && a === 2 , a == 1 && a == 2


看似荒谬的比较,为什么会存在 a 能满足上述条件呢?这得分开讨论。

对于前半段 a === 1 && a===2

如果 a 是原始数据类型,那上述的全等判定就不能成立,所以 a 就需要通过函数进行变形,那么 a 是一个返回1或着2的函数对象。

这个时候我们可以利用 getter 将对象属性绑定到查询该属性时将被调用的函数。而其对象正是 window 对象。

不难得出

let i = 1
Object.defineProperty(window, 'a', {
get() {
return i++
}
})
console.log(a === 1 && a === 2) //true
//当访问 window.a 时候则会实行 a 函数

对于后半段 a === 1 && a===2

在提及 []==![] 讨论过,当比较的两者类型不一致的时候,将进行隐式转换。对应的值会执行其内置的 ToPrimitive() 函数操作:

1、进行 valueOf(),如果得到的为原始数据类型(如Date类型会得到对应的数字),则返回对应原始值,否则进行第2步。

2、进行 toString() ,返回对应原始值,如果失败,抛出 TypeError

let a = {
i: 0,
valueOf() {
return this.i += 1
}
}
console.log(a == 1 && a == 2) //true

那些 JavaScript 自带的奇妙 Bug的更多相关文章

  1. Lazarus下面的javascript绑定另外一个版本bug修正

    Lazarus下面的javascript绑定另外一个版本bug修正 从svn 检出的代码有几个问题 1.fpcjs.pas 单元开始有 {$IFDEF FPC} {$MODE delphi} {$EN ...

  2. cnpm 莫名奇妙bug 莫名奇妙的痛

    cnpm 莫名奇妙bug 莫名奇妙的痛 最近想搭建react@v16 和 react-router@v4,搭建过程打算用vue脚手架webpack模板那套配置方法(webpack3). 由于我之前安装 ...

  3. Javascript 运动中Offset的bug——逐行分析代码,让你轻松了解运动的原理

    我们先来看看这个bug 是怎么产生的. <style type="text/css"> #div1 { width: 200px; height: 200px; bac ...

  4. 关于 javascript event flow 的一个bug

    [1]描述了firefox,safari 有一个bug和DOM 3 规范不一致:在event.currentTarget等于event.target的时候(即event flow处于target ph ...

  5. 原来javascript 自带 encodeURI 和 decodeURI文 方法了

    今天百度一下才知道js 自带 encodeURI 和 decodeURI 方法了,之前还找了其他代码来处理(笑哭了.jpg <script type="text/javascript& ...

  6. JavaScript运算符优先级引起的bug

    [下面是昨天发给同事的邮件,为防止泄露商业机密,隐去了项目名和变量名] ==================================================== 昨天发现Nx代码中的一 ...

  7. 解决JavaScript浮点数(小数) 运算出现Bug的方法

    解决JS浮点数(小数) 运算出现Bug的方法例如37.2 * 5.5 = 206.08 就直接用JS算了一个结果为: 204.60000000000002 怎么会这样, 两个只有一位小数的数字相乘, ...

  8. 是什么让javascript变得如此奇妙

    What Makes Javascript Weird...and AWESOME -> First Class Functions -> Event-Driven Evironment ...

  9. Javascript仿贪吃蛇出现Bug的反思

    bug现象:    图一

随机推荐

  1. [**P2766** 最长不下降子序列问题](https://www.luogu.org/problemnew/show/P2766)

    P2766 最长不下降子序列问题 考虑我们是如何\(dp\)这个\(LIS\)的. 我们是倒着推,设置\(dp(i)\)代表以\(i\)为起点的\(LIS\)是多少.转移太显然了 \[ dp(i)=m ...

  2. 通用高效的数据修复方法:Row level repair

    导读:随着大数据的进一步发展,NoSQL 数据库系统迅速发展并得到了广泛的应用.其中,Apache Cassandra 是最广泛使用的数据库之一.对于 Cassandra 的优化是大家研究的热点,而 ...

  3. 「UVA12004」 Bubble Sort 解题报告

    UVA12004 Bubble Sort Check the following code which counts the number of swaps of bubble sort. int f ...

  4. vue 2.0以上怎么在手机中运行自己的项目

    第一步 打开vue项目 第二步 打开项目config/index.js文件,然后找到 module.exports 配置里面的 dev 配置,修改字段host:0.0.0.0 第三步 打开cmd输入i ...

  5. spring boot的application配置文件

      上次我们已经对这个文件见过面了,并且对他进行了一些简单的配置.它有两种配置方式,一个是application.properties,一个是application.yml文件,需要记住,当两个文件都 ...

  6. 【Java基础总结】IO流

    字节流 1. InputStream 字节输入流 代码演示 InputStream in = System.in; System.out.println("int read(byte b) ...

  7. 理解 SQL 开窗函数

    一次面试被问到开窗函数,懵逼了,赶紧补补总结一下.... 开窗函数也是函数,所以 比如在原来的查询上添加一个总数列 create table ztest( id int identity, c1 in ...

  8. js 极简获取表单 元素 !

    let s =[]; $.each($('#formSearch input'),(m,n)=>{s.push(n)}); //示例获取表单所有 input 下滑线分割的 name 集合.set ...

  9. Kubernetes concepts 系列

    kubernetes concepts overview Pod overview Replication Controller Pod Liftcycle Termination Of Pod Re ...

  10. sqlalchemy 多线程 创建session

    1.基于threding.local,推荐使用 from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine ...