`
hax
  • 浏览: 962960 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

    博客分类:
  • JS
阅读更多
上一篇帖子中,我讨论了Peter提出的Lazy Function Definition Pattern,我指出了这个pattern并不能带来性能的提升,而所使用的closure也有可能造成内存泄漏。

当然内存泄露在任何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,因为开发者有了这样的负担:必须了解这个函数会否悄悄的变化,理解这种副作用可能带来的问题。
分享到:
评论
8 楼 kaize 2011-09-27  
Hax你好,我是周六W3ctech的一名听众:)

关于这个lazy loading Func我有些不同的意见:
其实lazy loading func“概念本身”的确是对函数的执行进行了优化,比如在<Pro Javscript for web developer 2nd Edition>中18。Advanced Tech一章就用到了这个pattern来创建XHR。

function createXHR(){
if (typeof XMLHttpRequest != “undefined”){
createXHR = function(){a
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != “undefined”){
createXHR = function(){
if (typeof arguments.callee.activeXString != “string”){
var versions = [“MSXML2.XMLHttp.6.0”, “MSXML2.XMLHttp.3.0”,
“MSXML2.XMLHttp”];
for (var i=0,len=versions.length; i < len; i++){
try {
var xhr = new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
return xhr;
} catch (ex){
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
createXHR = function(){
throw new Error(“No XHR object available.”);
};
}
return createXHR();
}

另:<pro javascript design patterns>第8章也有类似实现

综上,个人认为,lazy loading func这种pattern本身对于性能的优化是可以肯定的,而对其的使用,是有选择性的。而且lazy loading func Pattern恰恰体现了js的灵活性。
7 楼 netfishx 2007-08-19  
hax 写道
Peter同志好像把我在他blog上最后一个comments删除了,令我有点失望啊。

没有看到,有备份吗?
6 楼 hax 2007-08-19  
Peter同志好像把我在他blog上最后一个comments删除了,令我有点失望啊。
5 楼 netfishx 2007-08-18  
看了好几天了,说下我个人的感觉:peter的例子举的不好,没有体现出lazy的作用。但在特定场景下,这种模式是有用处的。至于边界条件确实应该说明出来,lazy模式不是放之四海而皆准的。

所谓functional更多的只是起一个吸引眼球的作用,这个模式根本不沾边,不必理睬。
4 楼 hax 2007-08-18  
所以这是考验lib开发者的。

我们有几种方式来解决问题。

第一个是给多一点选择,例如jquery,为了和其他类库的$共存,而提供了另外的api。

第二个是遵循社区的约定。例如private成员用下划线作为前缀。

第三个是使用某种良好的设施。例如jsi和pies,能帮助不同类库和平共处。

第四个是避免对用户的不必要约束,特别是对于比较trivial的事情,不能强加给用户约束。这是所谓unobstrusive javascript。

下面回过头看看这个问题。

对于function的不变性,绝大多数用户会有不自觉的隐含认可。因为function调用的意义是明确的,不变的。(有明确说我这个函数就是经常变化的例子么?)基于此,用户不会预期函数对象本身的变化。

注意,用户可以接受初始化这样一件事情。这是很正常的。但是用户为什么要接受,第一次调用与以后调用的差别呢!所以问题就在这里。调用函数是一件trivial的事情,不应该强迫用户了解调用之间的差别,这与用户的目标毫无关系。况且你就是告诉用户第一次调用和以后调用的差别,也不代表他们能理解这种副作用对于他们代码的影响。因此出现我说的那种情况的话,用户就会陷入迷茫。

你可以争辩说我所做的这个例子的假设会不会发生,有多大概率发生。

Ok,本身这就是一种权衡。我的看法是,附加这样一种对用户的假设所付出的代价,却没有得到什么明显的好处。所以我认为这是一种antipattern。



3 楼 radar 2007-08-18  
我明白你的意思。
但是假设和规范 是两码事。

例如:var是javascript的关键字,你不能乱用。ok这是规范。
$对prototype.js 这是规范
这些是明确要 语言,类库使用者遵守的规定。

假如你选择mootools,你的类必须按照它提供的方式创建。你还不能随便乱增加方法。... ...

如果选择jQuery,你的dom不能有 $event属性。

等等,其实这是个矛盾,你提到的边界很难把握,人与人之间这个点也是不同的。
2 楼 hax 2007-08-18  
To radar:
你明白程序员的职责么?代码质量的很重要的一条,就是程序员对于边界情况的处理。
你明白类库开发者的职责么?类库开发者很重要的一条,就是不要对类库的用户作出过多的限制。除非这种限制是在nontrivial的事情上。

因此不是我的假设太多,而是这个antipatterns本身的假设太多。
1 楼 radar 2007-08-18  
你的假设太多。

相关推荐

    利用thunk技术改写窗口类回调函数为窗口对象成员函数

    然而,这样的设计可能会导致代码组织不清晰,尤其是当窗口类与业务逻辑紧密关联时,回调函数往往需要访问类的成员变量,这就需要在全局作用域中传递额外的信息。为了解决这个问题,我们可以利用Thunk技术将窗口类的...

    32位函数重定向,文件说明参考博客32位函数重定向

    用jump cpu指令改写函数的头几个字节,该指令会转移到替换函数的内存地址,(替换函数必修和被hook的函数标记完全相同),当被hook的函数被调用时,jump指令实际上将转移到替换函数上执行,实现hook的目的。...

    js写的ajax核心构造和改写alert函数

    在本文中,我们将探讨如何构建一个基本的AJAX核心,并了解如何改写原生的`alert`函数。 首先,让我们从创建一个简单的AJAX核心开始。AJAX的核心功能是发起异步请求并与服务器进行通信。以下是一个基础的AJAX构造: ...

    指数函数、对数函数、幂函数基本性质练习含答案.doc

    1. 当a &gt; 1时,函数在实数集R上是严格增函数。 2. 当0 时,函数在实数集R上是严格减函数。 3. 指数函数的图象总通过点(0, 1),因为任何数的0次幂都是1。 4. 对于任意的x,y有ax + by = (ab)x,这是指数运算的结合律...

    可重入函数与不可重入函数

    在实际编程中,将不可重入函数改写为可重入函数的方法通常包括: 1. 移除函数中对全局变量和静态局部变量的依赖,转而使用函数参数传递。 2. 如果无法避免对全局变量的访问,应当使用互斥手段来保护访问过程,确保...

    Oracle查询优化改写技巧与案例

    第5~12章是提高部分,讲解了正则表达式、分析函数、树形查询及汇总函数的用法。这部分知识常用于对一些复杂需求的实现及优化改写。最后两章介绍日常的优化改写案例。这部分是前面所学知识的扩展应用。, 如果您是开发...

    Oracle查询优化改写技巧与案例2.zip

    第5~12章是提高部分,讲解了正则表达式、分析函数、树形查询及汇总函数的用法。这部分知识常用于对一些复杂需求的实现及优化改写。最后两章介绍日常的优化改写案例。这部分是前面所学知识的扩展应用。, 如果您是开发...

    传递函数转状态空间的各种方法

    【标题】: "传递函数转状态空间模型的详解" 【描述】: 在控制系统分析与设计中,从传递函数转换到状态空间模型是一个重要的步骤。本文将深入探讨多种将传递函数转化为状态空间表达式的方法,帮助读者理解不同情况下...

    javaparser:基于函数式组合子逻辑的JAVA语言分析框架

    无副作用。 2。高阶函数。 3。延迟计算 而最最有意义的(至少我认为如此),是基于高阶函数的函数组合能力。一些人把这叫做glue。 简短地说,什么让函数式编程如此强大?是用简单的函数组合出复杂函数的能力。 我...

    可重入函数

    为了避免上述问题,可以通过增加局部变量的方式改写上述函数,如下所示: ```c unsigned int example(int para) { unsigned int temp, ExamCopy; ExamCopy = para; // 复制参数到局部变量 temp = Square_Exam...

    OMP算法函数

    该函数通过改写沙维老师CS_OMP算法,将其改写成函数的形式,便于直接调用

    Delphi源码免杀之(自删除)函数改写过卡巴高启发

    因此,为了绕过这些检测,开发者需要对自删除函数进行改写,使其看起来更自然,不引起杀软的警觉。以下是一些可能的改写策略: 1. **混淆技术**:使用代码混淆工具,如UPX或JCL,使得原始代码变得难以理解,降低杀...

    《反比例函数》课件.ppt

    归纳小结部分强调了反比例函数的关键点,包括其定义形式y=k/x(k≠0),自变量x的取值范围为x≠0,以及反比例函数可以改写为1/kxy=1或kxy=1的形式。此外,还应反思学习过程,深化对反比例函数的理解。 强化训练则...

    函数指针和指针函数的理解

    在C语言中,函数指针与指针函数是两个重要的概念,它们虽然听起来相似,但实际上有着明显的区别。 **函数指针**:是指向函数的指针,即它存储了一个函数的地址。通过函数指针,我们可以像操作普通变量那样来调用一...

    Oracle查询优化改写 技巧与案例_高清带书签版本

    第5~12章是提高部分,讲解了正则表达式、分析函数、树形查询及汇总函数的用法。这部分知识常用于对一些复杂需求的实现及优化改写。最后两章介绍日常的优化改写案例。这部分是前面所学知识的扩展应用。 如果您是开发...

    实验2 C++中函数、数组及指针的综合运用.pdf

    数组可以作为函数参数传递,但要注意的是,传入函数的实际上是数组的地址,而不是数组本身。这意味着,如果函数修改了数组元素,这些修改在函数外部也是可见的。实验中提到的数组作为函数参数传递,让学生理解这一...

Global site tag (gtag.js) - Google Analytics