1. 历史状态管理
能够触发浏览器历史状态栈的操作,也是vue-router的实现原理
1.1 Hash
通过#
(锚点)来实现页面内定位,不会触发页面刷新
每次改变Hash
值会在浏览器状态栈中添加一条记录并触发hashchange
事件
Hash
是浏览器行为,对服务器无效,也不会被包含进HTTP
请求
1.2 History
能够在不加载新页面的情况下改变浏览器URL
API
history.back()
hsitory.forward()
hsitory.go()
pushState
:在历史状态栈中添加一条记录
replaceState
:重写当前记录
// 示例
window.onload = function () {
// 阻止页面后退
pushState ()
window.addEventListener('popstate', pushState)
};
function pushState () {
const state = {
title: 'title',
url: '#'
}
window.history.pushState(state, 'title', '#')
}
2. 高级函数
2.1 惰性载入函数
解决调用时多次if
判断的性能流失问题
使用方法:首次调用函数后,使用判断结果覆盖原函数
function createText() {
// 伪代码
if (system === 'windows') {
if (broswer === 'chrome') {
createText = function () {
return 'windows chrome'
}
}
} else if (system === 'android') {
if (device === 'Samsung') {
createText = function () {
return 'android Samsung'
}
}
}
return createText()
}
2.2 Yielding Processes
解决循环造成的脚本长时间运行
当脚本的运行不是必须同步执行且数据不是必须顺序完成时,可以使用数组分块技术进行处理
// 可以根据需求修改chunk函数的入参,实现更复杂的处理
function chunk(dataList, processFn, target) {
setTimeout(() => {
const data = dataList.shift()
processFn.call(target, data)
if (dataList.length) {
chunk(dataList, processFn, target)
}
}, 100)
}
2.3 防抖
函数连续触发时,只在停止触发后执行一次
function debounce(fn, target = this, interval = 100) {
clearTimeout(fn.tid)
fn.tid = setTimeout(() => {
fn.call(target)
}, interval)
}
2.4 节流
函数连续触发时,只在规定间隔后触发
function throttle(fn, target = this, interval = 100) {
if (!fn.isLock) {
fn.call(target)
fn.isLock = true
setTimeout(() => {
fn.isLock = false
}, interval)
}
}
3. 离线存储
3.1 cookie
3.1.1 cookie构成
name | value | domain | path | Expires/Max-age | secure |
---|---|---|---|---|---|
名,不区分大小写 | 值 | 指定域下有效 | 指定路径下向服务器发送Cookie | 失效时间,格式是GMT,需要使用new Date().toUTCString()设置才有效 | 是否只在HTTPS下发送Cookie |
除了name
和value
,其他参数都是用于指示浏览器在何时将cookie
发送至服务器,而这些参数并不会作为cookie
信息的一部分
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value; expires=Mon, 13 Apr 2020 03:26:15 GMT; domain=.baidu.com; path=/; secure
Other-header; other-header-value
限制:因浏览器而异,通常是每个域名50个,单个大小4KB
3.1.2 cookie操作
使用domain.cookie
来读写cookie
获取值时,domain.cookie
会返回所有的cookie
信息并使用;
分割
设置值时,domain.cookie
会设置一个新的cookie
信息并添加到cookie
集合中,如果设置的cookie
的name
属性已存在,则覆盖相同name
的cookie
由于读写的不便利,常常会使用封装函数来简化操作
const CookieUtil = {
get: function (name) {
const cookieName = encodeURIComponent(name) + '='
const cookieStart = document.cookie.indexOf(cookieName)
let cookieValue = null
if (cookieStart > -1) {
let cookieEnd = document.cookie.indexOf(';', cookieStart)
if (cookieEnd === -1) {
cookieEnd = document.cookie.length
}
cookieValue = decodeURIComponent(
document.cookie.substring(cookieStart + cookieName.length, cookieEnd)
)
}
return cookieValue
},
set: function (name, value, expires, path, domain, secure) {
let cookieText = encodeURIComponent(name) + '=' + encodeURIComponent(value)
if (expires instanceof Date) {
cookieText += '; expires=' + expires.toUTCString()
}
if (path) {
cookieText += '; path=' + path
}
if (domain) {
cookieText += '; domain=' + domain
}
if (secure) {
cookieText += '; secure'
}
document.cookie = cookieText
},
delete: function (name, path, domain, secure) {
this.set(name, '', new Date(0), path, domain, secure)
}
}
3.1.3 子cookie
在cookie
中存放name1=value1; name2=value2
的值,用于规避浏览器最大cookie
数量的限制。
可定义一系列方法来简化操作,不常用因此不深入讨论
3.1.4 cookie设置的坑
作用域: 当前路径及子路径可以访问,同级父级无法访问
设置: domain
和path
只能设置当前路径中已存在的,其余值设置不上
读写: 跨域请求服务器设置cookie
时,该cookie
存在于服务器所在域名下,chrome
可以看见无法读写,edge
无法看见cookie
,切换至同一域名后都可以看见并读写
请求头: 跨域请求服务器通过请求头Set-Cookie
设置cookie
时,chrome
会隐藏这个字段
有效期:max-age
以秒为单位,不设置默认当前会话有效(同sessionStorage
),设置0删除当前cookie
溢出:当单个cookie
的大小设置后超过4K
时,此次操作无效,且不会发生任何事情(无报错)
匹配规则: 匹配cookie
和请求的域名以及路径,尝试携带所有符合的cookie
,当请求头超过大小后会报错
3.2 storage
3.2.1 sessionStorage和localStorage
在来源
相同的情况下(不跨域,页面来自同一域名,协议,端口),所有页面共享数据
sessionStorage
在关闭浏览器后消失(关闭会话框再恢复不会消失,不同会话框即使同一页面也不共享)
localStorage
不清除始终存在
限制: 因浏览器而异,通常是每个来源
2.5MB - 10MB之间
3.2.2 IndexedDB
详细教程以及API这里
主要操作四个对象:
IDBRequest
对象:操作请求对象,用于新增,打开,删除数据库IDBDatabase
对象:数据库对象,用于对数据库进行操作,如增删表,创建事务等IDBTransaction
对象:事务对象,用于操作数据库事务,读写必经之路IDBObjectStore
对象:仓库对象,用于具体实现对数据库表的各种操作
次要操作对象:
IDBIndex
对象: 数据库的索引对象,用于通过除主键以外其他键,建立索引获取值IDBCursor
对象:指针对象,用于遍历IDBObjectStore
和IDBIndex
中的数据IDBKeyRange
对象:主键范围对象,用于设置一个主键的搜索范围,用于定位查找数据
let myDB
// 新建数据库,返回 IDBRequest 对象
const requestBD = window.indexedDB.open('myDB', 1)
requestBD.onupgradeneeded = function (event) {
// 返回 IDBDatabase 对象
myDB = event.target.result
if (!myDB.objectStoreNames.contains('person')) {
// 使用 IDBDatabase 对象添加person表,返回 IDBObjectStore 对象
const objectStore = myDB.createObjectStore('person',
{
keyPath: 'id' })
// 使用 IDBObjectStore 对象添加 person 表的索引
objectStore.createIndex('name', 'name', {
unique: false })
objectStore.createIndex('email', 'email', {
unique: true })
}
read()
readByIndex()
}
function read() {
// 使用 IDBKeyRange 对象设置主键范围
const range = IDBKeyRange.bound(1, 5, true, true);
// 使用 IDBDatabase 对象获取 IDBTransaction 对象
const request = myDB.transaction(['person'], 'readonly')
// 使用 IDBTransaction 对象获取 IDBObjectStore对象
.objectStore('person')
// 使用 IDBObjectStore 对象获取主键为 1 < key < 5 的数据
.get(range)
request.onerror = function () {
console.log('事务失败')
}
request.onsuccess = function (event) {
if (request.result) {
console.log('Name:' + request.result.name)
console.log('age:' + request.result.age)
console.log('email:' + request.result.email)
}
}
}
function readByIndex() {
const store = myDB.transaction(['person'],'readonly')
.objectStore('person')
// 使用 IDBObjectStore 对象获取 IDBIndex 对象
const request = store.index('name')
// 使用 IDBIndex 对象获取索引 name='李四' 的数据
.get('李四')
request.onsuccess = function (event) {
const result = event.target.result
if (result) {
console.log(result)
}
}
}
function readAll() {
const objectStore = myDB.transaction('person')
.objectStore('person');
// 使用 IDBObjectStore 对象获取 IDBCursor 对象
objectStore.openCursor().onsuccess = function (event) {
const cursor = event.target.result;
if (cursor) {
console.log('Id: ' + cursor.key);
console.log('Name: ' + cursor.value.name);
console.log('Age: ' + cursor.value.age);
console.log('Email: ' + cursor.value.email);
cursor.continue();
} else {
console.log('没有更多数据了!');
}
};
}
4. 维护
4.1 可维护性
4.1.1 代码约定
- 可读性: 代码缩进以及注释
- 变量和函数命名: 使用具有含义的命名
- 变量类型透明: 初始化变量或注释
4.1.2 松散耦合
- 解耦
HTML/JS
: 避免直接在JS
中嵌入插入HTML
内容的语句 - 解耦
CSS/JS
: 避免使用JS
直接修改CSS
,改为修改元素的类 - 解耦应用逻辑/事件处理: 避免在事件处理程序中直接嵌套应用逻辑,改为抽离应用逻辑,将(事件监听,逻辑触发)与逻辑本身分离
4.1.3 编程实践
- 尊重对象所有权:避免修改不是你创建的对象,不为实例或原型添加属性或方法,且不重写已有方法
- 避免全局变量: 最多创建一个全局变量,其余变量或库挂载在全局中,使用命名空间进行维护
- 避免与
null
进行比较:使用数据本来的类型或逻辑进行判断,使用instanceof
或typeof
代替 - 使用常量: 抽离可重复使用的值、用户界面字符串、URL、任何可能会更改的值
4.2 性能
4.2.1 变量访问
-
作用域查找 : 由于作用域链,局部变量访问速度始终快于全局。当有重复全局变量访问需求时,将全局变量提取成局部变量来提高访问效率
-
属性查找:变量和数组的访问是O(1),对象属性的访问是O(n)。当有重复对象属性访问需求时,将对象属性提取成局部变量来提高访问效率
4.2.2 循环优化
基本优化步骤:
- 减值迭代: 将
i
设置为最大值,使用i--
来迭代会更加高效 - 简化终止条件: 终止条件计算时,避免O(n)的访问操作
如:i < arr.length
- 简化循环体:优化循环内部逻辑
- 使用后测试循环:
do-while
比for
或者while
少一次终止条件判断
展开循环:
直接对每次循环调用循环体处理函数process()
会更加高效,这样会消除建立循环和处理终止条件的额外开销。当迭代次数无法确定时,可以使用一个叫做Deff装置
的技术来进行优化。
注:非大数据处理情况下用会得不偿失
let iterations = Math.floor(values.length / 8)
let leftover = values.length % 8
let index = 0
if (leftover > 0) {
do {
process(values[index++])
} while (--leftover > 0)
}
do {
process(values[index++])
process(values[index++])
process(values[index++])
process(values[index++])
process(values[index++])
process(values[index++])
process(values[index++])
process(values[index++])
} while (--iterations > 0)
4.2.2 语句优化
单条语句完成多个操作要比多条语句完成相同操作要快
- 变量声明
// 慢
let arr = [1,2]
let index = 0
let length = 2
//快
let arr = [1,2],
index = 0,
length = 2
- 迭代值使用
// 慢
arr[index] = 0
index ++
// 快
arr[index++] = 0
- 声明字面量
// 慢
let arr = new Array(3)
arr[0] = 0
arr[1] = 1
arr[2] = 2
let obj= new Object()
obj.name = 'soraka'
obj.age = 18
obj.introduce = function () {
console.log(this.name, this.age)
}
// 快
let arr = [0,1,2]
let obj = {
name: 'soraka',
age: 18,
introduce: function () {
console.log(this.name, this.age)
}
}
4.2.3 Dom优化
- 最小化更新: 尽量减少在
DOM
操作中引起的浏览器页面更新次数
// 慢
let list = document.getElementById('myList'),
item
for (let i = 0; i < 10; i++) {
item = document.createElement('li')
list.appendChild(item)
item.appendChild(document.createTextNode('Item' + i))
}
// 快
let list = document.getElementById('myList'),
fragment = document.createDocumentFragment(),
item
for (let i = 0; i < 10; i++) {
item = document.createElement('li')
fragment.appendChild(item)
item.appendChild(document.createTextNode('Item' + i))
}
list.appendChild(fragment)
- 使用
innerHTML
:对于大的DOM
更改,innerHTML
比createElement()
之类的DOM
方法要快的多
// 更快
let list = document.getElementById('myList'),
html = ''
for (let i = 0; i < 10; i++) {
html += '<li>Item${i}</li>'
}
list.appendChild(html)
- 使用事件委托
- 注意
HTMLCollection
:任何访问HTMLCollection
的操作都是在DOM
文档上进行一个昂贵的查询,最小化HTMLCollection
访问次数能极大的优化性能