如何理解NODEJS的回调函数中的上下文,这里需要用到闭包吗?
发布于 11 年前 作者 jbasttdi 15607 次浏览 最后一次编辑是 8 年前

今天对以前写的一段代码产生了疑惑,请问是不是在任何情况下,这两个值都相同?

exports.ts=function(data,callback){ 
    var query={_id:data.id};
    //var oldid=retextra(data.id);
    Mydb.findOne(query,{},{},function(err,result){ 
        if(err){
            console.log('Err:'+err);
            return callback(err);
        }
        var now=new Date().getTime();
        while(new Date().getTime() < now + 3000) {

        }
        console.log('传入值='+data.id+ ' 返回的值='+result._id);
       //console.log('原来传入值通过闭包获取的值:'+oldid());
        callback(err,result);
    });
};

我的想法是当不断地调用这个函数,传入不同的id值,会导致在回调函数中data.id取到的值与result._id返回的值不相同,因为data的作用域与result的作用域不相同,当在回调函数中获取data时,可能取到的值不是那时传入的值。 我也试着浏览器多次调用这个方法,但总返回相同值。求解惑。 btw: 我的想法是先把data值包在闭包中,再在result中可获取当时的值。闭包函数: function retextra(exdata) { var ts = exdata; function getext() { return ts; } return getext; } 但现在也有点搞不清楚了,是不是不用闭包也可以了。

8 回复

这是个好问题啊。

我也试着浏览器多次调用这个方法,但总返回相同值 

因为本来就只会返回相同值

你每次调用 exports.ts 时,都会产生不同的闭包来保存 data 和 result。所以它们是互不影响的。

多次调用 exports.ts,调用之间互不影响

function(data,callback) 这一句创建了一个 data 局部变量。这个局部变量每次调用都产生一个新的,而不是跨调用存在的。

你好,我知道用闭包是不会有冲突的,但好像现在不用闭包也不会有问题了。我试着在一个循环中不断改变id值并调用这个方法,结果两个值也是一样的。

测试代码如下: var myapi=require(’./action/myapi’); server.get(’/ts’,myapi.ts); //route设置

myapi.js中的方法: exports.ts=function(req,res,next){ var dt=[ ‘52e22790627642942467ea27’ ,‘52e22853e1c6a3782533aee7’ ,‘52e22854e1c6a3782533aee8’ ,‘52e22856e1c6a3782533aee9’ ,‘52e2286be1c6a3782533aeea’ ,‘52e22f9741f672600c7b25bb’ ,‘52e22fac41f672600c7b25bc’ ,‘52e23013b7ceecc00f8158f3’ ,‘52e23016b7ceecc00f8158f4’ ,‘52e23036b7ceecc00f8158f6’ ]; for(var i=0;i<10;i++){ var so={ id:dt[i] }; mydao.daots(so,function(err,result){ console.log(‘传入id=’+so.id+’ 返回的id=’+result._id); //不用闭包,这里的两个值确实不一样。 为什么? res.send(‘finished’); return next(); }); } };

mydao.js中的daots方法: exports.daots=function(data,callback){ var query={_id:data.id}; FindingPerson.findOne(query,{},{},function(err,result){ if(err){ console.log(‘Err:’+err); return callback(err); } var now=new Date().getTime(); while(new Date().getTime() < now + 3000) { } console.log(‘Dao层传入的id=’+data.id+ ’ Dao层返回的id=’+result._id); //结果返回中这里的两个值永远相同 ,为什么? callback(err,result); }); };

每次你调用 mydao.js中的daots方法时,参数 data 都保存了对于该次调用的 so 的引用。

而在

mydao.daots(so,function(err,result){ console.log(‘传入id=’+so.id+’ 返回的id=’+result._id); //不用闭包,这里的两个值确实不一样。 为什么? res.send(‘finished’); return next();

中,‘传入id=’+so.idso.id 却保存的是紧接着 for 循环下面那个 so 的引用。

console.log(‘传入id=’+so.id+’ 返回的id=’+result._id);

这一句中, so.id 的值都是数组的最后一个值。

感谢alsotang的指导,整理一下。 其实当时我是因为没搞清楚这个回调函数的作用域的问题,导致对回调和匿名函数没有一个清晰的认识。 写清楚点应该是这样的,

myapi.js中的方法: exports.ts=function(req,res,next){ var dt=[ ‘52e22790627642942467ea27’ ,‘52e22853e1c6a3782533aee7’ ,‘52e22854e1c6a3782533aee8’ ,‘52e22856e1c6a3782533aee9’ ,‘52e2286be1c6a3782533aeea’ ,‘52e22f9741f672600c7b25bb’ ,‘52e22fac41f672600c7b25bc’ ,‘52e23013b7ceecc00f8158f3’ ,‘52e23016b7ceecc00f8158f4’ ,‘52e23036b7ceecc00f8158f6’ ]; for(var i=0;i<10;i++){ var so={ id:dt[i] }; /* mydao.daots(so,function(err,result){ //不用闭包,这里的两个值确实不一样。 为什么? console.log(‘传入id=’+so.id+’ 返回的id=’+result._id); res.send(‘finished’); return next(); }); */ //这里给mydao.daots的回调函数取个名cb,它的两个传入参数err,result,是由mydao.daots每次执行 //后都会提供的,会是具体的值,与循环多少次,谁调用都没关系,每次都会是新的值。 //由于js内部变量(即var定义的变量)作用域的关系,即在同一个函数中,var定义的变量可以被 //函数中任何地方引用到,它的作用域是整个函数,而不是只限制在大括号{}中 //这里定义的cb函数中可以引用外面的so的值,是因为这个function cb()被定义在了最外面的ts方 //法中,而so这个变量在虽然在for(var i=0;i<10;i++){ var so…这个循环体中定义,但因为它 //也是在这个function ts()这个方法中且在调用cb时so已经被定义出来,所以cb中也可以访问so. function cb(err,result){

            console.log('传入id='+so.id+' 返回的id='+result._id); 
            res.send('finished');
            return next();
        }
        mydao.daots(so,cb); 
    }
};

但为什么调用cb()后, console.log(‘传入id=’+so.id+’ 返回的id=’+result._id); 这两个值不一样呢,且大多数情况下 "传入的id=循环体的最后一个值呢“是因为node.js的事件机制,当执行完mydao.daots()后不会等待返回结果,而继续执行循环体,当node.js从mongodb中取出数据后会自动调用我们设置的cb()这个回调函数,而由于执行循环体中的速度快,所以大部分情况下,所有循环执行执行完了,也就是说so这个变量现在的数据是循环中最后一个了,所以调用cb函数时,里面要打印so值时,打印的会是当前循环中最后一个值。且由于result是回调函数自己带回来的参数,这个result是当时传进去的so产生的结果值,所以两个值会不一样,只有最后一个回调函数的so值会与result带回来的so值相同。 所以如果要在cb中得到当时传进去的so值,必须要用闭包,闭包也就是把当时每次要传给mydao.daots中的so保存起来,需要用到的时候再取出来。

mydao.js中的daots方法中,结果返回中这里的两个值永远相同 console.log(‘Dao层传入的id=’+data.id+ ’ Dao层返回的id=’+result._id); 是因为每次调用daots方法时,传入的data值都是新产生的,且在自己的匿名函数体内这个值不会被改变,所以不管回调函数什么时候来取,它的值永远也就是传入时的值。

回到顶部