改写函数实际上违背了FP的无副作用的精神

在[url=http://hax.iteye.com/blog/113565]上一篇帖子[/url]中,我讨论了Peter提出的[url=http://peter.michaux.ca/article/3556]Lazy Function Definition Pattern[/url],我指出了这个pattern并不能带来性能的提升,而所使用的closure也有可能造成内存泄漏。

当然内存泄露在任何closure中都可能存在。因此仅仅从不必要的使用closure加大了内存泄漏的风险这一点来说,说服力可能并不强。而且,在之后的回复中,FCKEditor的FredCK给出了一个[url=http://peter.michaux.ca/article/3556#comment-3661]不使用closure的改进方案[/url]。

但是我仍然觉得哪里不妥。

经过一些思考,我终于意识到关键的坏味道,其实就在于对函数变量的重新赋值。

我们知道纯FP是不带有副作用的,因此不可能允许函数名称与实际代码的重新绑定。所以正如我之前的回复所指出的,这个Lazy Definition模式,其实一点也不functional。

那么让我们看看,副作用有什么危害吧!

首先是Peter的样例代码:
[code]
var getScrollY = function() {

if (typeof window.pageYOffset == 'number') {

getScrollY = function() {
return window.pageYOffset;
};

} else if ((typeof document.compatMode == 'string') &&
(document.compatMode.indexOf('CSS') >= 0) &&
(document.documentElement) &&
(typeof document.documentElement.scrollTop == 'number')) {

getScrollY = function() {
return document.documentElement.scrollTop;
};

} else if ((document.body) &&
(typeof document.body.scrollTop == 'number')) {

getScrollY = function() {
return document.body.scrollTop;
}

} else {

getScrollY = function() {
return NaN;
};

}

return getScrollY();
}
[/code]


假设一位倒霉的开发者John以前有自己写过一个类似getScrollY的函数,但是名字叫做getPageYOffset,用在他自己的页面代码中。现在他知道了Peter的getScrollY,他听说Peter这个版本兼容性更好、性能更高,于是决定采用Peter的版本代替自己的版本。同时他又不想对原有代码做过多改动。于是很自然的,他打算在html head部分引入Peter的脚本,然后后面加上一句:
getPageYOffset = getScrollY;

John特地写了一个testcase,测试这样做的效果。当然,testcase肯定能通过。

但是,大家都知道这样做的结果了,getPageYOffset指向的是原初的getScrollY,因此每次调用getPageYOffset都等于是第一次调用getScrollY。这样非但不会有假想中的节约一次条件判断,反而会而反复的对getScrollY变量重新赋值。性能肯定大大下降。

这还不算,还可能更糟。

考虑FredCK的版本:
[code]
var getScrollY = function() {

if (typeof window.pageYOffset == 'number')
return (getScrollY = getScrollY.case1)();

var compatMode = document.compatMode;
var documentElement = document.documentElement;

if ((typeof compatMode == 'string') &&
(compatMode.indexOf('CSS') >= 0) &&
(documentElement) &&
(typeof documentElement.scrollTop == 'number'))
return (getScrollY = getScrollY.case2)();

var body = document.body ;
if ((body) &&
(typeof body.scrollTop == 'number'))
return (getScrollY = getScrollY.case3)();

return (getScrollY = getScrollY.case4)();
};

getScrollY.case1 = function() {
return window.pageYOffset;
};

getScrollY.case2 = function() {
return documentElement.scrollTop;
};

getScrollY.case3 = function() {
return body.scrollTop;
};

getScrollY.case4 = function() {
return NaN;
};
[/code]

对于这个版本,John一样做了testcase,调用了一次getPageYOffset,没问题,又通过了。

然而当John实际使用的时候,会发生什么?大家可以推导一下。记住,真实应用中,getPageYOffset可能被调用很多次。

所以,这就是我为什么不喜欢这个Pattern,因为开发者有了这样的负担:必须了解这个函数会否悄悄的变化,理解这种副作用可能带来的问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值