题目:提升页面性能的方法有哪些?
1.代码合并、压缩,减少HTTP请求
如CSS的雪碧图,Base64编码的图片,webpack的代码压缩
2.非核心代码的异步加载
动态创建script、script标签的defer属性和async属性
3.利用浏览器缓存
强缓存(expires和max-age,sessionStorage、localStorage、IndexedDB )、协商缓存(Last-Modified和Etag)
4.使用CDN
5.预解析DNS
在一些高级的浏览器中,a标签是默认开启域名预解析的,如果是https协议,则默认关闭;
上述预解析DNS的第一行代码就是强制打开a标签的DNS预解析
异步加载
异步加载的方式
1.动态脚本加载
就是动态创建script节点
const script = document.createElement("script");
script.onload = function(){
console.log("loaded...");
}
script.src = "http://A.com";
cocument.script.appendChild(script);
2.defer
defer是在浏览器解析执行完才会执行,如果有多个脚本,则按照加载的顺序依次执行
<script charset="utf-8" defer src="http://A.com"></script>
<script charset="utf-8" defer src="http://B.com"></script>
<script charset="utf-8" defer src="http://C.com"></script>
<script charset="utf-8" defer src="http://D.com"></script>
3.async
async是在加载完后立即执行,如果有多个,则执行顺序与加载顺序无关
<script charset="utf-8" async src="http://A.com"></script>
<script charset="utf-8" async src="http://B.com"></script>
<script charset="utf-8" async src="http://C.com"></script>
<script charset="utf-8" async src="http://D.com"></script>
异步加载的区别
1.动态创建script标签的方式是在append到父元素上时会加载执行
2.defer是在HTML解析完之后才会执行,如果是多个,按照加载的顺序依次执行
3.async是在加载完之后立即执行,如果是多个,执行顺序和加载顺序无关
浏览器缓存分类
缓存说的就是这个资源文件在浏览器中存在的副本
1.强缓存
在一段时间内,不直接请求,直接拿过来使用
// 请求一个文件的时候,响应头上会携带两个东西(一个也可以)
//过期时间 服务器的绝对时间 判断客户端的时间是不是服务器给出的时间,可能会有偏差
Expires Expires:Thu,21 Jan 2017 23:39:02 GMT
//相对时间 3600s内都不会发送请求该资源,而是直接从缓存中拿
//若两个都有,则以这个为准
Cache-Control Cache-Control:max-age=3600
2.localStorage和sessionStorage
存储数据大小一般都是:5MB
都保存在客户端,不与服务器进行交互通信
只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理
3.IndexedDB
现有的浏览器数据储存方案,都不适合储存大量数据:Cookie 的大小不超过4KB,且每次请求都会发送回服务器;LocalStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引。所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景
IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库
特点:
(1)键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
(2)异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
(3)支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
(4)同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
(5)储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
(6)支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
基本概念
(1)数据库
数据库是一系列相关数据的容器。每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。
IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。
(2)对象仓库
每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表格。
(3)数据记录
对象仓库保存的是数据记录。每条记录类似于关系型数据库的行,但是只有主键和数据体两部分。主键用来建立默认的索引,必须是不同的,否则会报错。主键可以是数据记录里面的一个属性,也可以指定为一个递增的整数编号。
上面的对象中,id
属性可以当作主键。
数据体可以是任意数据类型,不限于对象。
(4)索引
为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。
(5)事务
数据记录的读写和删改,都要通过事务完成。事务对象提供error
、abort
和complete
三个事件,用来监听操作结果。
操作流程
IndexedDB 数据库的各种操作,一般是按照下面的流程进行的。这个部分只给出简单的代码示例,用于快速上手,详细的各个对象的 API 请看这里。
1.打开数据库
使用 IndexedDB 的第一步是打开数据库,使用indexedDB.open()
方法。
这个方法接受两个参数,第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本。如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1
。
indexedDB.open()
方法返回一个 IDBRequest 对象。这个对象通过三种事件error
、success
、upgradeneeded
,处理打开数据库的操作结果。
(1)error 事件
error
事件表示打开数据库失败。
(2)success 事件
success
事件表示成功打开数据库。
这时,通过request
对象的result
属性拿到数据库对象。
(3)upgradeneeded 事件
如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded
。
这时通过事件对象的target.result
属性,拿到数据库实例。
新建数据库
新建数据库与打开数据库是同一个操作。
如果指定的数据库不存在,就会新建。不同之处在于,后续的操作主要在upgradeneeded
事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。
通常,新建数据库以后,第一件事是新建对象仓库(即新建表)。
上面代码中,数据库新建成功以后,新增一张叫做person
的表格,主键是id
。
更好的写法是先判断一下,这张表格是否存在,如果不存在再新建。
主键(key)是默认建立索引的属性。比如,数据记录是{ id: 1, name: '张三' }
,那么id
属性可以作为主键。主键也可以指定为下一层对象的属性,比如{ foo: { bar: 'baz' } }
的foo.bar
也可以指定为主键。
如果数据记录里面没有合适作为主键的属性,那么可以让 IndexedDB 自动生成主键。
上面代码中,指定主键为一个递增的整数。
新建对象仓库以后,下一步可以新建索引。
上面代码中,IDBObject.createIndex()
的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)。
新增数据
新增数据指的是向对象仓库写入数据记录。这需要通过事务完成。
上面代码中,写入数据需要新建一个事务。新建时必须指定表格名称和操作模式("只读"或"读写")。
新建事务以后,通过IDBTransaction.objectStore(name)
方法,拿到 IDBObjectStore 对象,再通过表格对象的add()
方法,向表格写入一条记录。
写入操作是一个异步操作,通过监听连接对象的success
事件和error
事件,了解是否写入成功。
读取数据
读取数据也是通过事务完成。
上面代码中,objectStore.get()
方法用于读取数据,参数是主键的值。
遍历数据
遍历数据表格的所有记录,要使用指针对象 IDBCursor。
上面代码中,新建指针对象的openCursor()
方法是一个异步操作,所以要监听success
事件。
更新数据
更新数据要使用IDBObject.put()
方法。
上面代码中,put()
方法自动更新了主键为1
的记录。
删除数据
IDBObjectStore.delete()
方法用于删除记录。
使用索引
索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)。
假定新建表格的时候,对name
字段建立了索引。
现在,就可以从name
找到对应的数据记录了。
2.协商缓存
浏览器发现本地有副本,但不确定用不用它,向服务器询问
//上次修改的时间 上次请求该资源的服务器的时间 可能会内容没有改变。这就有消耗
Last-Modified If-Modified-Since Last-Modified:Wed,26,Jan 2017 00:35:11 GMT
//hash值 服务器给资源的时候会给Etag,当过了强缓存的时间,浏览器向服务器请求该缓存资源是否可用的时候,key为If-None-Match,value为Etag的值
Etag If-None-Match