JavaScript 延迟执行

标签 javascript linq

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

我很好奇 JavaScript 中是否有一个构造可以让我获得与 .Net 中相同的行为,在 .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; });),返回值为接口(interface)类型,实际处理集合(返回子集)仅包含 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”。


作为附录,我目前正在通过将我的方法原型(prototype)化到 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;
};

最佳答案

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

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; }

关于JavaScript 延迟执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19639262/

相关文章:

javascript - 如何知道 Firebase 更新后哪个值发生了变化?

javascript - 有没有办法使用 jQuery 在日历中创建 "pop up"事件?

c# - 嵌套 Linq 根据员工 ID 获取经理详细信息

c# - Web API 过滤器未捕获方法组引发的异常

c# - 在 ToDictionary 之前执行 ToList() 是否更好?

php - jQuery 在链接单击时从 url 添加和删除变量

javascript - javascript 上的 localStorage 带来刷新

javascript - 使用 JQuery 从不同的 php 文件获取数据

c# - 在txt文件中查找并删除整行

c# - 从 LINQ 查询返回一个结果