如果你想直接看用getter拦截器实现资源懒加载的EcmaScript代码,不想听我讲一大堆理论的话,可直接从第4个大标题读起。
专业名词广义化
计算机行业发展了这么多年,出现过无数的专业名词,有的古老名词过时了并被人们淡忘,但有的古老名词不仅没过时,还从本身的狭义概念上升到广义的哲学概念。
比如“双工”本身是物理链路中关于数据线传输方向的一个名词,现在被用来描述所有对等体之间的传输模式;再比如“幂等”原本是个数学名词,现在居然被拿来形容服务器接口的行为。还有许多这样的例子,随便就能想到几个:什么“编码”,“I/O”,“解耦”,“序列化”。。。
今天要谈的名词是“懒加载”。懒加载(Lazy Load / Load On Demand)是web1.0时代中浏览器按需加载图片的一种方式,因为当年网速很慢,为了节省带宽,只有进入屏幕视野的图片元素才被临时下载并显示。但是再次将图片滚动进视野时就不用再加载了。
虽然现在的网页图片也是这样加载的,但“懒加载”这个名词流传了下来,因为很多地方都体现到了懒加载的哲学,比如http后端路由模块的加载需要耗费一定资源,那么可以等到用户第一次访问到这个路由接口的时候再加载,第二次访问的时候就无需再加载了。
懒加载的充要条件
于是我们给懒加载指定一个命题,包括什么情况下该使用懒加载,以及懒加载应该怎么做:
加载一个资源需要耗费较多的时间或空间
但是每次加载得到的资源完全一样(资源不会动态变化)
这个资源可能要被读取0次,1次或多次。
第1次读取时才加载然后缓存下来
第2次及以后读取缓存
如果违反了以上任意一个充要条件就没必要使用懒加载或者懒加载被用错了!仔细看看第一个条件,其实懒加载还可以叫其他名字,比如懒计算,懒请求,懒存储。。。反正都是一个意思:以一种很懒的态度(延迟加载)来进行某种耗费时间或空间的任务。
用getter来优雅地懒加载
为什么要叫他优雅的懒加载呢?因为实现懒加载有许多方式,许多都是通过一个外部变量来表示资源是否已经被加载过,每次加载时都要先判断一下。但由于状态变量和加载函数是一对一的,所以把他俩合并起来效果会更好,比如把状态变量挂在加载函数身上。
但更好的方式是通过EcmaScript的getter拦截器来进行“元编程”,getter本身的作用就是可以在get的时候临时计算某个值,思考下面这个例子:
const foo = {
x: 3,
y: 4,
get xy() {
return this.x * this.y;
}
};
foo.xy; // 12
其中,x和y都是变量,xy代表x和y的乘积,每当x和y变化的时候,xy不用跟着变化,只有访问xy的时候才临时计算数量积。是不是已经有点“懒”的意思了?但由于这里x和y不是常量,不符合懒加载的充分必要条件,真实情况往往是当getter的计算量很大的情况,比如暴力破解某个固定的弱口令。
Example
如果我们希望第一次读取foo.bar的时候才计算实际的bar值,之后都读取这个计算好的值,但是每次读取foo.bar从代码上感觉不到区别,也就是为了“优雅”。
假如有一个页面,页面上有10万个dom元素,通过id选择器来查找<foo id="bar"></foo>元素实在是太为难引擎了,引擎希望能不找就不找,要找就找一次。我们可以让foo成为一个getter,当初次访问时查找到<foo>后删掉这个getter,取而代之的是一个同名的普通foo属性,foo属性保存着刚刚缓存的<foo>,这样一来,首次和后续访问foo时得到的都是同样的元素,除了首次的延迟,感觉不到其他区别,代码很简单:
{
get foo() {
delete this.foo;
return this.foo = document.getElementById('bar');
}
}
火狐的XPCOMUtils.jsm库里居然还有defineLazyGetter这个现成的方法:https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/XPCOMUtils.jsm#defineLazyGetter()
(完)