
1.2.1 history.pushState
1.基本用法
history.pushState方法作为HTML5特性的一部分,目前被广泛使用。history.pushState用于无刷新增加历史栈记录,调用history.pushState方法可改变浏览器路径。
history.pushState方法需要3个参数:状态对象、标题(目前被忽略)和可选的URL。其语法格式为

当设置第三个参数URL时,可改变浏览器的URL,且不会刷新浏览器。

如果URL中包含Unicode字符,则浏览器也会将字符按UTF-8编码。

虽然在浏览器地址栏中显示的还是中文字符串,但实际上代码得到的是已经编码过的字符串。
除设置第三个参数为字符串外,在笔者测试的Chrome 77版本中同样支持传入URL对象。

history.pushState方法的第一个参数为需要传入的状态,状态的类型可以为可实施结构化拷贝算法的任意类型。在设置了第一个参数后,可通过history.state读取。

因为历史栈由浏览器统一管理,不属于某个具体页面,并不存在于页面的内存中,所以历史栈在刷新页面后不会丢失,栈中记录的各state对象也为持久化存储,在导航过程中也不会丢失。
history.pushState使用结构化拷贝算法进行序列化存储,会将拷贝后的结果记录在历史栈的记录中。结构化拷贝算法除了能拷贝基本类型,还能拷贝更多的对象类型。相比JSON的序列化,这样的序列化手段更为安全,如循环引用的对象,结构化序列的手段将会序列化成功,而JSON的序列化将会报错,原因在于结构化序列的手段保存了每一个访问过的对象的记录,遇到复制过的对象会进行跳过。对此感兴趣的读者可以参考学习lodash的cloneDeep方法。结构化拷贝算法要注意特殊场景,如果history.pushState的state对象中有dom节点、error对象、function函数等,则调用history.pushState方法会抛出异常,且对某些对象的特定属性,如regExp的lastIndex、object对象的setter和getter等,结构化拷贝的过程都会丢失。
结构化克隆算法是由HTML5规范定义的用于序列化复杂JavaScript对象的一个新算法。它比JSON更有能力,因为它支持包含循环图的对象的序列化——对象中包含循环引用。此外,在某些情况下,结构化克隆算法可能比JSON更高效。
注意,history.pushState的第一个参数state,在Firefox中有大小限制,超过640KB的对象将会抛出异常。history.pushState的第三个参数URL出于安全考虑,需要同源的URL,例如,当前浏览器的域名为https://www.github.com,若history.pushState的URL为https://stackoverflow.com,则浏览器会抛出Uncaught DOMException异常。
2.历史栈变化
history.pushState的调用会引起历史栈的变化,浏览器通常会维护一个用户访问过的历史栈,以便用户进行导航。用户通常通过单击浏览器的“前进”和“后退”按钮或者调用window.history.go等方法在历史栈中进行移动,可理解为如图1-1所示的虚线所表示的栈指针,不改变历史栈的内容,栈内的记录数量不会发生变化。

图1-1 history.pushState添加历史栈
而当调用history.pushState方法时,历史栈的内容会被修改,行为表现为添加历史栈的栈记录,同时也会改变指针指向。如图1-1所示,若当前的路径地址为/b,当前的栈指针也指向/b的位置,在调用history.pushState({a:3},null,'/c')方法后,则栈记录加1,栈指针也指向最新的栈记录位置。


同时,要注意,如果当前栈指针不在栈顶,如当前栈的数量为3,单击浏览器的“后退”按钮,使得栈指针指向栈底后,再次调用history.pushState({a:4},null,'/d')方法,不仅会改变栈指针指向,而且会更新栈的内容,如图1-2所示。

图1-2 非栈顶情况history.pushState更新栈的内容
history.pushState会在当前指针所指的栈记录后一个位置添加新的历史记录,并使之成为新的栈顶。

注意,这里减少了栈的记录数量,栈记录数从3变为2,地址/d成了新的栈顶。
如果不传入第三个参数URL,则浏览器的地址栏不会发生变化,但是加入一个历史栈,history.length就会发生相应的变化。
