本篇文章参考书籍《JavaScript设计模式》–张容铭
前言
各位接触过机器学习没有,没了解过也没关系,这东西的大体思路就是,当第一次执行某种情况时,机器会去判断一些情况,到第二次执行的时候,就不需要再重复判断了,那么机器学习就会在第一次执行完成后,记录当时的计算过程,参数等数据,然后重新定义这种特定情况,当再次遇到后,不需要重复判断,直接获取上次的结果就行。
这个机器学习,就很像我们今天要说的惰性模式。真的超级像(我个人猜测,有可能当年发明机器学的人,就是惰性模式用的比较好)。
惰性模式
减少每次代码执行时的重复性的分支判断,通过对对象的重新定义来屏蔽原对象中的分支判断。
大家还记得之前我们一起写过的微型代码库不,以我们自己喜欢的名字命名的那个,不记得也没事哈,看一下下面的代码就懂了。
//单体模式定义命名空间
var A = {
};
//添加绑定事件方法 on
A.on = function(dom, type, fn) {
if(dom.addEventListener) {
dom.addEventListener(type, fn, false);
} else if(dom.attachEvent) {
dom.attachEvent('on' + type, fn);
} else {
dom['on' + type] = fn;
}
}
上面写了个绑定事件时的代码库,大家有没有考虑过,当我的代码运行在某个浏览器的时候,这个判断流程,只需要执行一次就可以了,不需要每次绑定事件的时候都走一遍能力检测。
那我们可以通过惰性模式来优化这种情况,这个设计模式有两种实现方式。
第一种,是在文件加载进来的时候用闭包执行该方法对其重新定义,缺点是会在加载页面的时候占用一点资源。
第二种就是在第一种的基础上,延迟执行,在函数第一次调用的时候对其重新定义。这么做的好处就是减少文件加载时的资源消耗,但是把消耗放在了第一次执行的时候。
所以第二种方式其实是对消耗的惰性推移,有时候是很有必要的,尤其是在重定义时消耗资源比较大而且想尽早的预览到页面上的时候。(可以理解为,交给你的活,早干晚干都得你干,就看你什么时候干最有利了)
对于第一种方法,我们可以这样做。
//添加绑定事件方法
A.on = function(dom, type, fn) {
//如果支持 addEventListener 方法
if(document.addEventListener) {
//返回新定义的方法
return function(dom, type, fn) {
dom.addEventListener(type, fn, false);
}
//如果支持 attachEvent 方法(IE)
} else if(document.attachEvent) {
//返回新定义方法
return function(dom, type, fn) {
dom.attachEvent('on' + type, fn);
}
//定义 on 方法
} else {
//返回新定义方法
return function(dom, type, fn) {
dom['on' + type] = fn;
}
}
} ();
上面方法首先对 document 做能力检测,通过闭包,在页面加载的时候执行,这样便达到了重写的目的。
接下来我们实现第二种方法。
//添加绑定事件 on 方法
A.on = function(dom, type, fn) {
//如果支持 addEventListener 方法
if(dom.addEventListener) {
A.on = function(dom, type, fn) {
dom.addEventListener(type, fn, false);
}
//如果支持 attachEvent 方法(IE)
} else if(dom.attachEvent) {
A.on = function(dom, type, fn) {
dom.attachEvent('on' + type, fn);
}
//如果支持 DOM0 级事件
} else {
A.on = function(dom, type, fn) {
dom['on' + type] = fn;
}
}
//执行重定义 on 方法
A.on(dom, type, fn);
}
第二种实现与第一种不同之处在于,内部元素 dom 执行能力检测并重写,其次,原始函数在函数的最末尾重新执行一遍来绑定事件。不过在文件加载后 A.on 方法还没能重新定义。所以我们还需要等到某一元素绑定事件时, A.on 才能被重新定义
A.on(document.body, 'click', function() {
alert(ll);
})
应用场景
其实很多惰性模式还有很多的应用场景,比如我们的代码里,往往为了兼容不同浏览器,会写很多分支,这样就使得代码特别臃肿,这个时候我们可以创建一个 XHR 对象:
//创建 XHR 对象
function createXHR() {
//标准浏览器
if(typeof XMLHttpRequset !== 'undefined') {
return new XMLHttpRequset();
//IE
} else if(typeof ActiveXObject !== 'undefined') {
if(typeof arguments.callee.activeXString !== 'string') {
var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'],
i = 0,
len = version.length;
//遍历并设置版本
for(; i < len; i++) {
try{
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
}catch(ex) {
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
//对不支持的浏览器抛出错误提示
} else {
throw new Error('您的浏览器不支持Ajax。');
}
}
上面代码中为了兼容 IE ,执行了很多分支,但是有些浏览器中部分分支是没必要的,所以我们可以用惰性模式进行优化。
//第一种 加载时损失性能,但是第一次调用时不损失性能
var createXHR = (function() {
if(typeof XMLHttpRequset !== 'undefined') {
return fucntion() {
return new XMLHttpRequset();
};
} else if(typeof ActiveXObject !== 'undefined') {
return fucntion() {
//...省略代码
};
} else {
return function() {
throw new Error('No XHR object available');
};
}
})();
//第二种方案,加载时不损失性能,但是第一次调用时损失性能
function createXHR() {
if(typeof XMLHttpRequset !== 'undefined') {
createXHR = function() {
return new XMLHttpRequest();
};
} else if(typeof ActiveXObject !== 'undefined') {
createXHR = function() {
//...省略代码
};
} else {
createXHR = function() {
throw new Error ('No XHR object available');
};
}
return createXHR();
}
技巧篇的设计模式基本都是通过一些小修改,来节约内存,积少成多,各位别小看这一点点,当真等到内存泄漏,崩溃的时候,那修改成本可海了去了。