# JS红皮书笔记

# 第1章 什么是JavaScript

  • ECMAScript:由ECMA-262定义并提供核心功能。
  • 文档对象模型(DOM):提供与网页内容交互的方法和接口。
  • 浏览器对象模型(BOM):提供与浏览器交互的方法和接口

# 第2章 HTML中的JavaScript

  • <script>元素有下列8个属性

    • async:可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部脚本文件有效。
    • charset:可选.使用 src 属性指定的代码字符集
    • crossorigin:可选。配置相关请求的CORS(跨源资源共享)设置。默认不使用CORS。crossorigin= "anonymous"配置文件请求不必设置凭据标志。crossorigin="use-credentials"设置凭据标志,意味着出站请求会包含凭据
    • defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。在 IE7 及更早的版本中,对行内脚本也可以指定这个属性
    • integrity:可选。允许比对接收到的资源和指定的加密签名以验证子资源完整性(SRI,Subresource Integrity)。如果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络(CDN,Content Delivery Network)不会提供恶意内容
    • language:废弃。
    • src:可选。表示包含要执行的代码的外部文件
    • type:可选。代替 language,表示代码块中脚本语言的内容类型(也称 MIME 类型)。按照惯例,这个值始终都是"text/javascript"
  • HTML 4.01 为<script>元素定义了一个叫 defer 的属性。这个属性表示脚本在执行的时候不会改变页面的结构。也就是说,脚本会被延迟到整个页面都解析完毕后再运行.因此,在<script>元素上设置 defer 属性,相当于告诉浏览器立即下载,但延迟执行.HTML5 规范要求脚本应该按照它们出现的顺序执行,因此第一个推迟的脚本会在第二个推迟的脚本之前执行,而且两者都会在 DOMContentLoaded 事件之前执行(关于事件,请参考第 17 章)。不过在实际当中,推迟执行的脚本不一定总会按顺序执行或者在 DOMContentLoaded事件之前执行,因此最好只包含一个这样的脚本

  • async 的脚本并不保证能按照它们出现的次序执行,。给脚本添加 async 属性的目的是告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到该异步脚本下载和执行后再加载其他脚本。正因为如此,异步脚本不应该在加载期间修改 DOM。异步脚本保证会在页面的 load 事件前执行,但可能会在 DOMContentLoaded(参见第 17 章)之前或之后

  • 在 XHTML(及 XML)中,CDATA 块表示文档中可以包含任意文本的区块,其内容不作为标签来解析,

  • 最初的文档模式有两种:混杂模式(quirks mode)和标准模式(standards mode).后来又有了 准标准模式(almost standards mode)。

  • <noscript>元素可以包含任何可以出现在<body>中的 HTML 元素,<script>除外。

  • 要包含外部 JavaScript 文件,必须将 src 属性设置为要包含文件的 URL。文件可以跟网页在同一台服务器上,也可以位于完全不同的域。

  • 所有<script>元素会依照它们在网页中出现的次序被解释。在不使用 defer 和 async 属性的情况下,包含在<script>元素中的代码必须严格按次序解释。

  • 对不推迟执行的脚本,浏览器必须解释完位于<script>元素中的代码,然后才能继续渲染页面的剩余部分。为此,通常应该把<script>元素放到页面末尾,介于主内容之后及</body>标签之前。

  • 可以使用 defer 属性把脚本推迟到文档渲染完毕后再执行。推迟的脚本原则上按照它们被列出的次序执行。

  • 可以使用 async 属性表示脚本不需要等待其他脚本,同时也不阻塞文档渲染,即异步加载。异步脚本不能保证按照它们在页面中出现的次序执行。

  • 通过使用<noscript>元素,可以指定在浏览器不支持脚本时显示的内容。如果浏览器支持并启用脚本,则<noscript>元素中的任何内容都不会被渲染。

# 第3章 语言基础

  • var message; 这行代码定义了一个名为 message 的变量,可以用它保存任何类型的值。(不初始化的情况下,变量会保存一个特殊值 undefined

  • 使用 var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:

  • 所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部。

  • 块作用域是函数作用域的子集,因此适用于 var 的作用域限制同样也适用于 let

  • [暂时性死区]let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升。

  • 在解析代码时,JavaScript 引擎也会注意出现在块后面的 let 声明,只不过在此之前不能以任何方式来引用未声明的变量。在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。

  • for循环中。在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。 每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循 环执行过程中每个迭代变量的值。

  • 调用typeof null 返回的是"object"。这是因为特殊值 null 被认为是一个对空对象的引用。

    • Null 类型同样只有一个值,即特殊值 null。逻辑上讲,null 值表示一个空对象指针,这也是给typeof 传一个 null 会返回"object"的原因
    • 在定义将来要保存对象值的变量时,建议使用 null 来初始化,不要使用其他值
  • 任何未经初始化的变量都会取得 undefined 值。

  • 之所以0.1+0.2 ==0.3 存在这种舍入错误,是因为使用了 IEEE 754 数值,这种错误并非 ECMAScript 所独有。其他使用相同格式的语言也有这个问题。

  • toString()可以接收一个底数参数,即以什么底数来输出数值的字符串表示.比如 (10).toString(16) == a

  • 在模版字符串中, 将表达式转换为字符串时会调用 toString(): let foo = { toString: () => 'World' }; console.log(Hello, ${ foo }!) === Hello World

  • ❗️❗️❗️ 模板字面量标签函数❗️❗️❗️

  • 使用模板字面量也可以直接获取原始的模板字面量内容 ,而不是被转换后的字符.比如表示String.raws+\u009

  • Symbol.for()对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运 行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。后续使用相同 字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例

  • 还可以使用 Symbol.keyFor()来查询全局注册表,let s = Symbol.for('foo'); console.log(Symbol.keyFor(s))

  • Object 也是派生其他对象的基类。

  • ++a ,a++ 。后缀版与前缀版的主要区别在于,后缀版递增和递减在语句被求值后才发生。

    • let num1 = 2;
    • let num2 = 20;
    • let num3 = num1-- + num2;
    • let num4 = num1 + num2;
    • console.log(num3); // 22
    • console.log(num4); // 21
  • 如果是对象,则调用其(第 5 章会详细介绍的)valueOf()方法取得可以操作的值。对得到的值应用上述, var obj = {toString(){return 1000}} ++obj

  • 位操作符是操作内存中表示数据的比特(位)。

  • ❗️❗️❗️负值以一种称为二补数(或补码)的二进制编码存储。

  • 按位非操作符用波浪符(~)表示,它的作用是返回数值的一补数。按按位非的最终效果是对数值取反并减 1, ~25

  • 按位与操作在两个位都是 1 时返回 1,在任何一位是 0 时返回 0。 25 & 3 == 1

  • 按位或操作在至少一位是 1 时返回 1,两位都是 0 时返回 0。 25 | 3 == 27

  • 按位异或与按位或的区别是,它只在一位上是 1 的时候返回 1(两位都是 1 或 0,则返回 0)。25 ^ 3

  • 左移操作符用两个小于号(<<)表示,会按照指定的位数将数值的所有位向左移动 2<<5 就是2的2进制 10全部左移5位 1000000

  • 有符号右移由两个大于号(>>)表示,会将数值的所有 32 位都向右移,同时保留符号(正或负)。有符号右移实际上是左移的逆运算。

  • ECMAScript 中的基本数据类型包括 Undefined、Null、Boolean、Number、String 和 Symbol。  与其他语言不同,ECMAScript 不区分整数和浮点值,只有 Number 一种数值数据类型。

  • Object 是一种复杂数据类型,它是这门语言中所有对象的基类。

  • 严格模式为这门语言中某些容易出错的部分施加了限制。

  • ECMAScript 提供了 C 语言和类 C 语言中常见的很多基本操作符,包括数学操作符、布尔操作符、关系操作符、相等操作符和赋值操作符等。

  • 这门语言中的流控制语句大多是从其他语言中借鉴而来的,比如 if 语句、for 语句和 switch语句等。ECMAScript 中的函数与其他语言中的函数不一样。

  • 不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值。

  • 不指定返回值的函数实际上会返回特殊值 undefined。

# 第4章 变量、作用域与内存

ECMAScript 中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数 中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是 引用值,那么就跟引用值变量的复制一样。对很多开发者来说,这一块可能会不好理解,毕竟变量有按 值和按引用访问,而传参则只有按值传递。

在按值传递参数时,值会被复制到一个局部变量(即一个命名参数,或者用 ECMAScript 的话说, 就是 arguments 对象中的一个槽位)。在按引用传递参数时,值在内存中的位置会被保存在一个局部变 量,这意味着对本地变量的修改会反映到函数外部。(这在 ECMAScript 中是不可能的。)

# 4.2

执行上下文(以下简称“上下文”)的概念在 JavaScript 中是颇为重要的。变量或函数的上下文决定 了它们可以访问哪些数据,以及它们的行为。每个上下文都有一个关联的变量对象(variable object), 而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台 处理数据会用到它。 全局上下文是最外层的上下文。

  • 定义在全局上下文上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)

每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。 在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。ECMAScript 程序的执行流就是通过这个上下文栈进行控制的。 上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain)。这个作用域链决定 了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域 链的最前端。如果上下文是函数,则其活动对象(activation object)用作变量对象。活动对象最初只有 一个定义变量:arguments。(全局上下文中没有这个变量。)作用域链中的下一个变量对象来自包含上 下文,再下一个对象来自再下一个包含上下文。以此类推直至全局上下文;全局上下文的变量对象始终 是作用域链的最后一个变量对象。 代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。搜索过程始终从作用域链 的最前端开始,然后逐级往后,直到找到标识符。(如果没有找到标识符,那么通常会报错。)

在使用 var 声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函 数的局部上下文。在 with 语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了, 那么它就会自动被添加到全局上下文,

严格来讲,let 在 JavaScript 运行时中也会被提升,但由于“暂时性死区”(temporal dead zone)的 缘故,实际上不能在声明之前使用 let 变量。因此,从写 JavaScript 代码的角度说,let 的提升跟 var 是不一样的。

标识符查找 当在特定上下文中为读取或写入而引用一个标识符时,必须通过搜索确定这个标识符表示什么。搜 索开始于作用域链前端,以给定的名称搜索对应的标识符。如果在局部上下文中找到该标识符,则搜索 停止,变量确定;如果没有找到变量名,则继续沿作用域链搜索。(注意,作用域链中的对象也有一个 原型链,因此搜索可能涉及每个对象的原型链。)这个过程一直持续到搜索至全局上下文的变量对象。 如果仍然没有找到标识符,则说明其未声明。

JavaScript 最常用的垃圾回收策略是标记清理

当变量进入上下文,比如在函数 内部声明一个变量时,这个变量会被加上存在于上下文中的标记。当变量离开上下文时,也会被加上离开上下文的标记。

IE7 发布后,JavaScript 引擎的垃圾回收程序被调优为动态改变分配变量、字面量或数组槽位等会触 发垃圾回收的阈值。IE7 的起始阈值都与 IE6 的相同。如果垃圾回收程序回收的内存不到已分配的 15%, 这些变量、字面量或数组槽位的阈值就会翻倍。如果有一次回收的内存达到已分配的 85%,则阈值重置 为默认值。这么一个简单的修改,极大地提升了重度依赖 JavaScript 的网页在浏览器中的性能。

要注意,解除对一个值的引用并不会自动导致相关内存被回收。解除引用的关键在于确保相关 的值已经不在上下文里了,因此它在下次垃圾回收时会被回收。

# 4.3

  • 隐藏类和删除操作

  • 静态分配与对象池

  • 原始值大小固定,因此保存在栈内存上。

  • 从一个变量到另一个变量复制原始值会创建该值的第二个副本。

  • 引用值是对象,存储在堆内存上。

  • 包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身。

  • 从一个变量到另一个变量复制引用值只会复制指针,因此结果是两个变量都指向同一个对象。

  • typeof 操作符可以确定值的原始类型,而 instanceof 操作符用于确保值的引用类型。 任何变量(不管包含的是原始值还是引用值)都存在于某个执行上下文中(也称为作用域)。这个 上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪些部分。执行上下文可以总结 如下。

  • 执行上下文分全局上下文、函数上下文和块级上下文。

  • 代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数。

  • 函数或块的局部上下文不仅可以访问自己作用域内的变量,而且也可以访问任何包含上下文乃 至全局上下文中的变量。

  • 全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据。

  • 变量的执行上下文用于确定什么时候释放内存。 JavaScript 是使用垃圾回收的编程语言,开发者不需要操心内存分配和回收。JavaScript 的垃圾回收 程序可以总结如下。

  • 离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。

  • 主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它们的内存。

  • 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。JavaScript 引擎不再使用这种算 法,但某些旧版本的 IE 仍然会受这种算法的影响,原因是 JavaScript 会访问非原生 JavaScript 对 象(如 DOM 元素)。

  • 引用计数在代码中存在循环引用时会出现问题。

  • 解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对 象、全局对象的属性和循环引用都应该在不需要时解除引用。

# 第5章 基本引用类型

# 5.1 Date

所有实现都必须支持下列日期格式:

  • “月/日/年”,如"5/23/2019";

  • “月名 日, 年”,如"May 23, 2019";

  • “周几 月名 日 年 时:分:秒 时区”,如"Tue May 23 2019 00:00:00 GMT-0700";  ISO 8601 扩展格式“YYYY-MM-DDTHH:mm:ss.sssZ”,如 2019-05-23T00:00:00(只适用于兼容 ES5 的实现)

  • Date 类型的 valueOf()方法根本就不返回字符串,这个方法被重写后返回的是日期的毫秒表示。因此,操作符(如小于号和大于号)可以直接使用它返回的值。

  • RegExp 构造函数的属性

    • input $_ 最后搜索的字符串(非标准特性)
    • lastMatch $& 最后匹配的文本
    • lastParen $+ 最后匹配的捕获组(非标准特性)
    • leftContext $` input 字符串中出现在 lastMatch 前面的文本
    • rightContext $' input 字符串中出现在 lastMatch 后面的文本

# 5.3 原始值包装类型

  • 在以读模式访问字符串值的任何时候,后台都会执行以下 3 步:
    • (1) 创建一个 String 类型的实例; let s1 = new String("some text");
    • (2) 调用实例上的特定方法;let s2 = s1.substring(2);
    • (3) 销毁实例。s1 = null;
  • Object 构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。比如 let obj = new Object("some text")。如果传给 Object 的是字符串,则会创建一个 String 的实例。如果是数值,则会创建 Number 的实例。布尔值则会得到 Boolean 的实例。;
  • Boolean 的实例会重写 valueOf()方法,返回一个原始值 true 或 false。toString()方法被调用时也会被覆盖,返回字符串"true"或"false"。不
  • 意 要深入了解关于字符编码的内容,推荐 Joel Spolsky 写的博客文章:“The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)”。
  • 虽然 ECMA-262 没有规定直接访问 Global 对象的方式,但浏览器将 window 对象实现为 Global对象的代理。因此,所有全局作用域中声明的变量和函数都变成了 window 的属性。
  • 当代码开始执行时,全局上下文中会存在两个内置对象:Global 和 Math。其中,Global 对象在 大多数 ECMAScript 实现中无法直接访问。不过,浏览器将其实现为 window 对象。所有全局变量和函 数都是 Global 对象的属性。Math 对象包含辅助完成复杂计算的属性和方法。
  • 函数实际上是 Function 类型的实例,也就是说函数也是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。

# 第6章 集合引用类型

  • zeroes.fill(7, 1, 3);

# ❗️❗️❗️6.3 定型数组 “TypedArray”类型,

  • OpenGL ES是 OpenGL 专注于 2D 和 3D 计算机图形的子集。这个新 API 被命名为 WebGL(Web Graphics Library),

# 第7章 迭代器与生成器

# 第8章 对象、类与面向对象编程

# 第9章 代理与反射

# 第10章 函 数

# 第11章 期约与异步函数

# 第12章 BOM

# 第13章 客户端检测

# 第14章 DOM

# 第15章 DOM扩展

# 第16章 DOM2和DOM3

# 第17章 事件

# 第18章 动画与Canvas图形

# 第19章 表单脚本

# 第20章 JavaScript API

# 第21章 错误处理与调试

# 第22章 处理XML

# 第23章 JSON

# 第24章 网络请求与远程资源

# 第25章 客户端存储

# 25.1.1 限制

  • 不超过 300 个 cookie;
  • 每个 cookie 不超过 4096 字节;
  • 每个域不超过 20 个 cookie;
  • 每个域不超过 81 920 字节。

Set-Cookie: name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; path=/; domain=.wrox.com

  • 名称:
  • 值:
  • 域:
  • 路径:
  • 过期时间:表
  • 安全标志:设

最好还是使用 encodeURIComponent()对名称和值进行编码, document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas") + "; domain=.wrox.com; path=/";

还有一种叫作 HTTP-only 的 cookie。HTTP-only 可以在浏览器设置,也可以在服务器设置,但只能 在服务器上读取,这是因为 JavaScript 无法取得这种 cookie 的值

# 25.2 Web Storage

clear() getItem(name) key(index) removeItem(name) setItem(name, value)

# 25.2.2 sessionStorage 对象

sessionStorage 对象只存储会话数据

存储在sessionStorage 对象中的数据只能由最初存储数据的页面使用,在多页应用程序中的用处有限。

localStorage 对象取代了 globalStorage,作为在客户端持久存储数据的机制

存储在 localStorage 中的数据会保留到通过 JavaScript 删除或者用户清除浏览器缓存。localStorage 数据不受页面刷新影响, 也不会因关闭窗口、标签页或重新启动浏览器而丢失。

# 25.2.4 存储事件

每当 Storage 对象发生变化时,都会在文档上触发 storage 事件

window.addEventListener("storage", 
 (event) => alert('Storage changed for ${event.domain}'));
1
2

# 25.3 IndexedDB

Indexed Database API 简称 IndexedDB,是浏览器中存储结构化数据的一个方案。IndexedDB 用于代 替目前已废弃的 Web SQL Database API。IndexedDB 背后的思想是创造一套 API,方便 JavaScript 对象的 存储和获取,同时也支持查询和搜索。 IndexedDB 的设计几乎完全是异步的。 IndexedDB 操作要求添加 onerror 和 onsuccess 事件处理程序来确定输出

# 25.3.1 数据库

IndexedDB 使用对象存储而不是表格保存数据。

使用 IndexedDB 数据库的第一步是调用 indexedDB.open()方法,并给它传入一个要打开的数据 库名称。如果给定名称的数据库已存在,则会发送一个打开它的请求;如果不存在,则会发送创建并打 开这个数据库的请求。这个方法会返回 IDBRequest 的实例,可以在这个实例上添加 onerror 和 onsuccess 事件处理程序。


let db, 
 request, 
 version = 1; 
request = indexedDB.open("admin", version); 
request.onerror = (event) => 
 alert(`Failed to open: ${event.target.errorCode}`); 
request.onsuccess = (event) => { 
 db = event.target.result; 
};
1
2
3
4
5
6
7
8
9
10

# 25.3.2 对象存储

用户名必须全局唯一,它也是大多数情况下访问数据的凭据。这个键很重要,因为创建对象存储时必须指定一个键.

数据库的版本决定了数据库模式,包括数据库中的对象存储和这些对象存储的结构。如果数据库还 不存在,open()操作会创建一个新数据库,然后触发 upgradeneeded 事件。可以为这个事件设置处 理程序,并在处理程序中创建数据库模式。如果数据库存在,而你指定了一个升级版的版本号,则会立 即触发 upgradeneeded 事件,因而可以在事件处理程序中更新数据库模式。

request.onupgradeneeded = (event) => { 
 const db = event.target.result; 
 // 如果存在则删除当前 objectStore。测试的时候可以这样做
 // 但这样会在每次执行事件处理程序时删除已有数据
 if (db.objectStoreNames.contains("users")) { 
 db.deleteObjectStore("users"); 
 } 
 db.createObjectStore("users", { keyPath: "username" }); 
}; 
这里第二个参数的 keyPath 属性表示应该用作键的存储对象的属性名。
1
2
3
4
5
6
7
8
9
10

# 25.3.3 事务

创建了对象存储之后,剩下的所有操作都是通过事务完成的。事务要通过调用数据库对象的 transaction()方法创建.

如果不指定参数,则对数据库中所有的对象存储有只读权限 let transaction = db.transaction();

指定一个或多个要访问的对象存储的名称: let transaction = db.transaction("users"); 这样可以确保在事务期间只加载 users 对象存储的信息。如果想要访问多个对象存储,可以给第一个参数传入一个字符串数组: let transaction = db.transaction(["users", "anotherStore"]);

每个事务都以只读方式访问数据。要修改访问模式,可以传入第二个参数。这个参数应 该是下列三个字符串之一:"readonly"、"readwrite"或"versionchange"。比如: let transaction = db.transaction("users", "readwrite");

有了事务的引用,就可以使用 objectStore()方法并传入对象存储的名称以访问特定的对象存储。 然后,可以使用 add()和 put()方法添加和更新对象,使用 get()取得对象,使用 delete()删除对象, 使用 clear()删除所有对象。其中,get()和 delete()方法都接收对象键作为参数,

const transaction = db.transaction("users"), 
 store = transaction.objectStore("users"), 
 request = store.get("007"); 
request.onerror = (event) => alert("Did not get the object!"); 
request.onsuccess = (event) => alert(event.target.result.firstName);

transaction.oncomplete = (event) => { 
 // 整个事务成功完成
};
1
2
3
4
5
6
7
8
9

# 25.3.5 通过游标查询

使用事务可以通过一个已知键取得一条记录。如果想取得多条数据,则需要在事务中创建一个游标 游标是一个指向结果集的指针。与传统数据库查询不同,游标不会事先收集所有结果。相反,游标指向 第一个结果,并在接到指令前不会主动查找下一条数据。 需要在对象存储上调用 openCursor()方法创建游标 openCursor()方法实际上可以接收两个参数,第一个是 IDBKeyRange 的实例,第二个是表示方 向的字符串。

request = store.openCursor(null, "nextunique"); 注意,openCursor()的第一个参数是 null,表示默认的键范围是所有值。此游标会遍历对象存 储中的记录,从第一条记录开始迭代,到最后一条记录,但会跳过重复的记录。

const transaction = db.transaction("users"), 
 store = transaction.objectStore("users"), 
 request = store.openCursor(); 
request.onsuccess = (event) => { 
 // 处理成功
}; 
request.onerror = (event) => { 
 // 处理错误
};
1
2
3
4
5
6
7
8
9

# 25.3.6 键范围

const onlyRange = IDBKeyRange.only("007"); const lowerRange = IDBKeyRange.lowerBound("007"); const lowerRange = IDBKeyRange.lowerBound("007", true); const upperRange = IDBKeyRange.upperBound("ace"); const upperRange = IDBKeyRange.upperBound("ace", true);

// 从"007"记录开始,到"ace"记录停止 const boundRange = IDBKeyRange.bound("007", "ace"); // 从"007"的下一条记录开始,到"ace"记录停止 const boundRange = IDBKeyRange.bound("007", "ace", true); // 从"007"的下一条记录开始,到"ace"的前一条记录停止 const boundRange = IDBKeyRange.bound("007", "ace", true, true); // 从"007"记录开始,到"ace"的前一条记录停止 const boundRange = IDBKeyRange.bound("007", "ace", false, true);

# 25.3.10 限制

IndexedDB 的很多限制实际上与 Web Storage 一样。首先,IndexedDB 数据库是与页面源(协议、域 和端口)绑定的,因此信息不能跨域共享。这意味着 www.wrox.com 和 p2p.wrox.com 会对应不同的数据 存储。 其次,每个源都有可以存储的空间限制。当前 Firefox 的限制是每个源 50MB,而 Chrome 是 5MB。 移动版 Firefox 有 5MB 限制,如果用度超出配额则会请求用户许可

# 第26章 模块

将代码拆分成独立的块,然后再把这些块连接起来可以通过模块模式来实现。这种模式背后的思想 很简单:把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪 些外部代码。

模块系统的核心是管理依赖。

模块加载是“阻塞的”,这意味着前置操作必须完成才能执行后续操作。每个模块在自己的代码到达 浏览器之后完成加载,此时其依赖已经加载并初始化。不过,这个策略存在一些性能和复杂性问题。为 一个应用程序而按顺序加载五个 JavaScript 文件并不理想,并且手动管理正确的加载顺序也颇为棘手

ECMAScript 6 模块是作为一整块 JavaScript 代码而存在的。带有 type="module"属性的<script> 标签会告诉浏览器相关代码应该作为模块执行,而不是作为传统的脚本执行。模块可以嵌入在网页中, 也可以作为外部文件引入:

与传统脚本不同,所有模块都会像<script defer>加载的脚本一样按顺序执行。解析到<script 
type="module">标签后会立即下载模块文件,但执行会延迟到文档解析完成。无论对嵌入的模块代码,
还是引入的外部模块文件,都是这样。<script type="module">在页面中出现的顺序就是它们执行
的顺序。与<script defer>一样,修改模块标签的位置,无论是在<head>还是在<body>中,只会影
响文件什么时候加载,而不会影响模块什么时候加载。

1
2
3
4
5
6

# 26.4.3 模块行为

ECMAScript 6 模块借用了 CommonJS 和 AMD 的很多优秀特性。下面简单列举一些。  模块代码只在加载后执行。  模块只能加载一次。  模块是单例。  模块可以定义公共接口,其他模块可以基于这个公共接口观察和交互。  模块可以请求加载其他模块。  支持循环依赖。 ES6 模块系统也增加了一些新行为。  ES6 模块默认在严格模式下执行。  ES6 模块不共享全局命名空间。  模块顶级 this 的值是 undefined(常规脚本中是 window)。  模块中的 var 声明不会添加到 window 对象。  ES6 模块是异步加载和执行的。

const moduleWorker = new Worker('moduleWorker.js', { type: 'module' });

# 第27章 工作者线程

# 第28章 最佳实践

# 28.1.1 什么是可维护的代码

通常,说代码“可维护”就意味着它具备如下特点。  容易理解:无须求助原始开发者,任何人一看代码就知道它是干什么的,以及它是怎么实现的。  符合常识:代码中的一切都显得顺理成章,无论操作有多么复杂。  容易适配:即使数据发生变化也不用完全重写。  容易扩展:代码架构经过认真设计,支持未来扩展核心功能。  容易调试:出问题时,代码可以给出明确的信息,通过它能直接定位问题。

# 28.1.3 松散耦合

只要应用程序的某个部分对另一个部分依赖得过于紧密,代码就会变成紧密耦合,因而难以维护。 典型的问题是在一个对象中直接引用另一个对象

# 28.1.4 编码惯例

 不要给实例或原型添加属性。  不要给实例或原型添加方法。  不要重定义已有的方法。

# 28.2.1 作用域意识

第 4 章讨论过 JavaScript 作用域的概念,以及作用域链的工作原理。随着作用域链中作用域数量的 增加,访问当前作用域外部变量所需的时间也会增加。访问全局变量始终比访问

不使用 with 语句 在性能很重要的代码中,应避免使用 with 语句。与函数类似,with 语句会创建自己的作用域, 因此也会加长其中代码的作用域链。在 with 语句中执行的代码一定比在它外部执行的代码慢,因为作 用域链查找时多一步。

# 28.2.2 选择正确的方法

如果实现某个需求既可以使用数组的数值索引,又可以使用命名属性(比如 NodeList 对象),那就都应 该使用数值索引。

优化循环

(1) 简化终止条件。因为每次循环都会计算终止条件,所以它应该尽可能地快。这意味着要避免属性 查找或其他 O(n)操作。 (2) 简化循环体。循环体是最花时间的部分,因此要尽可能优化。要确保其中不包含可以轻松转移到 循环外部的密集计算。 (3) 使用后测试循环。最常见的循环就是 for 和 while 循环,这两种循环都属于先测试循环。do-while 就是后测试循环,避免了对终止条件初始评估 ,因此应该会更快。

展开循环 如果不能提前预知循环的次数,那么或许可以使用一种叫作达夫设备(Duff’s Device)的技术

 switch 语句很快。如果代码中有复杂的 if-else 语句,将其转换成 switch 语句可以变得更 快。然后,通过重新组织分支,把最可能的放前面,不太可能的放后面,可以进一步提升性能。  位操作很快。在执行数学运算操作时,位操作一定比任何布尔值或数值计算更快。选择性地将 某些数学操作替换成位操作,可以极大提升复杂计算的效率。像求模、逻辑 AND 与和逻辑 OR 或都很适合替代成位操作。

# 28.2.3 语句最少化

  1. 多个变量声明 let count = 5, color = "blue", values = [1,2,3],
  2. 插入迭代性值 let name = values[i++];
  3. 使用数组和对象字面量