我是个刚入行前端的新人,这是我工作之余写的库,Linq.js(Github)源代码兼容ES5,主流的浏览器都可用!他主要提供两个类,Iterator和Iterable,有一系列对数据集合进行操作的方法,传统Linq能做到的都能做到。
什么是Iterator?
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
ES6的Iterator对象,有一个基本的方法next,每一次调用该方法,会得到一个对象。{value:any,done:bool}
value迭代器当前的值,done是表示迭代器的结束状态,true结束,false未结束。
在ES6,可以这样得到一个迭代器。
var iter = [2, 4, 6, 8][Symbol.iterator]();
iter.next(); //{value: 2, done: false}
iter.next(); //{value: 4, done: false}
iter.next(); //{value: 6, done: false}
iter.next(); //{value: 8, done: false}
iter.next(); //{value: undefined, done: true}
//...
iter.next(); //{value: undefined, done: true}
也可以自己构造一个迭代器。
//这是个自然数迭代器,无穷无尽
var iter = {
next: function () {
var index = 0;
this.next = function () {
return { value: index++, done: false };
}
return this.next();
}
};
iter.next(); //{value: 0, done: false}
iter.next(); //{value: 1, done: false}
iter.next(); //{value: 2, done: false}
//...
ES6有专门的访问迭代器的语法:
for (var x of [2, 4, 6, 8]) {
console.log(x);
}
这里的结果依次是打印2 4 6 8。
结合本文的第一个例子,可以发现,如果令一个对象的Symbol.iterator属性为一个迭代器的构造方法,同样可以用for…of…来遍历该对象。
var myIterable = {};
myIterable[Symbol.iterator] = function () { return Natural; };
for (var x of myIterable) {
console.log(x);
if (x === 10) break;
}
这里的结果依次是打印 3 4 5 6 7 8 9 10
这里从3开始,从句子意义上来说,是不对的。不过也合情合理,因为我没有在该构造器里返回一个新的迭代器。所以由于第二个例子对Natural进行了三次next,这时的Natural输出就从3开始了。
如果我把if (x === 10) break; 去掉,就会进入死循环,因为for..of…要得到一个属性done为true的对象才会自动停止。
于是可以将for…of..的循环过程视为,调用迭代器的next方法,对返回的对象进行判断。如果done为true时就退出;done为false时就返回value的值。
此时没有退出的话,将对语句体进行处理,然后重复一开始的调用及判断行为。
以上就是对ES6的Iterator的介绍。更加详细的介绍可以参考下阮一峰的文档。
什么是Linq?
LINQ,语言集成查询(Language Integrated Query)。
网上已经有好多关于Linq的资料,但没有一个比较统一的说法,我就根据自身经验来介绍吧。
Linq就是访问集合的范式。这种范式,能让我们以同样的形式去访问各种各样的数据集合。
在C#就有Linq to Objects,Linq to XMl,Linq to EF……
通俗来说,就是可以用“同样”的方法对不同的数据进行访问。
Linq的操作有投影,过滤,排序,联接,分组,聚合等等。
而且在C#linq还有强大的查询表达式(Query Expression),能用简便的类SQL的语法去表示些复杂的操作。
如:
var m = from n in arr where n < 5 orderby n descending select n; //选出数组中小于5的元素,并且倒叙排列。
如果不习惯这种表达式,可以使用一般的方法语法(Fluent Syntax)。(注:查询表达式执行时也是转化成方法语法。)
如:
var m = arr.Where(n => n < 5).OrderByDescending(n => n); //选出数组中小于5的元素,并且倒叙排列。
遗憾的是,我在JS所实现的Linq,没有做查询表达式这个功能。
在C#,只要一个对象实现了IEnumerator接口,该对象就能成为供Linq访问的迭代器。只要一个对象实现了IEnumerable接口,就能用Linq对其操作。
必须说到的是,IEnumerbale只需要实现一个GetEnumerator方法,该方法的作用是返回一个IEnumerator对象。其余的Linq方法,包括上文出现的whrere,OrderByDescending都是默认实现了的方法,不需要用户去实现。
也就是说,只要给出一个集合的迭代方法,就能使用Linq集成的所有访问集合的方法,非常方便。
Linq还有个很重要的特性,每个元素都是在访问的时候才被迭代器产生出来。使用得当,对一个集合进行多步操作,对数据源的迭代操作只会进行一次,性能上并不比自己调度的代码差。
以上就是对Linq的简单介绍。
如何使用ES6的Iterator实现Linq?
接下来介绍的是,我基于ES6的Iterator实现的Linq。
联系上文,可以知道IEnumerable等价于第一篇Iterator介绍里的拥有迭代器构造方法的对象。
在JS实现Linq也是基于这个观念。
我用Iterable取代IEnumerable,用Iterator取代IEnumerator。
Iterable.prototype实现各种Linq方法,所有的linq方法都是对Iterable对象的Symbol.iterator属性所构造的迭代器进行操作。
Linq方法在操作层次上分为两种,一种是返回Iterable对象的延迟操作,一种是触发迭代器next的生产操作。
//这是一个表示自然数的Iterable对象,他每次产生值都会打印这个值。
var natural = Iterable(function () {
var index = 0;
return {
next: function () {
console.log(index);
return { value: index++, done: false };
}
};
});
//自然数里前10个是既是2的倍数又不是3的倍数的数字。
var foo = natural.Where(function (v) { return v % 2 === 0; })
.Where(function (v) { return v % 3 !== 0; })
.Take(10);
此时foo并没有打印任何数据。这是上文所述的延迟操作。
再次使用Linq的ToArray方法。
foo.ToArray(); //[2, 4, 8, 10, 14, 16, 20, 22, 26, 28]
会在后台发现他从0打印到了32。这是上文所述的生产操作。
JS跟C#还是存在不少差异的,于是在JS上实现LInq也要做一些本地化的处理。
以下是一些与C#上的Linq不同的地方:
① 为了提高自由度,不主动抛出异常。
② 部分方法没有实现:Single,Cast,AsEnumerable,ToList。
③ 用ToMap方法代替ToDictionary方法。ES6的Map很好地代替了C#的Dictionary呢。
使用示例
简单地举两个使用场景。
var data = {
"users": [
{
"id": 6287,
"slug": "5SqsuF",
"nickname": "刘淼",
"avatar_source": "http://upload.jianshu.io/users/upload_avatars/6287/06c537002583.png",
"total_wordage": "307.3K",
"total_likes_count": "16.9K",
"subscription_id": 6183
},
{
"id": 326721,
"slug": "5cfa376301c5",
"nickname": "傅踢踢",
"avatar_source": "http://upload.jianshu.io/users/upload_avatars/326721/fbb53af4d452.jpg",
"total_wordage": "409.5K",
"total_likes_count": "11.1K",
"subscription_id": 334010
},
{
"id": 186093,
"slug": "0782ca5a0f41",
"nickname": "philren",
"avatar_source": "http://upload.jianshu.io/users/upload_avatars/186093/8f9c1cb18375.jpg",
"total_wordage": "251.0K",
"total_likes_count": "4.2K",
"subscription_id": 183924
},
{
"id": 1933412,
"slug": "fd0599061897",
"nickname": "一棵花白",
"avatar_source": "http://upload.jianshu.io/users/upload_avatars/1933412/2a8a65019896.jpg",
"total_wordage": "223.9K",
"total_likes_count": "11.8K",
"subscription_id": 1923632
},
{
"id": 1211570,
"slug": "4b9ff86a7af4",
"nickname": "毒舌电影",
"avatar_source": "http://upload.jianshu.io/users/upload_avatars/1211570/ef87d476-c24a-4645-91df-49c729f01b78.png",
"total_wordage": "3354.3K",
"total_likes_count": "79.2K",
"subscription_id": 1176787
},
{
"id": 233,
"nickname": "LF2",
"total_wordage": "223.9K",
"total_likes_count": "0K",
}
],
"total_page": 39,
"current_page": 1
};//简书上随手弄来的数据
var src = Iterable(data.users); //Iterable可以接受各种参数。构造迭代器的function,数组甚至是普通的object。
var f1 = src.OrderByDescending(function (item) { return Number(item.total_wordage.slice(0, -1)); })
.ThenBy(function (item) { return item.id;})
.Select(function (v) { return { nickname: v.nickname, total_wordage: v.total_wordage }; })
.ToArray(); //按写作字数按降序排序,写作字数一样时按id升序排序,并且返回由名字跟写作字数组成的对象的数组。
var f2 = src.Max(function (item) { return Number(item.total_likes_count.slice(0, -1)); }); //返回最大的粉丝数;返回79.2
var obj1 = {
id: 233,
name: 'LF2',
price: 6300,
count: 1
};
var obj2 = {
price: 6300,
count: 1,
id: 666,
name: 'MM'
}
var f3 = Iterable(obj1)
.Except(Iterable(obj2), function (x, y) { return x.key === y.key; })
.Any(); //求两个对象属性是否存在差集;返回false。(可以用来判断是否一种对象。)
var opt1 = {
url: '',
data: {},
success:Function()
}
var opt2 = {
beforeSend: Function(),
complete:Function()
}
var f4 = Iterable(opt1)
.Union(Iterable(opt2), function (x, y) { return x.key === y.key; })
.ToArray(); //这是求两个对象属性的合集。(啊咧咧,其实应该能返回Object,我应该开始做C#上没有的东西么。)
在ES6还能用for…of…的语法对Iterable对象进行遍历操作。
for (var x of Iterable("Hello World!").SkipWhile(function (v) { return v !== ' '; }).Reverse()) {
console.log(x);
}//直到遇到W之前都跳过,然后倒序;结果:! d l r o W
总结
这个玩意其实还有进一步的改进,不局限于Linq to Objects里已有的方法,可以根据JS的特性新增几个方法。譬如说Max可以返回对象而不止于数值,可以ToObject等等。感谢大家的阅读,欢迎指出bug以及建议。