2007-08-18
改写函数实际上违背了FP的无副作用的精神
在上一篇帖子中,我讨论了Peter提出的Lazy Function Definition Pattern,我指出了这个pattern并不能带来性能的提升,而所使用的closure也有可能造成内存泄漏。
当然内存泄露在任何closure中都可能存在。因此仅仅从不必要的使用closure加大了内存泄漏的风险这一点来说,说服力可能并不强。而且,在之后的回复中,FCKEditor的FredCK给出了一个不使用closure的改进方案。
但是我仍然觉得哪里不妥。
经过一些思考,我终于意识到关键的坏味道,其实就在于对函数变量的重新赋值。
我们知道纯FP是不带有副作用的,因此不可能允许函数名称与实际代码的重新绑定。所以正如我之前的回复所指出的,这个Lazy Definition模式,其实一点也不functional。
那么让我们看看,副作用有什么危害吧!
首先是Peter的样例代码:
假设一位倒霉的开发者John以前有自己写过一个类似getScrollY的函数,但是名字叫做getPageYOffset,用在他自己的页面代码中。现在他知道了Peter的getScrollY,他听说Peter这个版本兼容性更好、性能更高,于是决定采用Peter的版本代替自己的版本。同时他又不想对原有代码做过多改动。于是很自然的,他打算在html head部分引入Peter的脚本,然后后面加上一句:
getPageYOffset = getScrollY;
John特地写了一个testcase,测试这样做的效果。当然,testcase肯定能通过。
但是,大家都知道这样做的结果了,getPageYOffset指向的是原初的getScrollY,因此每次调用getPageYOffset都等于是第一次调用getScrollY。这样非但不会有假想中的节约一次条件判断,反而会而反复的对getScrollY变量重新赋值。性能肯定大大下降。
这还不算,还可能更糟。
考虑FredCK的版本:
对于这个版本,John一样做了testcase,调用了一次getPageYOffset,没问题,又通过了。
然而当John实际使用的时候,会发生什么?大家可以推导一下。记住,真实应用中,getPageYOffset可能被调用很多次。
所以,这就是我为什么不喜欢这个Pattern,因为开发者有了这样的负担:必须了解这个函数会否悄悄的变化,理解这种副作用可能带来的问题。
当然内存泄露在任何closure中都可能存在。因此仅仅从不必要的使用closure加大了内存泄漏的风险这一点来说,说服力可能并不强。而且,在之后的回复中,FCKEditor的FredCK给出了一个不使用closure的改进方案。
但是我仍然觉得哪里不妥。
经过一些思考,我终于意识到关键的坏味道,其实就在于对函数变量的重新赋值。
我们知道纯FP是不带有副作用的,因此不可能允许函数名称与实际代码的重新绑定。所以正如我之前的回复所指出的,这个Lazy Definition模式,其实一点也不functional。
那么让我们看看,副作用有什么危害吧!
首先是Peter的样例代码:
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();
}
假设一位倒霉的开发者John以前有自己写过一个类似getScrollY的函数,但是名字叫做getPageYOffset,用在他自己的页面代码中。现在他知道了Peter的getScrollY,他听说Peter这个版本兼容性更好、性能更高,于是决定采用Peter的版本代替自己的版本。同时他又不想对原有代码做过多改动。于是很自然的,他打算在html head部分引入Peter的脚本,然后后面加上一句:
getPageYOffset = getScrollY;
John特地写了一个testcase,测试这样做的效果。当然,testcase肯定能通过。
但是,大家都知道这样做的结果了,getPageYOffset指向的是原初的getScrollY,因此每次调用getPageYOffset都等于是第一次调用getScrollY。这样非但不会有假想中的节约一次条件判断,反而会而反复的对getScrollY变量重新赋值。性能肯定大大下降。
这还不算,还可能更糟。
考虑FredCK的版本:
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;
};
对于这个版本,John一样做了testcase,调用了一次getPageYOffset,没问题,又通过了。
然而当John实际使用的时候,会发生什么?大家可以推导一下。记住,真实应用中,getPageYOffset可能被调用很多次。
所以,这就是我为什么不喜欢这个Pattern,因为开发者有了这样的负担:必须了解这个函数会否悄悄的变化,理解这种副作用可能带来的问题。
- 04:27
- 浏览 (2152)
- 评论 (7)
- 分类: JS
- 进入论坛
- 发布在 javascript研究小组 圈子
- 相关推荐
评论
netfishx
2007-08-19
hax 写道
Peter同志好像把我在他blog上最后一个comments删除了,令我有点失望啊。
没有看到,有备份吗?
hax
2007-08-19
Peter同志好像把我在他blog上最后一个comments删除了,令我有点失望啊。
netfishx
2007-08-18
看了好几天了,说下我个人的感觉:peter的例子举的不好,没有体现出lazy的作用。但在特定场景下,这种模式是有用处的。至于边界条件确实应该说明出来,lazy模式不是放之四海而皆准的。
所谓functional更多的只是起一个吸引眼球的作用,这个模式根本不沾边,不必理睬。
所谓functional更多的只是起一个吸引眼球的作用,这个模式根本不沾边,不必理睬。
hax
2007-08-18
所以这是考验lib开发者的。
我们有几种方式来解决问题。
第一个是给多一点选择,例如jquery,为了和其他类库的$共存,而提供了另外的api。
第二个是遵循社区的约定。例如private成员用下划线作为前缀。
第三个是使用某种良好的设施。例如jsi和pies,能帮助不同类库和平共处。
第四个是避免对用户的不必要约束,特别是对于比较trivial的事情,不能强加给用户约束。这是所谓unobstrusive javascript。
下面回过头看看这个问题。
对于function的不变性,绝大多数用户会有不自觉的隐含认可。因为function调用的意义是明确的,不变的。(有明确说我这个函数就是经常变化的例子么?)基于此,用户不会预期函数对象本身的变化。
注意,用户可以接受初始化这样一件事情。这是很正常的。但是用户为什么要接受,第一次调用与以后调用的差别呢!所以问题就在这里。调用函数是一件trivial的事情,不应该强迫用户了解调用之间的差别,这与用户的目标毫无关系。况且你就是告诉用户第一次调用和以后调用的差别,也不代表他们能理解这种副作用对于他们代码的影响。因此出现我说的那种情况的话,用户就会陷入迷茫。
你可以争辩说我所做的这个例子的假设会不会发生,有多大概率发生。
Ok,本身这就是一种权衡。我的看法是,附加这样一种对用户的假设所付出的代价,却没有得到什么明显的好处。所以我认为这是一种antipattern。
我们有几种方式来解决问题。
第一个是给多一点选择,例如jquery,为了和其他类库的$共存,而提供了另外的api。
第二个是遵循社区的约定。例如private成员用下划线作为前缀。
第三个是使用某种良好的设施。例如jsi和pies,能帮助不同类库和平共处。
第四个是避免对用户的不必要约束,特别是对于比较trivial的事情,不能强加给用户约束。这是所谓unobstrusive javascript。
下面回过头看看这个问题。
对于function的不变性,绝大多数用户会有不自觉的隐含认可。因为function调用的意义是明确的,不变的。(有明确说我这个函数就是经常变化的例子么?)基于此,用户不会预期函数对象本身的变化。
注意,用户可以接受初始化这样一件事情。这是很正常的。但是用户为什么要接受,第一次调用与以后调用的差别呢!所以问题就在这里。调用函数是一件trivial的事情,不应该强迫用户了解调用之间的差别,这与用户的目标毫无关系。况且你就是告诉用户第一次调用和以后调用的差别,也不代表他们能理解这种副作用对于他们代码的影响。因此出现我说的那种情况的话,用户就会陷入迷茫。
你可以争辩说我所做的这个例子的假设会不会发生,有多大概率发生。
Ok,本身这就是一种权衡。我的看法是,附加这样一种对用户的假设所付出的代价,却没有得到什么明显的好处。所以我认为这是一种antipattern。
radar
2007-08-18
我明白你的意思。
但是假设和规范 是两码事。
例如:var是javascript的关键字,你不能乱用。ok这是规范。
$对prototype.js 这是规范
这些是明确要 语言,类库使用者遵守的规定。
假如你选择mootools,你的类必须按照它提供的方式创建。你还不能随便乱增加方法。... ...
如果选择jQuery,你的dom不能有 $event属性。
等等,其实这是个矛盾,你提到的边界很难把握,人与人之间这个点也是不同的。
但是假设和规范 是两码事。
例如:var是javascript的关键字,你不能乱用。ok这是规范。
$对prototype.js 这是规范
这些是明确要 语言,类库使用者遵守的规定。
假如你选择mootools,你的类必须按照它提供的方式创建。你还不能随便乱增加方法。... ...
如果选择jQuery,你的dom不能有 $event属性。
等等,其实这是个矛盾,你提到的边界很难把握,人与人之间这个点也是不同的。
hax
2007-08-18
To radar:
你明白程序员的职责么?代码质量的很重要的一条,就是程序员对于边界情况的处理。
你明白类库开发者的职责么?类库开发者很重要的一条,就是不要对类库的用户作出过多的限制。除非这种限制是在nontrivial的事情上。
因此不是我的假设太多,而是这个antipatterns本身的假设太多。
你明白程序员的职责么?代码质量的很重要的一条,就是程序员对于边界情况的处理。
你明白类库开发者的职责么?类库开发者很重要的一条,就是不要对类库的用户作出过多的限制。除非这种限制是在nontrivial的事情上。
因此不是我的假设太多,而是这个antipatterns本身的假设太多。
radar
2007-08-18
你的假设太多。
发表评论
提醒: 该博客已发表在公共论坛,博客所有留言会成为论坛回贴,留言请注意遵守论坛发贴规则
- 浏览: 139374 次
- 性别:

- 来自: 上海

- 详细资料
搜索本博客
最近加入圈子
最新评论
-
一个嵌入式HTML引擎
这个引擎的源码在什么地方可以下阿
-- by lizhaosuper -
注册Facebook的一点点用户 ...
注册验证码确实有点变态。好在只是一次性的。我经常看到一些网站每次留言都要验证码, ...
-- by hax -
注册Facebook的一点点用户 ...
那个注册验证码太强了点
-- by jinhao7773 -
注册Facebook的一点点用户 ...
第一次使用facebook也发现了这点。他的工作很细致。
-- by wutao8818 -
向左转?向右转?
这个论坛有人发过了
-- by lonelyblue






评论排行榜