第 25 章 客户端存储
-
cookie是与特定域绑定的,设置cookie后,它会与请求一起发送到创建它的域
-
如果cookie总数超过了单个域的上限,浏览器就会删除之前设置的cookie
-
浏览器也会限制cookie的大小。大多数浏览器对cookie的限制是不超过4096字节,即4kb
-
cookie由名称、值、域、路径、过期时间、安全标志构成,这些参数在set-cookie头部中使用分号加空格隔开
-
document.cookie方法会返回包含 页面中所有有效的cookie的字符串,格式为key=value,以分号分隔。所有名和值都是URL编码的,因此必须使用decodeURIComponent()解码。我们也可以通过document.cookie属性设置新的字符串。这个字符串在被解析后会添加到原有cookie中。设置document.cookie不会覆盖之前存在的任何cookie,除非设置了已有的cookie。
-
没有直接删除已有cookie的方法,需要再次设置同名cookie(包括相同路径、域和安全选项),但要将其过期时间设置为某个过去的时间
-
设置了HTTP-only的cookie便不能使用js获取cookie,http-only可以在浏览器设置,也可以在服务器设置,但是只能在服务器上读取
-
所有cookie都会作为请求头部由浏览器发送给服务器,所以在cookie中保存大量信息可能会影响特定域浏览器请求的性能,保存的cookie越大,请求完成的事件越长
-
web storage的目的是解决通过客户端存储不需要频繁发送回服务器的数据时使用cookie的问题,webStorage定义了两个对象:local storage永久存储机制和session storage跨会话的存储机制
-
storage对象具有的方法
- clear():删除所有值,不在firefox中实现
- getItem(name):取得给定name的值
- key(index):取得给定数值位置的名称
- removeItem(name):删除给定name的名/值对
- setItem(name,value):设置给定name的名/值对
-
session Storage:sessionStorage对象只存储会话数据,数据只会存储到浏览器关闭
-
local Storage:要访问同一个localStorage对象,页面必须来自同一个域,在相同的端口上使用相同的协议,子域不可以
子域:子域是域名的扩展,它成为域的一部分,例如,blog.websiterating.com 将是父域 websiterating.com 的子域
-
每当Storage对象发生变化时,都会在文档上触发storage时间,使用属性或setItem()设置值,使用delete或removeItem()删除值,以及每次调用clear()时都会触发这个事件,这个事件的事件对象有如下几个属性
- domain:存储变化对应的域
- key:被设置或删除的键
- newValue:键被设置的新值,若键被删除为null
- oldValue:键变化
-
不同浏览器给localstorage和sessionstorage设置了不同的空间限制,但大多数会限制为每个源(协议,域名,端口)5MB
-
IndexedDB使用对象存储而不是表格保存数据,是网页中的异步API,IndexedDB数据库就是在一个公共命名空间下的一组对象存储,使用IndexedDB数据库的步骤:
-
调用indexedDB.open()方法,并给它传入一个要打开的数据库名称。如果给定名称的数据库已存在,则会发送一个打开它的请求;如果不存在,则会发送创建并打开这个数据库的请求。这个方法会返回IDBRequest的实例,可以在这个实例上添加onerror和onsuccess事件处理程序
-
使用对象存储
-
事务:事务要通过调用数据库对象的transaction()方法创建,每个事务都以只读方式访问数据,要修改访问模式,可以传入第二个参数,应该是"readonly"、"readwrite"或"versionchange"之一
let transaction = db.transaction(访问对象存储的名称); // 如果不指定参数,则对数据库中的所有对象存储由只读权限 -
使用objectStore()方法并传入对象存储的名称以访问特定的对象存储。然后,可以使用add()和put()方法添加和更新对象,使用get()取得对象,使用delete()删除对象,使用clear()删除所有对象。其中,get()和delete()方法都接收对象键作为参数,这5个方法都创建新的请求对象。
- add()或put()这两个方法都接收一个参数,即要存储的对象,并把对象保存到对象存储。这两个方法只在对象存储中已存在同名的键时有区别。这种情况下,add()会导致错误,而put()会简单地重写该对象。每次调用add()或put()都会创建对象存储的新更新请求。如果想验证请求成功与否,可以把请求对象保存到一个变量,然后为它添加onerror和onsuccess事件处理程序
- openCursor(IDBKeyRange的实例,表示方向的字符串):创建游标。使用事务可以通过一个已知键取得一条记录。如果想取得多条数据,则需要在事务中创建一个游标。游标是一个指向结果集的指针。与传统数据库查询不同,游标不会事先收集所有结果。相反,游标指向第一个结果,并在接到指令前不会主动查找下一条数据/cursor.value保存着实际的对象。正因为如此,在显示它之前才需要使用JSON来编码
- 如果事务没有修改对象存储的权限,update()和delete()都会抛出错误。默认情况下,每个游标只会创建一个请求。要创建另一个请求,必须调用下列中的一个方法。
- continue(key):移动到结果集中的下一条记录。参数key是可选的。如果没有指定key,游标就移动到下一条记录;如果指定了,则游标移动到指定的键。
- advance(count):游标向前移动指定的count条记录。这两个方法都会让游标重用相同的请求,因此也会重用onsuccess和onerror处理程序,直至不再需要
-
键范围:键范围(key range)可以让游标更容易管理。键范围对应IDBKeyRange的实例。指定键范围的方式:
- only(想要获取的键):使用这个范围创建的游标类似于直接访问对象存储并调用get(想要获取的键)
- 定义结果集的下限。下限表示游标开始的位置
- 定义结果集的上限,通过调用upperBound()方法可以指定游标不会越过的记录。如果不想包含指定的键,可以在第二个参数传入true:
- 同时指定下限和上限,可以使用bound()方法。这个方法接收四个参数:下限的键、上限的键、可选的布尔值表示是否跳过下限和可选的布尔值表示是否跳过上限
-
索引:通过createIndex(索引的名称,索引属性的名称,包含键unique的options对象)创建索引
- unique应该必须指定,表示这个键是否在所有记录中唯一。
-
并发问题
8. 限制
-
第 26 章 模块
-
模块标识符是所有模块系统通用的概念。模块系统本质上是键/值实体,其中每个模块都有个可用于引用它的标识符。这个标识符在模拟模块的系统中可能是字符串,在原生实现的模块系统中可能是模块文件的实际路径。模块标识符解析为实际模块的过程要根据模块系统对标识符的实现。原生浏览器模块标识符必须提供实际JavaScript文件的路径。除了文件路径,Node.js还会搜索node_modules目录,用标识符去匹配包含index.js的目录
-
模块的核心系统是管理依赖。本地模块会向模块系统声明一组外部模块(依赖),这些外部模块对于当前模块正常运行是必需的。模块系统检视这些依赖,进而保证这些外部模块能够被加载并在本地模块运行时初始化所有依赖。
-
加载模块的步骤:
- 所有依赖加载并执行
- 发送请求并等待网络返回
- 收到模块代码之后,浏览器必须确定刚收到的模块是否有依赖
- 递归的评估并加载所有依赖,直到所有依赖模块都加载完成
- 整个依赖图加载完成,才可以执行入口模块
-
泄露模块模式:这种模式只返回一个对象,其属性是私有数据和成员的引用
-
Commonjs
- Commonjs规范概述了同步声明依赖的模块定义,这个规范主要用于在服务器端实现模块化代码组织,但也可用于定义在浏览器中使用的模块依赖,Commonjs模块语法不能在浏览器中直接运行
- commonjs模块定义需要使用require()指定以来,而是用exports对象定义自己的公共API
- 无论一个模块在require()中 被引用多少次,模块永远是单例
- 模块第一次加载后会被缓存,后续加载会取得缓存的模块
- 能一次性把所有模块都加载到内存
-
AMD——异步模块定义
- 以浏览器为目标执行环境,需要考虑网络延迟的问题
- AMD一般策略是让模块声明自己的依赖,而运行在浏览器中的模块系统会按需获取依赖,并在依赖加载完成后立即执行依赖他们的模块
- AMD模块实现的核心是用函数包装模块定义,这样可以防止声明全局变量,并允许加载器库控制何时加载模块
- 该规范定义了一个单独的函数“define”,它可以作为自由变量或全局变量使用
// id:模块定义,dependencies:正在定义的模块所需的依赖项, // factory:是一个函数,应该执行该函数来实例化模块或对象。 // 如果工厂是一个函数,则只应执行一次。 // 如果工厂参数是一个对象,则应将该对象指定为模块的导出值。 define(id?, dependencies?, factory);
-
UMD——通用模块定义
- UMD定义的模块会在启动时检测要使用哪个模块系统,然后进行适当配置,并把逻辑都包装在一个立即调用的函数表达式中
-
ES6模块系统
-
集AMD和CommonJS为一起的,ES6模块是作为一整块js代码而存在的,带有type=module属性的script标签会告诉浏览器相关代码应该作为模块执行,而不是作为传统的脚本执行,设置了type=module属性的脚本会立即下载模块文件,但执行会延迟到文档解析完成。
-
完全支持ECMAScript 6模块的浏览器可以从顶级模块加载整个依赖图,且是异步完成的。浏览器会解析入口模块,确定依赖,并发送对依赖模块的请求。这些文件通过网络返回后,浏览器就会解析它们的内容,确定它们的依赖,如果这些二级依赖还没有加载,则会发送更多请求。这个异步递归加载过程会持续到整个应用程序的依赖图都解析完成。解析完依赖图,应用程序就可以正式加载模块了。
-
优点:
- 模块代码只在加载后执行
- 模块只能加载一次
- 模块是单例
- 模块可以定义公共接口,其他模块可以基于这个公共接口观察和交互
- 模块可以请求加载其他模块
- 支持循环依赖
- ES6模块默认在严格模式下执行
- ES6模块不共享全局命名空间
- 模块顶级this的值是undefined
- 模块中的var声明不会添加到window对象
- ES6模块是异步加载和执行的
-
公共导出系统
- ES6模块支持两种导出:命名导出和默认导出,这两种导出方式不会冲突,所以可以在一个文件同时出现
- 导出语句必须在模块顶级,不能嵌套在某个块中
- ES6模块系统会识别作为别名提供的default关键字
const foo = 'foo'; // 等同于export default fo export { foo as default };
-
第 27 章 工作者线程
-
js环境实际上是运行在托管操作系统中的虚拟环境,在浏览器中每打开一个页面,就会分配一个它自己的环境,这样,每个页面就会有自己的内存,事件循环,DOM等,每个页面就相当于一个沙盒,不会干扰其他页面。使用工作者线程,浏览器可以在原始页面环境之外再分配一个完全独立的二级子环境,这个子环境不能与依赖单线程交互的API互操作,但可以与父环境并行执行代码
-
工作者线程与线程
- 工作者线程是以实际线程实现的
- 工作者线程并行执行
- 工作者线程可以共享某些内存
- 工作者线程不一定在同一个进程里
- 创建工作者线程的开销更大
-
工作者线程的类型
- 专用工作者线程,通常简称为工作者线程,Webworker或worker,可以让脚本单独创建一个js线程,以执行委托的任务,只能被创建它的页面使用
- 共享工作者线程,与专用工作者线程的主要区别为共享工作者线程可以被多个不同的上下文使用,包括不同的页面,任何与创建共享工作者线程的脚本同源的脚本,都可以向共享工作者线程发送消息或从中接收消息
- 服务工作者线程,主要用途是拦截,重定向和修改页面发出的请求,充当网络请求的仲裁者的角色
-
WorkerGlobalScope
- 在工作者线程的内部,这里的全局对象是WorkerGlobalScope,通过self关键字暴露出来
- WorkerGlobalScope上的属性
- self对象上的方法
-
WorkerGlobalScope的子类
每种类型的工作者线程都使用了自己的特定的全局对象,这继承自WorkerGlobalScope- 专用工作者线程使用DedicatedWorkerGlobalScope - 共享工作者线程使用ShareWorkerGlobalScope - 服务工作者线程使用ServiceWorkerGlobalScope
-
专用工作者线程
-
工作者线程限制:工作者线程的脚本文件只能从与父页面相同的源加载,从其他源加载工作者线程的脚本文件会导致错误
-
worker对象的使用:worker()构造函数返回的Worker对象是与刚创建的专用工作者线程通信的连接点。它可用于在工作者线程和父上下文间传输信息,以及捕获专用工作者线程发出的事件
-
worker对象支持的事件处理属性
- postMessage():用于异步消息事件向工作者线程发送消息
- terminate():用于立即终止工作者线程,没有为工作者线程提供清理的机会,脚本会忽然停止
-
DedicatedWorkerGlobalScope在WorkerGlobalScope基础上增加了以下属性和方法
- name:可以提供给Worker构造函数的一个可选的字符串标识符
- postMessage():与worker.postMessage()对应的方法,用于从工作者线程内部向父上下文发送消息。
- close():与worker.terminate()对应的方法,用于立即终止工作者线程。没有为工作者线程提供清理的机会,脚本会突然停止。
- importScripts():用于向工作者线程中导入任意数量的脚本
-
专用工作者线程的生命周期:一般来说,专用工作者线程可以非正式区分为处于下列三个状态:初始化(initializing)、活动(active)和终止(terminated)。。这几个状态对其他上下文是不可见的。虽然Worker对象可能会存在于父上下文中,但也无法通过它确定工作者线程当前是处理初始化、活动还是终止状态。换句话说,与活动的专用工作者线程关联的Worker对象和与终止的专用工作者线程关联的Worker对象无法分别
-
worker()构造函数允许将可选的配置对象作为第二个参数,该配置对象支持下列属性
- name:可以在工作者线程中通过self.name读取到的字符串标识符
- type:表示加载脚本的运行方式,可以是classic或module,classic将脚本作为常规脚本来执行,module将脚本作为模块来执行
- credentials:在type为"module"时,指定如何获取与传输凭证数据相关的工作者线程模块脚本。值可以是"omit"、"same-orign"或"include"。这些选项与fetch()的凭证选项相同。在type为"classic"时,默认为"omit"
-
在js行内创建工作者线程
工作者线程需要基于脚本文件来创建,但这并不意味着该脚本必须是远程资源。专用工作者线程也可以通过Blob对象URL在行内脚本创建。这样可以更快速地初始化工作者线程,因为没有网络延迟
-
工作者线程中可以使用importScripts()方法通过编程方式加载和执行任意脚本,会加载脚本并按照加载顺序同步执行
- 该方法可以接收任意数量的脚本作为参数,浏览器下载他们的顺序没有限制,但执行会严格按照他们在参数列表的顺序进行
- 脚本加载收到常规CORS的限制,但是在工作者线程内部可以请求来自任何源的脚本
-
如果工作者线程脚本抛出了错误,该工作者线程沙盒可以阻止它打断父线程的执行,不过相应的错误时间仍然会冒泡到工作者线程的全局上下文
-
与专用工作者线程通信
- postMessage()
- MessageChannel()
- BroadcastChannel:同源脚本能够通过BroadcastChannel相互之间发送和接收消息。
-