红宝书精读——第三章 语言基础(附思维导图)

331 阅读5分钟

前言

前篇我们刚刚了解了JavaScript在HTML中使用的一些细节,本章我们终于要开始详细的介绍javaScript的语言相关知识。本章内容较多,我会尝试尽量记录一些可拓展的,比较常考的知识点,并且尝试用对比的方法让它们更容易记忆。

思维导图

因为第三部分 数据类型的内容太多了,所以单独列为一张知识导图。

同时因为本章关于 Object 和函数的内容过于零碎,所以我打算整理到详细讲解它们的章节中。 Chapter 3 语言基础.png

原始数据类型.png

表格

表格一 var 和 let 区别

varlet
作用域函数作用域块作用域
全局声明作为 window 的属性不作为 window 的属性
声明提升提升到作用域顶端在声明前无法访问(暂时性死区)
冗余声明不报错报错
for 循环中渗透每个迭代声明一个新的迭代变量

表格二 Boolean() 转型函数

数据类型转为 true转为 false
Booleantruefalse
String非空字符串''(空字符串)
Number非零数值(包括无穷值)0, NaN
Object任意对象null
Undefinedundefined

表格三 Number(), parseInt(), parseFloat() 转型函数区别

Number()parseInt()parseFloat()
Booleantrue => 1; false => 0
Number直接返回返回整数直接返回
Null0
undefinedNaN
字符串① 有数值就返回,无就NaN
②空字符串返回0
①同但是②空字符串返回NaN①同但是②空字符串返回NaN
对象valueOf()值不为NaN就返回,为NaN就再调用toString()

主要注意三个函数处理空字符串的表现不一样。

四 逻辑与或非表现

逻辑非可以理解为 Boolean() 函数的表现再取反,这里就不做介绍。

如果直接把书关于逻辑与和或的特殊规则特别长,难以记忆。不过在理解了它们的性质之后就比较好记忆了。逻辑与和逻辑或都是短路操作符,即第一个操作数决定了结果的话,第二个操作数就不会被求值

image.png

比如让我们观察上面的规则:逻辑与中如果第一个操作数是对象,则返回第二个操作数。那是因为对象肯定会被判为true,所以就返回了第二个操作数。而如果两个操作数都是对象,则返回第二个操作数。这个规则和上面的规则其实类似,因为第一个对象用于判断为true了,所以自然只返回第二个对象。而逻辑或也类似。

所以我们可以归纳出上面的规律的三个特点:

  • 逻辑与和逻辑或返回的特殊情况有:对象、null、undefined、NaN

对象是真值,null、undefined、NaN是假值

  • 逻辑与:一真返二,一假返一
  • 逻辑或:一假返二,一真返一

本章重点

本章就如章名所写的,讲述的都是语言最基础的内容,其实可拓展的东西比较少,如果只是说考点的话,常考的有:

  • 原始类型有哪些? Null, Undefined, Boolean, String, Number, Symbol, BigInt(ES2020 新增)

我自己记成: 南南北北上上上,前六个字取首字符(NNBBSS),最后一个上取U(up => u)。当然其实都挺好记的。

  • 类型判断:

    • typeof:能判断所有值类型,函数。不可对 null、对象、数组进行精确判断,因为都返回 object
    • instanceof:能判断对象类型,不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型
    • Object.prototype.toString.call() :所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等。
  • 类型转换结果:这个就看表格就可以了

  • var, let, const区别:这个看表格一

    • 不过这里有个很有意思的点,表格一中写了:let在for循环中会为每个迭代声明一个新的迭代变量,这是怎么实现的呢?我们可以通过Babel编译后的代码发现其原理。这点需要详细介绍
  • 0.1 + 0.2 != 0.3 原因和解决办法: 这个也需要详细介绍

1. let 在 for 循环中的实现原理

let在for循环中会为每个迭代声明一个新的迭代变量,这是怎么实现的呢?我们可以查看babel编译后的代码看看它做了什么。

原始 es6 代码如下

var funcs = [];
for (let i = 0; i < 10; i++) {
  funcs[i] = function () {
    console.log(i);
  };
}
funcs[0](); // 0

babel 编译之后的代码如下

var funcs = [];

var _loop = function _loop(i) {
  funcs[i] = function () {
    console.log(i);
  };
};

for (var i = 0; i < 10; i++) {
  _loop(i);
}
funcs[0](); // 0

看到这里就很明白了,其实就是在es6出现之前解决那个经典的循环中var变量渗透出来导致func[1-10]()结果都是10的方法:闭包

let 就是借助闭包和函数作用域来实现块级作用域的效果的。

2. 0.1+0.2 !== 0.3 原因及解决办法

原因其实很简单,就来自于 IEEE 754 的特性。

首先,IEEE 754 中数字是这样保存的:

image.png

所以超过53位有效数字的部分就会被截断。你可能会想到Number.MAX_SAFE_INTEGER === Math.pow(2,53) -1,想到大数会被截断。但其实在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会发生无限循环,所以即使是0.1和0.2这样小的数字也会发生截断导致精度丢失。

并且更糟糕的是由于0.1和0.2指数位数不相同(0.2比0.1大1),运算时需要对阶运算,所以把 0.1 的尾码右移一位,阶码减 1,让两个数的阶码(指数位数)保持一致,这一步也进行了截断,所以也导致了精度丢失。两步加起来,由于截断导致出现误差不相等也很正常了。

那么又怎么解决这个问题呢?一个很简单的想法就是把它们都转换成整数运算,因为在整数部分不会出现精度丢失(除了大于9007199254740991的大数)。但是如果小数位数够多,确实也会超过Number.MAX_SAFE_INTEGER导致出问题。这时候可以考虑:

①找到一个可以接受的误差值,比如使用使用 Number.EPSILON 误差范围。当然这个就看具体应用场景接受不接受了。

function isEqual(a, b) { 
    return Math.abs(a - b) < Number.EPSILON; 
} 
console.log(isEqual(0.1 + 0.2, 0.3)); // true

②直接转成字符串,对字符串做加法运算。这种做法基本没有什么问题。

这种做法简单的来说就是不断地每位相加得到最后的结果。

// 字符串数字相加
var addStrings = function (num1, num2) {
  let i = num1.length - 1;
  let j = num2.length - 1;
  const res = [];
  let carry = 0;
  while (i >= 0 || j >= 0) {
    const n1 = i >= 0 ? Number(num1[i]) : 0;
    const n2 = j >= 0 ? Number(num2[j]) : 0;
    const sum = n1 + n2 + carry;
    res.unshift(sum % 10);
    carry = Math.floor(sum / 10);
    i--;
    j--;
  }
  if (carry) {
    res.unshift(carry);
  }
  return res.join("");
};

function isEqual(a, b, sum) {
  const [intStr1, deciStr1] = a.toString().split(".");
  const [intStr2, deciStr2] = b.toString().split(".");
  const inteSum = addStrings(intStr1, intStr2); // 获取整数相加部分
  const deciSum = addStrings(deciStr1, deciStr2); // 获取小数相加部分
  return inteSum + "." + deciSum === String(sum);
}

console.log(isEqual(0.1, 0.2, 0.3)); // true

总结

本章内容很多很杂,印象深刻的是关于Symbol的方法介绍了一大堆,然后我全部不知道有什么用...

所以总结的时候也没有把那些方法全都写下来,可能等我对Symbol更了解之后会再更新。

面试相关文章推荐

硬核基础二进制篇(一)0.1 + 0.2 != 0.3 和 IEEE-754 标准

详细的解释了IEEE 754 中码阶和尾数的意义,并且案例分析了0.1和0.2是怎么表示怎么相加的。