js框架开发之旅--函数式编程

函数式编程首先让人想到了面向过程过程编程。自从面向对象的编程出现之后,面向过程编程也只有在编程入门的教程中才能用的到。
让我们先看一下函数式编程的特点:
  • 专注于解决具体问题。
  • 把函数作为一等公民,并且可以作为变量来使用。
  • 避免数据的声明和变化。
想Erlang和Haskell都是函数式编程语言。js不是严格的函数式语言,但是比起Ruby,Python和Java它包含有更多函数式编程的特性。大部分语言语言,都使用了函数编程来简化一些简单的编程任务。
函数式语言一般注重列表的处理,把函数作为一等公民看待,并且具有像闭包等有用的功能。js的函数和闭包都非常强大,并且它的数组和对象也类似于类lisp语言的列表和属性列表。
我曾看到过Tom Stuart的一篇文章Thinking Functionally in Ruby。里面提到了一些方法让函数式编程更容易理解。如果你仍然对函数式编程的概念不太清楚,你可以看一下这篇文章。


迭代器

大部分js框架都提供了each方法迭代数组。并且许多框架的基础功能都大量使用了数组的迭代功能。
Ruby程序经常使用each迭代数组:
[1, 2, 3].each { |number| puts number }
# 1
# 2
# 3

上面的代码把一个块区(block)传给each,each会多次运行块区。
小牧注:花括号内的部分在Ruby中称为块区(block),number是每次迭代的数据元素,针对每个元素都会执行后面的代码。
Enumerable使用each扩展和许多方法,这些方法对函数式编程有很大的用处。任何集合类型的对象都继承了Enumerable的这些方法。
给JavaScript定义each方法如下:
Array.prototype.each = function(callback) {
  for (var i = 0; i < this.length; i++) {
    callback(this[i]);
  }
}

[1, 2, 3].each(function(number) {
  print(number);
});

其实Javascript支持 Array.forEach, Array.prototype.forEach, for (var i in objectWithIterator)的许多迭代的方法,为什么所有的框架还要定义自己的迭代方法呢?我们通过查看jQuery.each和Prototype的each方法找到了答案。因为浏览器对迭代方法的支持不尽相同。
你可以查看jQuery的core.js的源码。Prototye会判断浏览器是否支持forEach:
(function() {
  var arrayProto = Array.prototype,
      slice = arrayProto.slice,
      _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available

Underscore使用相同的方法:
  // The cornerstone, an each implementation.
  // Handles objects implementing forEach, arrays, and raw objects.
  // Delegates to JavaScript 1.6's native forEach if available.
  var each = _.forEach = function(obj, iterator, context) {
    try {
      if (nativeForEach && obj.forEach === nativeForEach) {
        obj.forEach(iterator, context);
      } else if (_.isNumber(obj.length)) {
        for (var i = 0, l = obj.length; i < l; i++) iterator.call(context, obj[i], i, obj);
      } else {
        for (var key in obj) {
          if (hasOwnProperty.call(obj, key)) iterator.call(context, obj[key], key, obj);
        }
      }
    } catch(e) {
      if (e != breaker) throw e;
    }
    return obj;
  };

上述方法使用了js本身的一些特性来实现迭代,而不是继承类似Enumerable这样的类来实现迭代。


性能

我写了一些例子用来测试这些方法的性能。你可以查看这些代码test.htmliteratortest.js
迭代方法是一个框架重要的基础方法,因此我们要找一个性能最好的迭代方式。
     Rhino     Node     Firefox     Safari     Chrome     Opera     IE8     IE7     IE6
eachNative     1428ms     69ms     709ms     114ms     62ms     1116ms             
eachNumerical     2129ms     55ms     904ms     74ms     58ms     1026ms     3674ms     10764ms     6840ms
eachForIn     4223ms     309ms     1446ms     388ms     356ms     2378ms     4844ms     21782ms     14224ms

通过比较发现原生的迭代表现的更好一些,这也是为什么大部分框架选择使用原生的迭代支持(如果存在的话)。for...in在ie里表现的非常糟糕,因此我们要慎重使用。


API设计

好的API设计是一个函数式编程要重点考虑的问题。Prototype通过修改Object和Array的原型来添加迭代方法。这个方法使接口的使用变得简单,但是却不利于和其他框架的共存,这不是一个安全的做法。
我们提供的接口是这样的:
turing.enumerable.each([1, 2, 3], function(number) { number + 1; });
turing.enumerable.map([1, 2, 3], function(number) { return number + 1; });
// 2, 3, 4
这些方法日后会映射到标准的方法


测试

我们做一个基本了测试,来保证数组可以正常的遍历:
Riot.context('turing.enumerable.js', function() {
  given('an array', function() {
    var a = [1, 2, 3, 4, 5];
    should('iterate with each', function() {
      var count = 0;
      turing.enumerable.each(a, function(n) { count += 1; });
      return count;
    }).equals(5);

    should('iterate with map', function() {
      return turing.enumerable.map(a, function(n) { return n + 1; });
    }).equals([2, 3, 4, 5, 6]);
  });
});

我们换成数组也可以正常工作:
given('an object', function() {
  var obj = { one: '1', two: '2', three: '3' };
  should('iterate with each', function() {
    var count = 0;
    turing.enumerable.each(obj, function(n) { count += 1; });
    return count;
  }).equals(3);

  should('iterate with map', function() {
    return turing.enumerable.map(obj, function(n) { return n + 1; });
  }).equals(['11', '21', '31']);
});

我的实现更多的借鉴了Underscore,因为它相对更容易理解,也符合我对性能的要求。可以这里查看代码: turing.enumerable.js


总结

这一章我们阐述了函数式编程的一些概念,实现了each方法,这样我们就可以基于迭代扩展方法来处理集合数据。我测试了不同的迭代实现的性能,并且讨论了不同类库是如何实现迭代的。
我之前提到过js是一个动态语言,静态语言的类型一旦定义就不能再变了。要想改变数据的类型,就必须新建一个变量,然后转换过去。在动态语言里,我们在定义时可以不确定变量的类型,当我们用到时,再通过赋值来确定变量的类型,这样有利于性能的提高。


扩展阅读


牧客网--让自由职业成为一个靠谱的工作


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值