Lazy.js : 让 JavaScript 变懒

Lazy.js是相似UnderscoreLo-Dash的JavaScript工具库,可是它有一个很是独特的特性:惰性求值。不少状况下,惰性求值都将带来巨大的性能提高,特别是当处理巨大的数组和连锁使用多个方法的时候。javascript

Lazy.js的网站上展现了与Underscore、Lo-Dash比较的图表:html

benchmark lazyjs underscore Lo-Dash

当数组很是大的时候,对于不须要迭代整个数组的方法,例如indexOftake,Lazy.js的性能提高更为惊人:java

benchmark lazyjs underscore Lo-Dash

安装

Lazy.js没有外部依赖,因此加载Lazy.js很是方便:node

<script type="text/javascript" src="lazy.min.js"></script>

若是你但愿支持DOM事件序列的惰性求值,那么用这个:git

<script type="text/javascript" src="lazy.dom.js"></script>

若是你使用Node.js:github

npm install lazy.js

简介

咱们建立一个包含1000个整数的数组:npm

var array = Lazy.range(1000).toArray();

注意咱们调用了toArray。若是没有这个,Lazy.range给咱们的将不是一个数组而是一个Lazy.Sequence对象,你能够经过each来迭代这个对象。segmentfault

如今咱们打算取每一个数字的平方,增长一下,最后取出前5个偶数。为了保持代码简短,咱们使用这些辅助函数:api

function square(x) { return x * x; }
function inc(x) { return x + 1; }
function isEven(x) { return x % 2 === 0; }

这是一个奇怪的目标。无论怎么样,咱们能够用Underscore的chain方法实现它:数组

var result = _.chain(array).map(square).map(inc).filter(isEven).take(5).value();

注意上面这行语句作了多少事情:

  • map(square)迭代了整个数组,建立了一个新的包含1000个元素的数组
  • map(inc)迭代了新的数组,建立了另外一个新的包含1000个元素的数组
  • filter(isEven)迭代了整个数组,建立了一个包含500个元素的新数组
  • take(5)这一切只是为了5个元素!

若是你须要考虑性能,你可能不会这么干。相反,你会写出相似这样的过程式代码:

var results = [];
for (var i = 0; i < array.length; ++i) {
  var value = (array[i] * array[i]) + 1;
  if (value % 2 === 0) {
    results.push(value);
    if (results.length === 5) {
      break;
    }
  }
}

如今咱们没有建立任何多余的数组,在一次迭代中完成了一切。有什么问题么?

好吧。最大的问题在于这是一次性的代码,咱们花了一点时间编写了这段代码,却没法复用。要是咱们可以利用Underscore的表达力,同时获得手写的过程式代码的性能,那该多好啊!

这就是Lazy.js该发威的时候了。用 Lazy.js,上面的代码会写成:

var result = Lazy(array).map(square).map(inc).filter(isEven).take(5);

看上去和用Underscore的代码几乎同样?正是如此:Lazy.js但愿带给JavaScript开发者熟悉的体验。每一个Underscore的方法应该和Lazy.js有相同的名字和表现,惟一的不一样是Lazy.js返回一个序列对象,以及相应的each方法。

重要的是,直到你调用了each才会产生迭代,并且不会建立中间数组。 Lazy.js将全部查询操做组合成一个序列,最终的表现和咱们开始写的过程式代码差很少。

固然,与过程式代码不一样的是,Lazy.js确保你的代码是干净的,函数式的。这样你就能够专一于构建应用,而不是优化遍历数组的代码。

特性

酷!Lazy.js还能作什么?

生成无穷序列

是的,无穷序列,无穷无尽!一样支持全部Lazy内建的map和filter功能。

看个例子吧。假设咱们须要在1和1000之间获取300个不一样的随机数:

var uniqueRandsFrom1To1000 = Lazy.generate(function() { return Math.random(); })
  .map(function(e) { return Math.floor(e * 1000) + 1; })
  .uniq()
  .take(300);

// 输出:亲眼看看吧
uniqueRandsFrom1To1000.each(function(e) { console.log(e); });

至关不错。换一个高级点的例子吧。让咱们用Lazy.js建立一个斐波那契数列。

var fibonacci = Lazy.generate(function() {
  var x = 1,
      y = 1;
  return function() {
    var prev = x;
    x = y;
    y += prev;
    return prev;
  };
}());

// 输出: undefined
var length = fibonacci.length();

// 输出: [2, 2, 3, 4, 6, 9, 14, 22, 35, 56]
var firstTenFibsPlusOne = fibonacci.map(inc).take(10).toArray();

不错,还有什么?

异步迭代

你之前多半见过如何在JavaScript中异步迭代数组的代码片断。可是你见过这样的吗?

var asyncSequence = Lazy(array)
  .async(100) // 100毫秒
  .map(inc)
  .filter(isEven)
  .take(20);

//  这个函数会立刻返回,而后开始异步迭代
asyncSequence.each(function(e) {
  console.log(new Date().getMilliseconds() + ": " + e);
});

很好。还有吗?

事件序列

咱们看到,和Underscore和Lo-Dash不一样,对于无穷序列,Lazy.js并不须要把一个把全部数据放到内存以便迭代。异步序列也显示了它并不须要一次完成全部迭代。

如今咱们要介绍一个Lazy.js的小扩展lazy.dom.js(基于浏览器的环境须要包含一个单独的文件),它组合了以上两个特性,如今,处理DOM事件也可使用Lazy.js的力量了。换句话说,Lazy.js让你把DOM事件当作是一个序列——和其余序列同样——而后能够将那些用于序列的函数mapfilter应用到序列上。

下面是一个例子。好比咱们打算处理给定的DOM元素的全部mousemove事件,同时显示它们的坐标。

// 首先咱们定义事件序列
var mouseEvents = Lazy.events(sourceElement, "mousemove");

// 将事件序列和坐标相map
var coordinates = mouseEvents.map(function(e) {
  var elementRect = sourceElement.getBoundingClientRect();
  return [
    Math.floor(e.clientX - elementRect.left),
    Math.floor(e.clientY - elementRect.top)
  ];
});

// 对于在元素一边的鼠标事件,在一个地方显示坐标
coordinates
  .filter(function(pos) { return pos[0] < sourceElement.clientWidth / 2; })
  .each(function(pos) { displayCoordinates(leftElement, pos); });

// 对于元素另外一边的鼠标事件,在另外一处显示坐标
coordinates
  .filter(function(pos) { return pos[0] > sourceElement.clientWidth / 2; })
  .each(function(pos) { displayCoordinates(rightElement, pos); });

还有么?固然!

字符串处理

这多是你不会想到过的东西:String.matchString.split。在JavaScript中,这两个方法会返回包含子字符串的数组。若是你这么作,一般意味着JavaScrit会作一些没必要要的事。可是从开发者的角度而言,这是完成任务最快的方法。

例如,你想从一段文本中抽取出前5行。你固然能够这么作:

var firstFiveLines = text.split("\n").slice(0, 5);

固然,这意味着将整个字符串分割成单行。若是这个字符串很是大,这很浪费。

有了Lazy.js,咱们不用分割整个字符串,咱们只需将它当作行的序列。将字符串用Lazy包裹以后再调用split,能够取得一样的效果:

var firstFiveLines = Lazy(text).split("\n").take(5);

这样咱们就能够读取任意大小的字符串的前5行(而不须要预先生成一个巨大的数组),而后像对其余序列同样使用map/reduce

String.match同理。例如咱们须要找出字符串中最前面5个数字或字母。使用Lazy.js,这很容易!

var firstFiveWords = Lazy(text).match(/[a-z0-9]+/i).take(5);

小菜一碟。

流处理

在Node.js中,Lazy.js一样能够封装流。

给定一个可读流,你能够像封装数组同样用Lazy包裹一番:

Lazy(stream)
  .take(5) // 仅仅阅读数据中的前五块内容
  .each(processData);

为了方便,Lazy.js也提供了处理文件流和HTTP流的专门辅助方法。(注意:API将来可能会改变。)

// 读取文件的前5行
Lazy.readFile("path/to/file")
  .lines()
  .take(5)
  .each(doSomething);

// 从HTTP响应中读取5-10行
Lazy.makeHttpRequest("http://example.com")
  .lines()
  .drop(5)
  .take(5)
  .each(doSomething);

lines()方法将每段切割成行(固然了,切割是惰性的)。


Lazy.js是试验性的,仍在开发中。项目主页在此

相关文章
相关标签/搜索