让我们再聊聊浏览器资源加载优化

1.简介

几乎每一个前端程序员都知道应该把 script 标签放在页面底部。关于这个经典的论述可以追溯到 Nicholas 的 High Performance Javasript 这本书的第一章 Loading and Execution 中,他之所以建议这么做是因为

简而言之,如果浏览器加载并执行脚本,会引起页面的渲染被暂停,甚至还会阻塞其他资源 (比如图片) 的加载。为了更快的给用户呈现网页内容,更好的用户体验,应该把脚本放在页面底部,使之最后加载。

我们经常谈论的一些页面优化技巧,比如上面所说的总是把脚本放在页面的底部,压缩合并样式或者脚本文件等,时至今日已不再是最佳的解决方案,甚至事与愿违,转化为性能的毒药。这篇文章所要聊的,便是展示某些不被人关注的浏览器特性或者技巧,来继续完成资源加载性能优化的任务

一. Preloader

什么是 Preloader

<head>
    <link rel="stylesheet" type="text/css" href="">
    <script type="text/javascript"></script>
</head>
<body>
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <script type="text/javascript"></script>
    <script type="text/javascript"></script>
    <script type="text/javascript"></script>
</body>

这类页面的特点是,一个外链脚本置于页面头部,三个外链脚本置于页面的底部,并且是故意跟随在一系列 img 之后,在 Chrome 中页面加载的网络请求瀑布图如下

在这里插入图片描述
值得注意的是,虽然脚本放置在图片之后,但加载仍先于图片。为什么会出现这样的情况?为什么故意置后资源能够提前得到加载?
虽然浏览器引擎的实现不同,但原理都十分的近似。不同浏览器的制造厂商们 (vendor) 非常清楚浏览器的瓶颈在哪 (比如 network, javascript evaluate, reflow, repaint)。针对这些问题,浏览器也在不断的进化,所以我们才能看到更快的脚本引擎,调用 GPU 的渲染等一推陈出新的优化技术和方案

同样在资源加载上,早在 IE8 开始,一种叫做 lookahead pre-parser(在 Chrome 中称为 preloader) 的机制就已经开始在不同浏览器中兴起。IE8 相对于之前 IE 版本的提升除了将每台 host 最高并行下载的资源数从 2 提升至 6,并且能够允许并行下载脚本文件之外,最后就是这个 lookahead pre-parser 机制

但我还是没有详述这是一个什么样的机制,不着急,首先看看与 IE7 的对比
以上面的页面为例,我们看看 IE7 下的瀑布图
在这里插入图片描述
底部的脚本并没有提前被加载,并且因为由于单个域名最高并行下载数 2 的限制,资源总是两个两个很整齐的错开并行下载

但在 IE8 下,很明显底部脚本又被提前
在这里插入图片描述
并没有统一的标准规定这套机制应具备何种功能已经如何实现。但你可以大致这么理解:浏览器通常会准备两个页面解析器 parser,一个 (main parser) 用于正常的页面解析,而另一个 (preloader) 则试图去文档中搜寻更多需要加载的资源,但这里的资源通常仅限于外链的 js、stylesheet、image;不包括 audio、video 等。并且动态插入页面的资源无效

但细节方面却值得注意:

  • 如关于 preloader 的触发时机,并非与解析页面同时开始,而通常是在加载某个 head 中的外链脚本阻塞了 main parser
    的情况下才启动;
  • 也不是所有浏览器的 preloader 会把图片列为预加载的资源,可能它认为图片加载过于耗费带宽而不把它列为预加载资源之列;
  • preloader 也并非最优,在某些浏览器中它会阻塞 body 的解析。因为有的浏览器将页面文档拆分为 head 和 body
    两部分进行解析,在 head 没有解析完之前,body 不会被解析。一旦在解析 head 的过程中触发了 preloader,这无疑会导致
    head 的解析时间过长

Preloader 在响应式设计中的问题

filamentgroup 有一种著名的响应式设计的图片解决方案 Responsive Design Images

<html>
<head>
    <title></title>
    <script type="text/javascript" src="./responsive-images.js"></script>
</head>
<body>
    <img src="./running.jpg?medium=_imgs/running.medium.jpg&large=_imgs/running.large.jpg">
</body>
</html>

它的工作原理是,当 responsive-images.js 加载完成时,它会检测当前显示器的尺寸,并且设置一个 cookie 来标记当前尺寸。同时你需要在服务器端准备一个.htaccess 文件,接下来当你请求图片时,.htaccess 中的配置会检测随图片请求异同发送的 Cookie 是被设置成 medium 还是 large, 这样也就保证根据显示器的尺寸来加载对于的图片大小

很明显这个方案成功的前提是,js 执行先于发出图片请求。但在 Chrome 下打开,你会发现执行顺序是这样:
在这里插入图片描述
responsive-images.js 和图片几乎是同一时间发出的请求。结果是第一次打开页面给出的是默认小图,如果你再次刷新页面,因为 Cookie 才设置成功,服务器返回的是大图

严格意义上来说在某些浏览器中这不一定是 preloader 引起的问题,但 preloader 引起的问题类似:插入脚本的顺序和位置或许是开发者有意而为之的,但 preloader 的这种“聪明”却可能违背开发者的意图,造成偏差

如果你觉得上一个例子还不够说明问题的话,最后请考虑使用 picture (或者 @srcset) 元素的情况

<picture>
    <source src="med.jpg" media="(min-width: 40em)" />
    <source src="sm.jpg"/>
    <img src="fallback.jpg" alt="" />
</picture>

在 preloader 搜寻到该元素并且试图去下载该资源时,它应该怎么办?一个正常的 paser 应该是在解析该元素时根据当时页面的渲染布局去下载,而当时这类工作不一定已经完成,preloader 只是提前找到了该元素。退一步来说,即使不考虑页面渲染的情况,假设 preloader 在这种情形下会触发一种默认加载策略,那应该是"mobile first"还是"desktop first"?默认应该加载高清还是低清照片?

二. JS Loader

理想是丰满的,现实是骨感的。出于种种的原因,我们几乎从不直接在页面上插入 js 脚本,而是使用第三方的加载器,比如 seajs 或者 requirejs。关于使用加载器和模块化开发的优势在这里不再赘述。但我想回到原点,讨论应该如何利用加载器,就从 seajs 与 requirejs 的不同聊起

在开始之前我已经假设你对 requirejs 与 seajs 语法已经基本熟悉了,如果还没有,请移步这里

CMD 标准: https://github.com/cmdjs/specification/blob/master/draft/module.md
AMD 标准:
https://github.com/amdjs/amdjs-api/blob/master/AMD.md

BTW: 如果你还是习惯在部署上线前把所有 js 文件合并打包成一个文件,那么 seajs 和 requirejs 其实对你来说并无区别
seajs 与 requirejs 在模块的加载方面是没有差异的,无论是 requirejs 在定义模块时定义的依赖模块,还是 seajs 在 factory 函数中 require 的依赖模块,在会在加载当前模块时被载入,异步,并且顺序不可控。差异在于 factory 函数执行的时机

执行差异

为了增强对比,我们在定义依赖模块的时候,故意让它们的 factory 函数要执行相当长的时间,比如 1 秒:

/ dep_A.js 定义如下,dep_B、dep_C 定义同理 

define(function(require, exports, module) {

    (function(second) {
        var start = +new Date();
        while (start + second * 1000 > +new Date()) {}
    })(window.EXE_TIME);

    // window.EXE_TIME = 1;此处会连续执行 1s

    exports.foo = function() {
        console.log("A");
    }
})

为了增强对比,设置了三组进行对照试验,分别是

//require.js:
require(["dep_A", "dep_B", "dep_C"], function(A, B, C) {

});

//sea.js:
define(function(require, exports, module) {

    var mod_A = require("dep_A");
    var mod_B = require("dep_B");
    var mod_C = require("dep_C");
});

//sea.js(定义依赖但并不 require):
define(["dep_A", "dep_B", "dep_C"], function(require, exports, module){

}

接下来我们看看代码执行的瀑布图
1.require.js:在加载完依赖模块之后立即执行了该模块的 factory 函数
在这里插入图片描述
在这里插入图片描述

  • sea.js: 下面两张图应该放在一起比较。两处代码都同时加载了依赖模块,但因为没有 require
    的关系,第三张图中没有像第二张图那样执行耗时的 factory 函数。可见 seajs 执行的原则正如 CMD 标准中所述
    Execution must be lazy
  • 我想进一步表达的是,无论 requirejs 和 seajs,通常来说大部分的逻辑代码都会放在模块的 factory 函数中,所以
    factory 函数执行的代价是非常大的。但上图也同样告诉我们模块的 define,甚至模块文件的 Evaluate 代价非常小,与
    factory 函数无关。所以我们是不是应该尽可能的避免执行 factory 函数,或者等到我们需要的指定功能的时候才执行对应的
    factory 函数?比如:
document.body.onclick = function () {
    require(some_kind_of_module);
}
  • 这是非常实际的问题,比如爱奇艺一个视频播放的页面,我们有没有必要在第一屏加载页面的时候就加载登陆注册,或者评论,或者分享功能呢?因为有非常大的可能用户只是来这里看这个视频,直至看完视频它都不会用到登陆注册功能,也不会去分享这个视频等。加载这些功能不仅仅对浏览器是一个负担,还有可能调用后台的接口,这样的性能消耗是非常可观的。
  • 我们可以把这样称之为"懒执行"。虽然 seajs 并非有意实现如上所说的“懒执行”(它只是在尽可能遵循 CommonJS 标准靠近)。但“懒执行”确实能够有助于提升一部分性能
    但也有人会对此产生顾虑
    记得玉伯转过的一个帖子: SeaJS 与 RequireJS 最大的区别。我们看看其中反对这么做的人的观点

我个人感觉 requirejs 更科学,所有依赖的模块要先执行好。如果 A 模块依赖 B。当执行 A 中的某个操 doSomething()
后,再去依赖执行 B 模块 require(‘B’); 如果 B 模块出错了,doSomething 的操作如何回滚? 很多语言中的
import, include, useing 都是先将导入的类或者模块执行好。如果被导入的模块都有问题,有错误,执行当前模块有何意义?

而依赖 dependencies
是工厂的原材料,在工厂进行生产的时候,是先把原材料一次性都在它自己的工厂里加工好,还是把原材料的工厂搬到当前的 factory
来什么时候需要,什么时候加工,哪个整体时间效率更高?

首先回答第一个问题
第一个问题的题设并不完全正确,“依赖”和“执行”的概念比较模糊。编程语言执行通常分为两个阶段,编译 (compilation) 和运行 (runtime)。对于静态语言 (比如 C/C++) 来说,在编译时如果出现错误,那可能之前的编译都视为无效,的确会出现描述中需要回滚或者重新编译的问题。但对于动态语言或者脚本语言,大部分执行都处在运行时阶段或者解释器中:假设我使用Nodejs 或者Python 写了一段服务器运行脚本,在持续运行了一段时间之后因为某项需求要加载某个(依赖) 模块,同时也因为这个模块导致服务端挂了——我认为这时并不存在回滚的问题。在加载依赖模块之前当前的模块的大部分功能已经成功运行了。
再回答第二个问题
对于“工厂”和“原材料”的比喻不够恰当。难道依赖模块没有加载完毕当前模块就无法工作吗?requirejs 的确是这样的,从上面的截图可以看出,依赖模块总是先于当前模块加载和执行完毕。但我们考虑一下基于 CommonJS 标准的 Nodejs 的语法,使用 require 函数加载依赖模块可以在页面的任何位置,可以只是在需要的时候。也就是说当前模块不必在依赖模块加载完毕后才执行
你可能会问,为什么要拿 AMD 标准与 CommonJS 标准比较,而不是 CMD 标准?
玉伯在 CommonJS 是什么这篇文章中已经告诉了我们 CMD 某种程度上遵循的就是 CommonJS 标准

从上面可以看出,Sea.js 的初衷是为了让 CommonJS Modules/1.1
的模块能运行在浏览器端,但由于浏览器和服务器的实质差异,实际上这个梦无法完全达成,也没有必要去达成。

更好的一种方式是,Sea.js 专注于 Web 浏览器端,CommonJS
则专注于服务器端,但两者有共通的部分。对于需要在两端都可以跑的模块,可以 有便捷的方案来快速迁移。

CommonJS 当然是一个理想的标准,但至少现阶段对浏览器来说还不够友好,所以才会出现 AMD 与 CMD,其实他们都是在做同一件事,就是致力于前端代码更友好的模块化。所以个人认为依赖模块的加载和执行在不同标准下实现不同,可以理解为在用不同的方式在完成同一个目标, 并不是一件太值得过于纠结的事。

懒加载

其实我们可以走的更远,对于非必须模块不仅仅可以延迟它的执行,甚至可以延迟它的加载

但问题是我们如何决定一个模块是必须还是非必须呢,最恰当莫过取决于用户使用这个模块的概率有多少。Faceboook 早在 09 年的时候就已经注意到这个问题: Frontend Performance Engineering in Facebook : Velocity 2009 ,只不过他们是以样式碎片来引出这个问题。

假设我们需要在页面上加入 A、B、C 三个功能,意味着我们需要引入 A、B、C 对应的 html 片段和样式碎片 (暂不考虑 js),并且最终把三个功能样式碎片在上线前压缩到同一个文件中。但可能过了相当长时间,我们移除了 A 功能,但这个时候大概不会有人记得也把关于 A 功能的样式从上线样式中移除。久而久之冗余的代码会变得越来越多。Facebook 引入了一套静态资源管理方案 (Static Resource Management) 来解决这个问题:
在这里插入图片描述
具体来说是将样式的“声明”(Declaration) 和请求 (Delivery) 请求,并且是否请求一个样式由是否拥有该功能的 html 片段决定。
当然同时也考虑也会适当的合并样式片段,但这完全是基于使用算法对用户使用模块情况进行分析,挑选出使用频率比较高的模块进行拼合
在这里插入图片描述
这一套系统不仅仅是对样式碎片,对 js,对图片 sprites 的拼合同样有效
你会不会觉得我上面说的懒加载还是离自己太远了? 但然不是,你去看看现在的人人网个人主页看看
在这里插入图片描述
如果你在点击图中标注的“与我相关”、“相册”、“分享”按钮并观察 Chrome 的 Timeline 工具,那么都是在点击之后才加载对应的模块
在这里插入图片描述

三. Delay Execution

利用浏览器缓存

  • 脚本最致命的不是加载,而是执行。因为何时加载毕竟是可控的,甚至可以是异步的,比如通过调整外链的位置,动态的创建脚本。但一旦脚本加载完成,它就会被立即执行
    (Evaluate Script),页面的渲染也就随之停止,甚至导致在低端浏览器上假死。
  • 更加充分的理由是,大部分的页面不是 Single Page
    Application,不需要依靠脚本来初始化页面。服务器返回的页面是立即可用的,可以想象我们初始化脚本的时间都花在用户事件的绑定,页面信息的丰满
    (用户信息,个性推荐)。 Steve Souders 发现在 Alexa 上排名前十的美国网站上的 js 代码,只有 29% 在
    window.onload 事件之前被调用,其他的 71% 的代码与页面的渲染无关。
  • Steve Souders 的 ControlJS 是我认为一直被忽视的一个加载器,它与 Labjs 一样能够控制的脚本的异步加载,甚至
    (包括行内脚本,但不完美)
    延迟执行。它延迟执行脚本的思路非常简单:既然只要在页面上插入脚本就会导致脚本的执行,那么在需要执行的时候才把脚本插入进页面。但这样一来脚本的加载也被延迟了?不,我们会通过其他元素来提前加载脚本,比如
    img 或者是 object 标签,或者是非法的 mine type 的 script
    标签。这样当真正的脚本被插入页面时,只会从缓存中读取。而不会发出新的请求。
  • Stoyan Stefanov 在它的文章 Preload CSS/JavaScript without execution
    中详细描述了这个技巧, 如果判断浏览器是 IE 就是用 image 标签,如果是其他浏览器,则使用 object 元素
window.onload = function () {

    var i = 0,
        max = 0,
        o = null,

        preload = [
            // list of stuff to preload    
        ],

        isIE = navigator.appName.indexOf('Microsoft') === 0;

    for (i = 0, max = preload.length; i < max; i += 1) {

        if (isIE) {
            new Image().src = preload[i];
            continue;
        }
        o = document.createElement('object');
        o.data = preload[i];

        // IE stuff, otherwise 0x0 is OK
        //o.width = 1;
        //o.height = 1;
        //o.style.visibility = "hidden";
        //o.type = "text/plain"; // IE 
        o.width  = 0;
        o.height = 0;


        // only FF appends to the head
        // all others require body
        document.body.appendChild(o);
    }

};

同时它还列举了其他的一些尝试,但并非对所有的浏览器都有效,比如

  • 使用 元素加载 script,这么做在 Chrome
    中的风险是,在当前页有效,但是在以后打开需要使用该脚本的页面会无视该文件为缓存
  • 改变 script 标签外链的 type 值,比如改为 text/cache 来阻止脚本的执行。这么做会导致在某些浏览器 (比如
    FF3.6) 中压根连请求都不会发出

type=prefetch

延迟执行并非仅仅作为当前页面的优化方案,还可以为用户可能打开的页面提前缓存资源,如果你对这两种类型的 link 元素熟悉的话

  • : subresource 类型用于加载当前页面将使用 (但还未使用) 的资源 (预先载入缓存中),拥有较高优先级
  • : prefetch 类型用于加载用户将会打开页面中使用到的资源,但优先级较低,也就意味着浏览器不做保证它能够加载到你指定的资源。

    那么上一节延迟执行的方案就可以作为 subresource 与 prefeth 的回滚方案。同时还有其他的类型

  • : dns-prefetch 类型用于提前 dns 解析和缓存域名主机信息,以确保将来再请求同域名的资源时能够节省 dns 查找时间,比如我们可以看到淘宝首页就使用了这个类型的标签

在这里插入图片描述

: prerender 类型就比较霸道了,它告诉浏览器打开一个新的标签页 (但不可见) 来渲染指定页面,比如这个页面: ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020052716140713.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzODg5ODQ4,size_16,color_FFFFFF,t_70) 这也就意味着如果用户真的访问到该页面时,就会有“秒开”的用户体验。

但现实并非那么美好,首先你如何能预测用户打开的页面呢,这个功能更适合阅读或者论坛类型的网站,因为用户有很大的概率会往下翻页;要注意提前的渲染页面的网络请求和优先级和 GPU 使用权限优先级都比其他页面的要低,浏览器对提前渲染页面类型也有一定的要求,具体可以参考这里

利用 LocalStorage

聊如何用它来解决我们遇到的问题之前,个人觉得首先应该聊聊它的优势和劣势
Chris Heilmann 在文章 There is no simple solution for local storage 中指出了一些常见的 LS 劣势,比如同步时可能会阻塞页面的渲染、I/O 操作会引起不确定的延时、持久化机制会导致冗余的数据等。虽然 Chirs 在文章中用到了比如"terrible performance", "slow"等字眼,但却没有真正的指出究竟是具体的哪一项操作导致了性能的低下。

Nicholas C. Zakas 于是写了一篇针对该文的文章 In defense of localStorage ,从文章的名字就可以看出,Nicholas 想要捍卫 LS,毕竟它不是在上一文章中被描述的那样一无是处,不应该被抵制。

比较性能这种事情,应该看怎么比,和谁比。

就“读”数据而言,如果你把“从 LS 中读一个值”和“从 Object 对象中读一个属性”相比,是不公平的,前者是从硬盘里读,后者是从内存里读,就好比让汽车与飞机赛跑一样,有一个 benchmark 各位可以参考一下: localStorage vs. Objects :
在这里插入图片描述
跑分的标准是 OPS(operation per second),值当然是越高越好。你可能会注意到,在某个浏览器的对比列中,没有显示关于 LS 的红色列——这不是因为统计出错,而是因为 LS 的操作性能太差,跑分太低 (相对从 Object 中读取属性而言),所以无法显示在同一张表格内,如果你真的想看的话,可以给你看一张放大的版本:
在这里插入图片描述
这样以来你大概就知道两者在什么级别上了

在浏览器中与 LS 最相近的机制莫过于 Cookie 了:Cookie 同样以 key-value 的形式进行存储,同样需要进行 I/O 操作,同样需要对不同的 tab 标签进行同步。同样有 benchmark 可以供我们进行参考: localStorage vs. Cookies

从 Brwoserscope 中提供的结果可以看出,就 Reading from cookie, Reading from localStorage getItem, Writing to cookie,Writing to localStorage property 四项操作而言,在不同浏览器不同平台,读和写的效率都不太相同,有的趋于一致,有的大相径庭。

甚至就 LS 自己而言,不同的存储方式和不同的读取方式也会产生效率方面的问题。有两个 benchmark 非常值得说明问题:
localStorage-string-size

localStorage String Size Retrieval
在第一个测试中,Nicholas 在 LS 中用四个 key 分别存储了 100 个字符,500 个字符,1000 个字符和 2000 个字符。测试分别读取不同长度字符的速度。结果是:读取速度与读取字符的长度无关。

第二个测试用于测试读取 1000 个字符的速度,不同的是对照组是一次性读取 1000 个字符;而实验组是从 10 个 key 中 (每个 key 存储 100 个字符) 分 10 次读取。结论: 是分 10 此读取的速度会比一次性读取慢 90% 左右。

LS 也并非没有痛点。大部分的 LS 都是基于同一个域名共享存储数据,所以当你在多个标签打开同一个域名下的站点时,必须面临一个同步的问题,当 A 标签想写入 LS 与 B 标签想从 LS 中读同时发生时,哪一个操作应该首先发生?为了保证数据的一致性,在读或者在写时 务必会把 LS 锁住 (甚至在操作系统安装的杀毒软件在扫描到该文件时,会暂时锁住该文件)。因为单线程的关系,在等待 LS I/O 操作的同时,UI 线程和 Javascript 也无法被执行。

但实际情况远比我们想象的复杂的多。为了提高读写的速度,某些浏览器 (比如火狐) 会在加载页面时就把该域名下 LS 数据加载入内存中,这么做的副作用是延迟了页面的加载速度。但如果不这么做而是在临时读写 LS 时再加载,同样有死锁浏览器的风险。并且把数据载入内存中也面临着将内存同步至硬盘的问题。

上面说到的这些问题大部分归咎于内部的实现,需要依赖浏览器开发者来改进。并且并非仅仅存在于 LS 中,相信在 IndexedDB、webSQL 甚至 Cookie 中也有类似的问题在发生。

实战开始

考虑到移动端网络环境的不稳定,为了避免网络延迟 (network latency),大部分网站的移动端站点会将体积庞大的类库存储于本地浏览器的 LS 中。但百度音乐将这个技术也应用到了PC 端,他们将所依赖的jQuery 类库存入LS 中。用一段很简单的代码来保证对jQuery 的正确载入。我们一起来看看这段代码。代码详解就书写在注释中了:

!function (globals, document) {
    var storagePrefix = "mbox_";
    globals.LocalJs = {
        require: function (file, callback) {
            /*
                如果无法使用 localstorage,则使用 document.write 把需要请求的脚本写在页面上 
                作为 fallback,使用 document.write 确保已经加载了所需要的类库 
            */

            if (!localStorage.getItem(storagePrefix + "jq")) {
                document.write('<script src="' + file + '" type="text/javascript"></script>');
                var self = this;

            /*
                并且 3s 后再请求一次,但这次请求的目的是为了获取 jquery 源码,写入 localstorage 中 (见下方的 _loadjs 函数)
                这次“一定”走缓存,不会发出多余的请求 
                为什么会延迟 3s 执行?为了确保通过 document.write 请求 jQuery 已经加载完成。但很明显 3s 也并非一个保险的数值 
                同时使用 document.write 也是出于需要故意阻塞的原因,而无法为其添加回调,所以延时 3s
            */
                setTimeout(function () {
                    self._loadJs(file, callback)
                }, 3e3)
            } else {
                // 如果可以使用 localstorage,则执行注入 
                this._reject(localStorage.getItem(storagePrefix + "jq"), callback)
            }
        },
        _loadJs: function (file, callback) {
            if (!file) {
                return false
            }
            var self = this;
            var xhr = new XMLHttpRequest;
            xhr.open("GET", file);
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        localStorage.setItem(storagePrefix + "jq", xhr.responseText)
                    } else {}
                }
            };
            xhr.send()
        },
        _reject: function (data, callback) {
            var el = document.createElement("script");
            el.type = "text/javascript";
            /*
                关于如何执行 LS 中的源码,我们有三种方式 
                1. eval
                2. new Function
                3. 在一段 script 标签中插入源码,再将该 script 标签插入页码中 

                关于这三种方式的执行效率,我们内部初步测试的结果是不同的浏览器下效率各不相同 
                参考一些 jsperf 上的测试,执行效率甚至和具体代码有关。
            */
            el.appendChild(document.createTextNode(data));
            document.getElementsByTagName("head")[0].appendChild(el);
            callback && callback()
        },
        isSupport: function () {
            return window.localStorage
        }
    }
}(window, document);
!
function () {
    var url = _GET_HASHMAP ? _GET_HASHMAP("/player/static/js/naga/common/jquery-1.7.2.js") : "/player/static/js/naga/common/jquery-1.7.2.js";
    url = url.replace(/^\/\/mu[0-9]*\.bdstatic\.com/g, "");
    LocalJs.require(url, function () {})
}(); 

因为桌面端的浏览器兼容性问题比移动端会严峻的多,所以大多数对 LS 利用属于“做加法”,或者“轻量级”的应用。最后一瞥不同站点在 PC 平台的对 LS 的使用情况:

  • 比如百度和 github 用 LS 记录用户的搜素行为,为了提供更好的搜索建议
    在这里插入图片描述
  • Twitter 利用 LS 最主要的记录了与用户关联的信息 (截图自我的 Twitter 账号,因为关注者和被关注者的不同数据会有差异):
  • userAdjacencyList 表占 40,158 bytes,用于记录每个字关联的用户信息
  • userHash 表占 36,883 bytes,用于记录用户被关注的人信息
    在这里插入图片描述
  • Google 利用 LS 记录了样式
    在这里插入图片描述
  • 天猫用 LS 记录了导航栏的 HTML 碎片代码:
    -在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值