# 你不知道的 JavaScript(中卷)
# 类型和语法
# 类型
最新的 ECMAScript 标准定义了 8 种数据类型
null undefined boolean string number symbol object bigint
typeof 有一个特殊的安全防范机制。对于未定义的变量会返回 undefined
# 值
# 数组
数组手动设置 undefined 和 空白单元不一致
数组可以设置非数字索引 但是类似给对象添加属性 无法改变 length
类数组
// Determine if o is an array-like object.
// Strings and functions have numeric length properties, but are
// excluded by the typeof test. In client-side JavaScript, DOM text
// nodes have a numeric length property, and may need to be excluded
// with an additional o.nodeType != 3 test.
function isArrayLike(o) {
if (
o && // o is not null, undefined, etc.
typeof o === 'object' && // o is an object
isFinite(o.length) && // o.length is a finite number
o.length >= 0 && // o.length is non-negative
o.length === Math.floor(o.length) && // o.length is an integer
o.length < 4294967296
)
// o.length < 2^32
return true
// Then o is array-like
else return false // Otherwise it is not
}
类数组对象转化为数组
// 1
Array.prototype.slice.call(arrLike)
//2
Array.from(arrLike)
# 字符串
翻转字符串
a.splice('') // 将字符串已参数分割为数组
.reverse() // 翻转数组
.join('') //
多数情况下 将字符串转换为数组更加便于处理
# 数字
JavaScript 中的数字类型是基于 IEEE 754 标准来实现的,该标准通常也被称为“浮点数”。JavaScript 使用的是“双精度”格式(即 64 位二进制)。
0.1 + 0.2 === 0.3 //false
简单来说,二进制浮点数中的 0.1 和 0.2 并不是十分精确,它们相加的结果并非刚好等于 0.3,而是一个比较接近的数字 0.30000000000000004,所以条件判断结果为 false。
怎么解决这个问题?
- 设置一个精度范围(机器精度) 如果是这个范围内的就默认是相等的
Number.EPSILON Math.pow(2,-52)
整数的安全范围
Math.pow(2,53)-1
IEEE754 规定,双精度浮点数的有效数字是 52 有效数字实际可以存储 53 位。
# 特殊的数值
null undefined
# 不是数字的数字
NaN 计算失败的值 不等于自身 用 isNaN 判断类型
# 无穷数
Infinity (1/0)
# 值和引用
!!我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。
赋值/参数传递是通过引用还是值复制完全由值的类型来决定,所以使用哪种类型也间接决定了赋值/参数传递的方式。
# 原生函数
String() Number() Boolean() Function() Object() Array() RegExp() 字面量 // Date()
# 内部属性 class
Object.prototype.toString.call([]) // 判断数据类型
# 封装
# 强制类型转换
# 语法
# 异步和性能
# 异步:现在与将来
# 事件循环
JavaScript 引擎本身所做的只不过是在需要的时候,在给定的任意时刻执行程序中的单个代码块。
JavaScript 引擎本身并没有时间的概念,只是一个按需执行 JavaScript 任意代码片段的环境。“事件”(JavaScript 代码执行)调度总是由包含它的环境进行。-- 由宿主程序来调度 JavaScript 代码的执行
什么是事件循环?
一个持续运行的程序 从一个循环队列获取需要执行的代码块 ,一个个拿出来执行
setTimeout(..)并没有把你的回调函数挂在事件循环队列中。它所做的是设定一个定时器。当定时器到时后,环境会把你的回调函数放在事件循环中,这样,在未来某个时刻的 tick 会摘下并执行这个回调。
# 并行线程
单线程的好处是代码块按照栈的方式一个个运行无法同时在某一个时间点同时处理一个变量 而是又函数的不确定性去解决
# 并发
同一“进程”维护自己的事件循环队列
# 任务队列
它是挂在事件循环队列的每个 tick 之后的一个队列。在事件循环的每个 tick 中,可能出现的异步动作不会导致一个完整的新事件添加到事件循环队列中,而会在当前 tick 的任务队列末尾添加一个项目(一个任务)。
它是挂在事件循环队列的每个 tick 之后的一个队列。在事件循环的每个 tick 中,可能出现的异步动作不会导致一个完整的新事件添加到事件循环队列中,而会在当前 tick 的任务队列末尾添加一个项目(一个任务)。
# 回调
回调面临的 2 个问题
- 回调不符合大脑思考逻辑
- 回调控制反转了 无法保证调用时传入的数据是自己定义的回调函数是可以用的 Promise 控制反转在反转 坚决不可信任性
缺乏顺序性和可信任性
# Promise
什么 Promise?
# 未来值
未来值的一个重要特性:它可能成功,也可能失败。
# 完成事件
一种在异步任务中作为两个或更多步骤的流程控制机制,时序上的 this-then-that。
事件侦听对象 evt 就是 Promise 的一个模拟。
# 具有 then 方法的鸭子类型
识别 Promise(或者行为类似于 Promise 的东西)就是定义某种称为 thenable 的东西,将其定义为任何具有 then(..)方法的对象和函数。我们认为,任何这样的值就是 Promise 一致的 thenable。
你肯定已经注意到 Promise 并没有完全摆脱回调。它们只是改变了传递回调的位置。我们并不是把回调传递给 foo(..),而是从 foo(..)得到某个东西(外观上看是一个真正的 Promise),然后把回调传给这个东西。
如果向 Promise.resolve(..)传递一个非 Promise、非 thenable 的立即值,就会得到一个用这个值填充的 promise。
# 链式流
每次你对 Promise 调用 then(..),它都会创建并返回一个新的 Promise,我们可以将其链接起来;
不管从 then(..)调用的完成回调(第一个参数)返回的值是什么,它都会被自动设置为被链接 Promise(第一点中的)的完成。
Promise 固有特性
调用 Promise 的 then(..)会自动创建一个新的 Promise 从调用返回
then 方法总会返回一个 promise 对象
在完成或拒绝处理函数内部,如果返回一个值或抛出一个异常,新返回的(可链接的)Promise 就相应地决议。
return 返回 resolve 异常 返回 reject
如果完成或拒绝处理函数返回一个 Promise,它将会被展开,这样一来,不管它的决议值是什么,都会成为当前 then(..)返回的链接 Promise 的决议值。
返回了一个 promise(或者类 promise) 这个.then 调用的结果就是这个
resolve 可以接收一个 reject 的 promise 所以返回的 promise 可能是完成状态也可能是拒绝状态 所以使用 resolve 名字更合适
reject(..)就是拒绝这个 promise;但 resolve(..)既可能完成 promise,也可能拒绝,要根据传入参数而定。如果传给 resolve(..)的是一个非 Promise、非 thenable 的立即值,这个 promise 就会用这个值完成。
但是,如果传给 resolve(..)的是一个真正的 Promise 或 thenable 值,这个值就会被递归展开,并且(要构造的)promise 将取用其最终决议值或状态。
# 生成器
生成器可以让函数暂停 等到需要它的时候在调用函数的后半部分
解决的就是回调顺序问题
生成器是 ES6 的一个新的函数类型,它并不像普通函数那样总是运行到结束。取而代之的是,生成器可以在运行当中(完全保持其状态)暂停,并且将来再从暂停的地方恢复运行。
这种交替的暂停和恢复是合作性的而不是抢占式的,这意味着生成器具有独一无二的能力来暂停自身,这是通过关键字 yield 实现的。不过,只有控制生成器的迭代器具有恢复生成器的能力(通过 next(..))。
在异步控制流程方面,生成器的关键优点是:生成器内部的代码是以自然的同步/顺序方式表达任务的一系列步骤。其技巧在于,我们把可能的异步隐藏在了关键字 yield 的后面,把异步移动到控制生成器的迭代器的代码部分
换句话说,生成器为异步代码保持了顺序、同步、阻塞的代码模式,这使得大脑可以更自然地追踪代码,解决了基于回调的异步的两个关键缺陷之一
# async / await
一个 async 执行了 await 后面的执行也会变成异步