Lazy.js : 让 JavaScript 变懒

简介: Lazy.js是类似Underscore或Lo-Dash的JavaScript工具库,但是它有一个非常独特的特性:惰性求值。很多情况下,惰性求值都将带来巨大的性能提升,特别是当处理巨大的数组和连锁使用多个方法的时候。

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


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

image.png

当数组非常大的时候,对于不需要迭代整个数组的方法,例如indexOftake,Lazy.js的性能提升更为惊人:

image.png

安装

Lazy.js没有外部依赖,所以加载Lazy.js非常方便:

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

如果你希望支持DOM事件序列的惰性求值,那么用这个:

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

如果你使用Node.js:

npm install lazy.js

简介

我们创建一个包含1000个整数的数组:

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

注意我们调用了toArray。如果没有这个,Lazy.range给我们的将不是一个数组而是一个Lazy.Sequence对象,你可以通过each来迭代这个对象。

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

functionsquare(x) { returnx * x; }

functioninc(x) { returnx + 1; }

functionisEven(x) { returnx % 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() { returnMath.random(); })

 .map(function(e) { returnMath.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;

 returnfunction() {

   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(newDate().getMilliseconds() + ": " + e);

});

很好。还有吗?


事件序列

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


现在我们要介绍一个Lazy.js的小扩展lazy.dom.js(基于浏览器的环境需要包含一个单独的文件),它组合了以上两个特性,现在,处理DOM事件也可以使用Lazy.js的力量了。换句话说,Lazy.js让你把DOM事件看成是一个序列——和其他序列一样——然后可以将那些用于序列的函数mapfilter应用到序列上。


下面是一个例子。比如我们打算处理给定的DOM元素的所有mousemove事件,同时显示它们的坐标。

// 首先我们定义事件序列

varmouseEvents=Lazy.events(sourceElement, "mousemove");

// 将事件序列和坐标相map

varcoordinates=mouseEvents.map(function(e) {

 varelementRect=sourceElement.getBoundingClientRect();

 return [

   Math.floor(e.clientX-elementRect.left),

   Math.floor(e.clientY-elementRect.top)

 ];

});

// 对于在元素一边的鼠标事件,在一个地方显示坐标

coordinates

 .filter(function(pos) { returnpos[0] <sourceElement.clientWidth / 2; })

 .each(function(pos) { displayCoordinates(leftElement, pos); });

// 对于元素另一边的鼠标事件,在另一处显示坐标

coordinates

 .filter(function(pos) { returnpos[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()方法将每段切割成行(当然了,切割是惰性的)。

相关文章
|
2天前
|
数据采集 JavaScript 前端开发
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
|
2月前
|
JavaScript 前端开发
JavaScript基础&实战(1)js的基本语法、标识符、数据类型
这篇文章是JavaScript基础与实战教程的第一部分,涵盖了JavaScript的基本语法、标识符、数据类型以及如何进行强制类型转换,通过代码示例介绍了JS的输出语句、编写位置和数据类型转换方法。
JavaScript基础&实战(1)js的基本语法、标识符、数据类型
|
2月前
|
JavaScript 前端开发
JavaScript基础&实战 JS中正则表达式的使用
这篇文章介绍了JavaScript中正则表达式的使用,包括正则表达式的创建、匹配模式、字符串匹配、拆分、搜索、匹配和替换等方法,并通过示例代码展示了如何应用这些技术。
JavaScript基础&实战 JS中正则表达式的使用
|
2月前
|
JavaScript 前端开发
JavaScript基础&实战(5)js中的数组、forEach遍历、Date对象、Math、String对象
这篇文章介绍了JavaScript中的数组、Date对象、Math对象以及包装类(String、Number、Boolean),并详细讲解了数组的创建、方法(如forEach、push、pop、unshift、slice、splice)和遍历操作,以及工厂方法创建对象和原型对象的概念。
JavaScript基础&实战(5)js中的数组、forEach遍历、Date对象、Math、String对象
|
2月前
|
JavaScript 前端开发
JavaScript基础&实战(4)js中的对象、函数、全局作用域和局部作用域
这篇文章介绍了JavaScript中对象的基本概念和操作,包括对象属性和方法的使用、对象字面量的创建、函数的定义和作用域的概念,以及全局作用域和局部作用域的区别和特性。
JavaScript基础&实战(4)js中的对象、函数、全局作用域和局部作用域
|
2月前
|
JavaScript 前端开发
JavaScript基础&实战(3)js中的流程控制语句、条件分支语句、for循环、while循环
这篇文章讲解了JavaScript中的流程控制语句,包括基本的if条件判断、弹窗提示输入、switch条件分支语句、while和do...while循环以及for循环的使用和示例。
JavaScript基础&实战(3)js中的流程控制语句、条件分支语句、for循环、while循环
|
2月前
|
JavaScript 前端开发
JavaScript基础&实战(2)js中的强制类型转换、运算符、关系运算符、逻辑运算符、条件运算符
这篇文章详细介绍了JavaScript中的强制类型转换、运算符(包括算术、逻辑、条件、赋值和关系运算符)的使用方法和优先级规则。
JavaScript基础&实战(2)js中的强制类型转换、运算符、关系运算符、逻辑运算符、条件运算符
|
2月前
|
Java 数据库连接 数据库
从零到精通:揭秘 Hibernate 构建持久层服务的全过程,你离数据持久化大师还有多远?
【8月更文挑战第31天】本文详细介绍了如何从零开始使用 Hibernate 构建一个持久层服务。首先,通过在 Maven 项目中添加必要的依赖,确保项目具备使用 Hibernate 的条件。接着,配置 `hibernate.cfg.xml` 文件以连接 MySQL 数据库,并设置了基本属性。然后定义了一个简单的 `User` 实体类及其映射关系。此外,还创建了一个 `HibernateUtil` 工具类来管理 `SessionFactory`。
30 0
|
2月前
|
缓存 JavaScript 前端开发
Vue.js与JavaScript性能优化终极揭秘:掌握这些技巧,让你的Web应用飞一般地流畅!
【8月更文挑战第30天】随着前端应用复杂度的增加,性能优化变得至关重要。本文深入探讨了如何利用Vue.js和JavaScript实现高效的应用性能。主要内容包括:优化组件设计以减少不必要的渲染,采用异步组件与懒加载技术加速应用启动,利用虚拟滚动和分页处理大数据集,改进Vuex使用方式以及合理运用浏览器缓存等策略。通过具体示例和最佳实践,帮助开发者充分挖掘Vue.js潜力,打造高性能的前端应用。
54 0
|
2月前
|
设计模式 JavaScript 前端开发
从工厂到单例再到策略:Vue.js高效应用JavaScript设计模式
【8月更文挑战第30天】在现代Web开发中,结合使用JavaScript设计模式与框架如Vue.js能显著提升代码质量和项目的可维护性。本文探讨了常见JavaScript设计模式及其在Vue.js中的应用。通过具体示例介绍了工厂模式、单例模式和策略模式的应用场景及其实现方法。例如,工厂模式通过`NavFactory`根据用户角色动态创建不同的导航栏组件;单例模式则通过全局事件总线`eventBus`实现跨组件通信;策略模式用于处理不同的表单验证规则。这些设计模式的应用不仅提高了代码的复用性和灵活性,还增强了Vue应用的整体质量。
26 0