米娜桑,哦哈哟~

本章讲解关于 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. CommandPattern(命令模式)-----Java/.Net

    命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式.请求以命令的形式包裹在对象中,并传给调用对象.调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该 ...

  2. Web基础了解版11-Ajax-JSON

    Ajax AJAX即“Asynchronous Javascript And XML”:是,不发生页面跳转.异步请求载入内容并改写局部页面内容的技术. 也可以简单的理解为通过JS向服务器发送请求.   ...

  3. Revealjs网页版PPT让你复制粘贴另类装逼,简洁优雅又低调,不懂编程也看过来

    Revealjs网页版PPT让你复制粘贴另类装逼,简洁优雅又低调,不懂编程也看过来 要了解一个新知识我们可以从三个方面入手:是什么,有什么用,怎么用.下面我们就从这三个方面进行讲解Reveal.js噢 ...

  4. HDU4352 XHXJ's LIS 题解 数位DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4352 题目大意: 求区间 \([L,R]\) 范围内最长上升子序列(Longest increasin ...

  5. 数据库中间件分片算法之enum

    前言 最近挺焦虑的,不知道未来该做什么,方向又是什么.只能用别慌,月亮也正在大海的某处迷茫.来安慰下自己.不过学习的初心咱们还是不要忘记.今天我们学习的是enum分片算法. 1.hash分区算法 2. ...

  6. CSS单行文字超出省略

    .ellipsis { white-space:nowrap overflow:hidden text-overflow:ellipsis }

  7. 【GeneXus】开发移动APP时,如何使用Canvas进行布局?

    当我们开发移动端APP的时候,经常遇到一种布局方式,那就是层级的布局,比如:一张照片我想在照片的上面显示它的名称,但不影响我照片展示的布局大小,也就是这个名称是浮在照片上的,如图: 如果要实现这样的布 ...

  8. Flutter使用SingleTickerProviderStateMixin报错

    最近在学习开发Flutter应用项目,在创建tabbar和tabview后,进行网络请求后显示顶部tab标签,设置TabController,并使class类实现SingleTickerProvide ...

  9. LoopBox 用于包装循环的盒子

    /******************************************************* * * 作者:朱皖苏 * 创建日期:20180608 * 说明:此文件只包含一个类,具 ...

  10. python中类的输出或类的实例输出为何是<__main__类名 object at xxxx>这种形式?

    原因: __str__()这个特殊方法将对象转换为字符串的结果 效果图: 代码: # 定义一个Person类 class Person(object): """人类&qu ...