JavaScript 作用域规则和 MongoDB 的 map/reduce 函数

4
我想要创建一些JavaScript函数,在mongo中执行参数化的map/reduce作业,但是我对JavaScript的范围感到困惑。例如,以下代码会给我计算“gender”变量的次数;即它将告诉我有多少条“male”和“female”的记录:
// count categories
db.responses.mapReduce(
    function(){
        emit(this["gender"], {count: 1})
    }, function(state, values){
        var result = {count: 0};
        values.forEach(function(value) {
            result.count += value.count;
        });
        return result;
    }, {out: { inline : 1}}
);

这个可以完美地工作。接下来我想创建一个函数,使其可以针对任意属性实现此功能。

function countCategories(item) { 
    function mapper(it){
        fn = function(){
            if(this[it]){
                emit(this[it], {count: 1});
            }
        };
        return fn;
    }(item); 
    var reducer = function(state, values){
        var result = {count: 0};
        values.forEach(function(value) {
            result.count += value.count;
        });
        return result;
    };
    var out = {out: { inline : 1}};
    var results = db.responses.mapReduce(
        mapper, 
        reducer, 
        out
    );
    return results;
}   

countCategories("gender")

然而,当我尝试执行以下操作时:
countCategories("gender")
{
    "results" : [ ],
    "timeMillis" : 48,
    "counts" : {
        "input" : 2462,
        "emit" : 0,
        "reduce" : 0,
        "output" : 0
    },
    "ok" : 1,
}

emit函数从未被调用。出了什么问题? 我猜测是mongo提供的emit函数作用域有问题,但我不确定为什么它没有被调用或抛出错误。


你根本不需要闭包,因为item已经在countCategories的作用域中定义了。你为什么不直接将fn分配给mapper呢? - Bergi
因为那个方法不起作用了...顺便我稍微改了一下问题。 - Jeroen Ooms
当然,那并不是一个解决方案的意图... - Bergi
2个回答

3

根据我在文档中阅读到的内容,数据库命令中使用的函数范围不是它们默认的JavaScript范围,而是可以(如果需要)手动设置。所以,我认为这应该可以工作:

var mapper = function(){
    if(item in this){
        emit(this[item], {count: 1});
    }
};
...
db.responses.mapReduce(
    mapper, 
    reducer, 
    {
       out: {"inline": 1},
       scope: {"item": item}
    }
);

0

我认为您在mapper的声明方面存在问题:

您的代码:

function mapper(it){
    fn = function(){
        if(this[it]){
            emit(this[it], {count: 1});
        }
    };
    return fn;
}(item); 

由于 JavaScript 语法/提升/分号插入等问题,它是等效的:

function mapper(it){
    fn = function(){
        if(this[it]){
            emit(this[it], {count: 1});
        }
    };
    return fn;
}

item; 

你的函数语句声明了一个你想要立即执行的函数,但实际上并没有执行。

item;这样单独使用在JavaScript中是完全可以的。它不会做任何事情,但是它是有效的。(就像"use strict";

你需要的是一个函数表达式

var mapper = (function (it){
    fn = function(){
        if(this[it]){
            emit(this[it], {count: 1});
        }
    };
    return fn;
})(item); 

这里的重要部分不是在一行开头只有function。函数表达式周围的()并非必需,但它表示您立即执行该函数。如果您没有将其赋值给变量,则需要它们;以避免以function开头的行。

他之前曾经有过这个,但是不起作用。 - Bergi
@Bergi 嗯,至少这解释了为什么你没有收到任何错误。许多内部函数 fn 被创建并丢弃,而从未被调用。 - Odalrick
我认为这里的根本问题是Mongo以某种方式干扰了函数的环境,以便在其范围内注入emit()函数。 - Jeroen Ooms

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接