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是試驗性的,仍在開發中。項目主頁在此

相關文章
相關標籤/搜索