3

对于个人挑战,我正在 JavaScript 中实现 LINQ(嗯,一组具有类似 LINQ 功能的函数)。但是,截至目前,这些函数正在立即处理数据;这对于某些函数(例如 Sum 或 Aggregate)是正确的行为,但对于其他函数(例如 Select 或 While)则不正确。

我很好奇 JavaScript 中是否有一个构造可以让我获得与 .Net 中相同的行为,在枚举集合或使用立即执行的函数之前,不会发生真正的处理。

注意:我相信这个任务(在 JS 中实现 LINQ)已经完成了。那不是重点。这是我自己对自己的挑战,这很可能会帮助我增加对 LINQ(以及,巧合的是,JS)的理解。除了个人的熏陶,我很快就会在工作中使用 LINQ,可能会根据个人项目的需要将 JS 用于我的工作,并且我会将 JS 用于工作之外的一些事情。

编辑:似乎我吸引了不熟悉 LINQ 的人,所以我想我应该在这方面给出一些解释。LINQ 是 Language-INtegrated Query,来自 .Net。LINQ 允许对许多数据源(包括实际的 SQL 关系数据库)进行类似 SQL 的查询,例如 LINQ to Objects,这是我想要实现的。

LINQ 的特性之一是延迟执行许多方法。如果我有一个集合customers并调用var query = customers.Where(c => c.Age > 40);(或者它最终会在 JS 中出现var query = customers.Where(function (c) { return c.Age > 40; });),则返回值是一个接口类型,并且集合的实际处理(返回仅包含 40 岁以上客户的集合子集)还没有发生。当我使用其中一种没有延迟执行的方法(例如,query.First()query.ToArray())时,所有的延迟处理都会发生。这可以是一个链,例如customers.Where(...).Skip(5).Select(...).OrderBy(...)(每个“...”都是一个函数)。

结果是这样的代码:

var collection = [1, 2, 3, 4, 5];
var query = collection.Where(function (n) { return n % 2 == 0; });
collection.push(6);
alert(query.Max());

将导致“6”。


作为附录,我目前正在通过将我的方法原型化到 Object 和 Array 上来实现这个项目,迭代 的元素this,并跳过任何作为函数的元素。像制作 Enumerable 类这样的东西可能更好(事实上,如果需要返回函数或匿名对象等东西,我的延迟执行计划可能需要它),但这就是我目前所拥有的。我的功能通常表现为以下内容:

Object.prototype.Distinct = Array.prototype.Distinct = function (comparer) {
    comparer = comparer || function (a, b) { return a == b; };

    var result = [];
    for (var idx in this) {
        var item = this[idx];
        if (typeof item == "function") continue;
        if (!result.Contains(item, comparer)) result.push(item);
    }
    return result;
};
4

2 回答 2

2

从根本上说,您需要做的是从您的函数中返回对象,而不是执行操作。您返回的对象将包含将来执行操作所需的代码。考虑一个示例用例:

var myCollection = [];
for(var i = 0; i < 100; i++) { myCollection.push(i); }

var query = Iter(myCollection).Where(function(v) { return v % 2 === 0; })
    .Skip(5).Select(function(v) { return v*2; });

var v;
while(v = query.Next()) {
    console.log(v);
}

我们期望输出:

20
24
28
...
188
192
196

为了做到这一点,我们定义了方法 .Where()、.Skip() 和 .Select() 以返回具有重写版本的 .Next() 方法的类的实例。支持此功能的工作代码:(将 trace 设置为 true 以观察执行顺序是惰性的)

var trace = false;

function extend(target, src) {
    for(var k in src) {
        target[k] = src[k];
    }
    return target;
}

function Iter(wrapThis) {
    if(wrapThis.Next) {
        return wrapThis;
    } else {
        return new ArrayIter(wrapThis);
    }
}

Iter.prototype = {
    constructor: Iter,
    Where:  function(fn) { return new WhereIter(this, fn); },
    Skip:   function(count) { return new SkipIter(this, count); },
    Select: function(fn) { return new SelectIter(this, fn); }
};

function ArrayIter(arr) {
    this.arr = arr.slice();
    this.idx = 0;
}

ArrayIter.prototype = extend(Object.create(Iter.prototype),
{
    constructor: ArrayIter,
    Next: function() {
        if(this.idx >= this.arr.length) {
            return null;
        } else {
            return this.arr[this.idx++];
        }
    }
});

function WhereIter(src, filter) {
    this.src = src; this.filter = filter;
}

WhereIter.prototype = extend(Object.create(Iter.prototype), {
    constructor: WhereIter,
    Next: function() {
        var v;
        while(true) {
            v = this.src.Next();
            trace && console.log('Where processing: ' + v);
            if(v === null || this.filter.call(this, v)) { break; }
        }
        return v;
    }
});

function SkipIter(src, count) {
    this.src = src; this.count = count;
    this.skipped = 0;
}

SkipIter.prototype = extend(Object.create(Iter.prototype), {
    constructor: SkipIter,
    Next: function() {
        var v;
        while(this.count > this.skipped++) {
            v = this.src.Next();
            trace && console.log('Skip processing: ' + v);
            if(v === null) { return v; }
        }
        return this.src.Next();
    }
});

function SelectIter(src, fn) {
    this.src = src; this.fn = fn;
}

SelectIter.prototype = extend(Object.create(Iter.prototype), {
    constructor: SelectIter,
    Next: function() {
        var v = this.src.Next();
        trace && console.log('Select processing: ' + v);
        if(v === null) { return null; }
        return this.fn.call(this, v);
    }
});

var myCollection = [];
for(var i = 0; i < 100; i++) {
    myCollection.push(i);
}

var query = Iter(myCollection).Where(function(v) { return v % 2 === 0; })
    .Skip(5).Select(function(v) { return v*2; });

var v;
while(v = query.Next()) {
    console.log(v);

}

您可能还想研究“字符串 lambdas”以使您的查询更具可读性。那会让你说"v*2"而不是function(v) { return v*2; }

于 2013-10-28T16:53:56.997 回答
0

我不完全清楚你到底想做什么,但我认为你应该研究的是defineProperty方法。然后,您可能希望做的是重新定义.length属性并仅在读取代码后才执行代码。或者,如果您只想在读取属性本身后执行此操作,请在此时执行此操作。不知道 LINQ 是如何工作的,甚至不知道它是什么,所以这就是我有点含糊的原因。无论哪种方式,defineProperty你都可以做类似的事情

Object.defineProperty(o, "a", { get : function(){return 1;});

允许您仅在访问属性后执行操作(您还可以做更多的事情)。

于 2013-10-28T16:02:01.053 回答