前言
前篇我们刚刚了解了JavaScript在HTML中使用的一些细节,本章我们终于要开始详细的介绍javaScript的语言相关知识。本章内容较多,我会尝试尽量记录一些可拓展的,比较常考的知识点,并且尝试用对比的方法让它们更容易记忆。
思维导图
因为第三部分 数据类型的内容太多了,所以单独列为一张知识导图。
同时因为本章关于 Object 和函数的内容过于零碎,所以我打算整理到详细讲解它们的章节中。
表格
表格一 var 和 let 区别
| var | let | |
|---|---|---|
| 作用域 | 函数作用域 | 块作用域 |
| 全局声明 | 作为 window 的属性 | 不作为 window 的属性 |
| 声明提升 | 提升到作用域顶端 | 在声明前无法访问(暂时性死区) |
| 冗余声明 | 不报错 | 报错 |
| for 循环中 | 渗透 | 每个迭代声明一个新的迭代变量 |
表格二 Boolean() 转型函数
| 数据类型 | 转为 true | 转为 false |
|---|---|---|
| Boolean | true | false |
| String | 非空字符串 | ''(空字符串) |
| Number | 非零数值(包括无穷值) | 0, NaN |
| Object | 任意对象 | null |
| Undefined | undefined |
表格三 Number(), parseInt(), parseFloat() 转型函数区别
| Number() | parseInt() | parseFloat() | |
|---|---|---|---|
| Boolean | true => 1; false => 0 | 同 | 同 |
| Number | 直接返回 | 返回整数 | 直接返回 |
| Null | 0 | 同 | 同 |
| undefined | NaN | 同 | 同 |
| 字符串 | ① 有数值就返回,无就NaN ②空字符串返回0 | ①同但是②空字符串返回NaN | ①同但是②空字符串返回NaN |
| 对象 | valueOf()值不为NaN就返回,为NaN就再调用toString() | 同 | 同 |
主要注意三个函数处理空字符串的表现不一样。
四 逻辑与或非表现
逻辑非可以理解为 Boolean() 函数的表现再取反,这里就不做介绍。
如果直接把书关于逻辑与和或的特殊规则特别长,难以记忆。不过在理解了它们的性质之后就比较好记忆了。逻辑与和逻辑或都是短路操作符,即第一个操作数决定了结果的话,第二个操作数就不会被求值。
比如让我们观察上面的规则:逻辑与中如果第一个操作数是对象,则返回第二个操作数。那是因为对象肯定会被判为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 对象等。
- typeof:能判断所有值类型,函数。不可对 null、对象、数组进行精确判断,因为都返回
-
类型转换结果:这个就看表格就可以了
-
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 中数字是这样保存的:
所以超过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是怎么表示怎么相加的。