米娜桑,哦哈哟~

本章讲解关于 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:整数、浮点数、特殊值(Infinity、NaN)
  • 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. $CF809C\ Find\ a\ car$ 数位$dp$

    正解:数位$dp$ 解题报告: 传送门! 然后因为没有翻译所以先放个翻译$QAQ$ 有一个无穷大的矩阵,第$i$行第$j$列的数是$(i-1)\ xor\ (j-1)+1$,有$q$次询问,每次询问一 ...

  2. Ural1057. Amount of Degrees 题解 数位DP

    题目链接: (请自行百度进Ural然后查看题号为1057的那道题目囧~) 题目大意: Create a code to determine the amount of integers, lying ...

  3. Frangi形态学滤波详解

    利用Hessian矩阵的滤波函数Frangi,网上的文章只是把论文中的公式贴出来了. 我感觉分析下滤波函数是怎么起作用,还是挺有意思的一件事情. Frangi滤波方法的论文是: Frangi A F, ...

  4. 利用shell脚本实现每隔60秒磁盘内存数据监控脚本

    #!/bin/bash #Author:GaoHongYu #QQ: #Time:-- :: #Name:ncjk.sh #Version:V1. clear xtip=$(hostname -I) ...

  5. 字符串String类常见算法题

    1.将一个字符串进行反转.将字符串中指定部分进行反转. public class StringDemo { //方式一:转换为char[] public String reverse(String s ...

  6. Java类成员之构造器

    构造器含义: 是指使得JVM在构造对象的时候,帮助进行成员变量的初始化的方法. 构造器(构造方法)格式: 1.对于构造方法而言,方法的名称是固定的,和类名相同. 2.对于构造方法而言,它没有返回值,而 ...

  7. 秦皇岛 I 题

    有 n 个数字,你可以挑选其中任意个数字代表一个背包的体积,其余的数字表示的物品的体积,所挑选的背包必选完全装满,询问最终的方案数 思路分析 : 定义dp[i] 表示挑选 i 状态下的物品的方案数,f ...

  8. 《Java核心技术》 JVM指令集

    https://www.jianshu.com/p/bc91c6b46d7b

  9. Gym - 101982F 扫描线+线段树

    题目链接:https://codeforces.com/gym/101982/attachments 要你求覆盖奇数次的矩形面积并,每次更新时减去原先的值即可实现奇数次有效,下推时为保证线段长度不变左 ...

  10. window10配置python虚拟环境的路径

    当我们搭建python+django项目的时候,需要配置虚拟环境,从而使项目与项目之间的包相互隔离,互不影响. 1.配置虚拟环境前,首先需要安装两个包 pip install virtualenvwr ...