用户名: 密码: 验证码:           网站地图 高级搜索 RSS订阅 收藏本站
Google
您的位置:首页>>图形设计>>JavaScript>>阅读资讯:学习Javascript:掌握惰性函数定义模式

学习Javascript:掌握惰性函数定义模式

[ 来源: | 阅读:次 | 更新日期:2007-8-28 23:01:46 | 评论 0 条 | 我要投稿 ]

这篇文章阐述的是一种函数式编程(functional-programming)设计模式,我称之为惰性函数定义(Lazy Function Definition)。我不止一次发现这种模式在JavaScript中大有用处,尤其是编写跨浏览器的、高效运行的库之时。 字串8

热身问题

月落

编写一个函数foo,它返回的是Date对象,这个对象保存的是foo首次调用的时间。

yueluo.net

方法一:上古时代的技术 yueluo.net

这个最简陋的解决方案使用了全局变量t来保存Date对象。foo首次调用时会把时间保存到t中。接下来的再次调用,foo只会返回保存在t中的值。

月落网

var t;
function foo() {
    if (t) {
        return t;
    }
    t = new Date();
    return t;
}

yueluo.net

但是这样的代码有两个问题。第一,变量t是一个多余的全局变量,并且在 foo调用的间隔期间有可能被更改。第二,在调用时这些代码的效率并没有得到优化因为每次调用 foo都必须去求值条件。虽然在这个例子中,求值条件并不显得低效,但在现实世界的实践例子中常常会有极为昂贵的条件求值,比如在if-else-else-…的结构中。

月落网

方法二:模块模式 www.yueluo.net

我们可以通过被认为归功于Cornford 和 Crockford 的模块模式来弥补第一种方法的缺陷。使用闭包可以隐藏全局变量t,只有在 foo内的代码才可以访问它。 月落网

var foo = (function() {
    var t;
    return function() {
        if (t) {
            return t;
        }
        t = new Date();
        return t;
    }
})();
字串9

但这仍然没有优化调用时的效率,因为每次调用foo依然需要求值条件。 月落网

虽然模块模式是一个强大的工具,但我坚信在这种情形下它用错了地方。 yueluo.net

方法三:函数作为对象 月落

由于JavaScript的函数也是对象,所以它可以带有属性,我们可以据此实现一种跟模块模式质量差不多的解决方案。 字串8

function foo() {
    if (foo.t) {
        return foo.t;
    }
    foo.t = new Date();
    return foo.t;
}
字串6

在一些情形中,带有属性的函数对象可以产生比较清晰的解决方案。我认为,这个方法在理念上要比模式模块方法更为简单。 字串8

这个解决方案避免了第一种方法中的全局变量t,但仍然解决不了foo每次调用所带来的条件求值。 字串7

方法四:惰性函数定义 月落网

现在,这是你阅读这篇文章的理由:

字串7

var foo = function() {
    var t = new Date();
    foo = function() {
        return t;
    };
    return foo();
};

字串7

当foo首次调用,我们实例化一个新的Date对象并重置 foo到一个新的函数上,它在其闭包内包含Date对象。在首次调用结束之前,foo的新函数值也已调用并提供返回值。

yueluo.net

接下来的foo调用都只会简单地返回t保留在其闭包内的值。这是非常快的查找,尤其是,如果之前那些例子的条件非常多和复杂的话,就会显得很高效。

字串8

弄清这种模式的另一种途径是,外围(outer)函数对foo的首次调用是一个保证(promise)。它保证了首次调用会重定义foo为一个非常有用的函数。笼统地说,术语“保证” 来自于Scheme的惰性求值机制(lazy evaluation mechanism)。每一位JavaScript程序员真的都应该 学习Scheme ,因为它有很多函数式编程相关的东西,而这些东西会出现在JavaScript中。

字串6

确定页面滚动距离 字串5

编写跨浏览器的JavaScript, 经常会把不同的浏览器特定的算法包裹在一个独立的JavaScript函数中。这就可以通过隐藏浏览器差异来标准化浏览器API,并让构建和维护复杂的页面特性的JavaScript更容易。当包裹函数被调用,就会执行恰当的浏览器特定的算法。 字串9

在拖放库中,经常需要使用由鼠标事件提供的光标位置信息。鼠标事件给予的光标坐标相对于浏览器窗口而不是页面。加上页面滚动距离鼠标的窗口坐标的距离即可得到鼠标相对于页面的坐标。所以我们需要一个反馈页面滚动的函数。演示起见,这个例子定义了一个函数getScrollY。因为拖放库在拖拽期间会持续运行,我们的getScrollY必须尽可能高效。 字串7

不过却有四种不同的浏览器特定的页面滚动反馈算法。Richard Cornford在他的feature detection article 文章中提到这些算法。最大的陷阱在于这四种页面滚动反馈算法其中之一使用了 document.body. JavaScript库通常会在HTML文档的<head>加载,与此同时docment.body并不存在。所以在库载入的时候,我们并不能使用特性检查(feature detection)来确定使用哪种算法。

字串6

考虑到这些问题,大部分JavaScript库会选择以下两种方法中的一种。第一个选择是使用浏览器嗅探navigator.userAgent,为该浏览器创建高效、简洁的getScrollY. 第二个更好些的选择是getScrollY在每一次调用时都使用特性检查来决定合适的算法。但是第二个选择并不高效。

字串8

好消息是拖放库中的getScrollY只会在用户与页面的元素交互时才会用到。如果元素业已出现在页面中,那么document.body也会同时存在。getScrollY的首次调用,我们可以使用惰性函数定义模式结合特性检查来创建高效的getScrollY. 字串5

 

字串9

总结

字串8

惰性函数定义模式让我可以编写一些紧凑、健壮、高效的代码。用到这个模式的每一次,我都会抽空赞叹JavaScript的函数式编程能力。 字串6

JavaScript同时支持函数式和面向对象便程。市面上有很多重点着墨于面向对象设计模式的书都可以应用到JavaScript编程中。不过却没有多少书涉及函数式设计模式的例子。对于JavaScript社区来说,还需要很长时间来积累良好的函数式模式。

月落网

更新

字串6

这个模式虽然有趣,但由于大量使用闭包,可能会由于内存管理的不善而导致性能问题。来自 FCKeditor 的FredCK改进了getScrollY,既使用了这种模式,也避免了闭包: 月落

字串8

var getScrollY = function() { 字串9

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

字串7

    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)();

字串7

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

月落

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

www.yueluo.net

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

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

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

yueluo.net

getScrollY.case4 = function() {
        return NaN;
}; 月落网

yueluo.net

var getScrollY = function() {

字串8

    if (typeof window.pageYOffset == 'number') {
        getScrollY = function() {
            return window.pageYOffset;
        }; www.yueluo.net

    } else if ((typeof document.compatMode == 'string') &&
               (document.compatMode.indexOf('CSS') >= 0) &&
               (document.documentElement) &&
               (typeof document.documentElement.scrollTop == 'number')) {
        getScrollY = function() {
            return document.documentElement.scrollTop;
        }; 字串9

    } else if ((document.body) &&
               (typeof document.body.scrollTop == 'number')) {
      getScrollY = function() {
          return document.body.scrollTop;
      } 月落网

    } else {
      getScrollY = function() {
          return NaN;
      }; 字串8

    }

月落

    return getScrollY();
}

yueluo.net


Tags:网页制作,javascript
责任编辑:
您的评论
用户名:新注册) 密码: 匿名评论 [所有评论]

·用户发表意见仅代表其个人意见,并且承担一切因发表内容引起的纠纷和责任
·本站管理人员有权在不通知用户的情况下删除不符合规定的评论信息或留做证据
·请客观的评价您所看到的资讯,提倡就事论事,杜绝漫骂和人身攻击等不文明行为