1
luin 2012-12-04 12:43:08 +08:00
回调函数执行完会自动释放外层局部变量。
|
2
hidden 2012-12-04 12:46:05 +08:00
如果没有作用域照样存在这个这个问题,如果限制了作用域,访问callback外面的变量麻烦。用熟了表示没什么压力。
|
4
robhsiao OP @hidden
明白。可能是表达上有点错误,并不是说变量作用域造成的问题,而是想了解一下大家有没有一些 best practices之类的来解决这样的问题.. ^^ |
5
hyq 2012-12-04 13:12:32 +08:00
@robhsiao 用var关键字声明变量,要养成这个好习惯
var x = 1; function(){ var x = 2; console.log("inner x = " + x); }() console.log("outer x = " + x); |
6
robhsiao OP @hyq
这个也有了解,但是您知道,node.js 里边处处是异步,所有的都要通过callback,父级函数里边定义的变量,子函数内不能把它盖掉,因此孙函数可能还需要用它。 这样就造成每个变量命名都要全局是唯一。 |
7
hyq 2012-12-04 13:31:43 +08:00
那么你可以用一个变量来保存作用域,如
function fun(){ var _scopeFun = this; var a = 1; function(){ var a = 2; console.log("inner a = ", a); console.log("outer a = ", _scopeFun.a); }() } |
9
BOYPT 2012-12-04 14:01:29 +08:00
所以coffeescript就是爲了解決js的這些尷尬問題而定好的一系列規範而出現的。
|
10
robhsiao OP |
11
hidden 2012-12-04 16:45:15 +08:00
个人使用倒是不麻烦,覆盖了全局变量,并且后面还要用到这个变量的概率是很小的。 即使用到了,后面来修改也不麻烦。
|
12
robhsiao OP @hidden
最常见的就是... request、response 这样的变量 最外层的http server callback会有一组这样的变量,里边的callback可能在连memcache、或者使用http.request请求其它API服务时都可能有一个request一个response,而这些callback最终要生成响应吐给客户端,所以最外层的response不能覆盖。 --- 也不知道大家是不是清楚我在说什么 |
13
hidden 2012-12-04 18:20:18 +08:00
遇到过啊。 这个名字可以变来变去的用。 Request, request, req, res... 全局一个request,里面实例一个req。 套一个callback你可以取出你下面要用的具体数据例: var ip = req.socket.remoteAddress; 在里面就放心覆盖吧。 也就最多套两个callback。 再套的话你就得考虑拧一个函数出来了。 反正蛮灵活的。
|
14
jackyz 2012-12-04 19:53:28 +08:00 3
>> 初次用 expressjs 写了点小东西,窃以为变量作用域名是node.js(或者应该是javascript) ugly 的一个地方。
用了很长时间的 node.js 了,真没觉得。 >> 基本每一个模块需要占用一个全局变量, 类似 var m = require('./my_module'); 你可以在 node 命令行里 require 你自己的代码,然后输入 m. 按 tab 试试看。全局的可以直接按 tab 看到,或者 global. tab 查看得到。这会让你对 exports 有个感性认识。你需要了解的是 node.js 的 module 机制。参考下面的代码, module 的概念与之类似: (funciton(){ var a = 1; .... return {x:a}; })(); 这个叫啥来着,立即执行的匿名函数?闭包?反正就是这个东西了。 你的 module 对外可见的,只是你 exports.fun = xxx 的部分,其余的一律不可见(我认为,这种机制比起 public private 什么的,至少是一样的强大)。全局变量的问题,基本不用担心。 >> 而且由于回调函数里可能需要使用父级函数的局部变量,所以回调函数嵌套几层之后,感觉使用每一个变量要非常谨慎,每一个变量都是一个炸弹。 ... function some_fun(req,res,next){ redis.incr('next_id', function(e,r){ if(e) return next(e); redis.set('key', object, function(e,r){ if(e) return next(e); res.send([1,'ok']); } } } ... 类似这样的回调层次和作用域(不同层次都有 e 和 r 存在,这里,因为有 var shadow 机制,你可以很放心地取相同的名字),没什么可担心的。 回调使用外层的变量,这是 javascript 提供的语法机制,但如果回调里很大量地在使用外层变量,那就有可能是 bad smell 了,这很微妙,但不复杂,这种情况一般都可以很简单地重构为传参的形式。 >> 请问大家是如何规避这个问题?依靠命名规范?或者是我用法是不正确? 我认为你可能是还没有“习惯”。 btw. 个人建议慎用 coffee script 既然 plain javascript 能解决所有的问题,那就没有必要引入实质上是同一种语言(coffe script IS javascript)的另外一套语法。有个老外写过一篇,处处不记得了,转载在这里 http://cliclip.com/#clip/7/521 。 |
15
linlinqi 2012-12-04 20:13:56 +08:00
解决连环嵌套比较好的方法就是使用promise模式编程了,见这里的解释:
http://www.infoq.com/cn/news/2011/09/js-promise node.js能用上的promise库有哪些呢? http://stackoverflow.com/questions/7588581/what-nodejs-library-is-most-like-jquerys-deferreds |
16
BOYPT 2012-12-05 11:05:44 +08:00
js不是完美的语言。
作为一个语言,容易让人混淆困惑的地方都应该认为是语言的缺陷;当然这些可以通过提倡一种“xxxx规范”,在某情况下本来可以有多种approach,但根据规范仅限定仅使用一种,等等。 看C++就是了,什么规范的书比基本语法书要厚几倍。 Coffeescript从另外一个角度来解决这个问题。有个说法是,计算机科学里面的任意问题都可以通过一层封装解决,Coffeescript就是这么一层封装。那堆麻烦的plain javascript caveat,看看知道就好了,实在不想时刻惦记着。 |
17
jackyz 2012-12-05 12:14:38 +08:00
@linlinqi 借机与楼上探讨。
n 层嵌套确实很难看,但我很怀疑 promise 或者 async 这类的解决方案是否真的有效。我也曾经尝试过一些 module ——感觉对代码表达的限制很大。这类东西基本都是语法糖,而且是 for control flow 的语法糖,但 control flow 其实是一段程序的精髓,是很灵活的东西,对于这个东西的抽象常常让人产生“还不如退回去的感想”。 举例说明 function some_fun(req,res,next){ --redis.incr('next_id', function(e,r){ ----if(e) return next(e); ----var object = {id:r, now:Date.now()}; ----redis.set('key', object, function(e,r){ ------if(e) return next(e); ------res.send([1,'ok']); ----} --} } 贴代码没有缩进,就用-符号代替 space 了。 通常来说,似乎可以用 serial 来理解这里的两层嵌套,但,如果考虑错误处理,问题就很复杂了。比如,if(e)return next(e); 这一句 return 则可以避免内层的 redis 语句执行,是有性能意义的代码。这里的处理是极度简化之后的情况,实际情况比这可能要复杂很多。 我要表达的意思是,如果以通用的 promise 库来做 serial 之类的流程抽象,似乎无法准确的表达这里的精微之处。 @linlinqi 对此如何取舍? |
18
linlinqi 2012-12-05 13:29:19 +08:00
@jackyz 我试着用promise模式写一下你这个例子
https://gist.github.com/4212565 Deferreds是可以嵌套的,这里我就没细写了。用done和fail可以表现出这些流程,习惯之后会好用很多。 |
19
linlinqi 2012-12-05 13:30:24 +08:00
|
21
jackyz 2012-12-05 15:38:07 +08:00
@linlinqi
谢谢重构,两个感觉不太适应的地方: 1. 10 行变成 23 行 ,没感觉在哪里变得更清晰了呢? 2. 从 node 的 callback(e,r) 风格转变为 resolve -> done reject -> fail 风格。 另外,如果再加一层回调呢,会变成什么样子?在实际应用中有个 5,6 层的回调不稀奇呀。 那个层次还需要再包装 resolve 和 reject 还是可以“重用” promise 又或者怎样?各个层次的异常如果要有不同的处理代码,要怎么表达呢?是: fun1().done().fail().fun2().done().fail() 还是: fun().done( fun2().done().fail() ).fail() 又或者还是怎样? 之前的尝试,进行到这里就退回去了(感觉没啥区别呀,而且还要分别包装 e 和 r ),没准我是那个关节没相通? |
22
luin 2012-12-05 15:59:35 +08:00
|
23
linlinqi 2012-12-05 17:33:24 +08:00
@jackyz 假设redis这个对象已经是基于Deferred模式封装良好的话,那么写的好看一些会是这样
http://gist.github.com/4212565.js?file=ahhhh.js 这样的行数是不是满足了? |
24
jackyz 2012-12-05 19:20:05 +08:00
嵌套层次太多确实难看。要追求漂亮,但不能以失去灵活性为代价。
@luin 论坛氛围不错,我改写了例子,希望能把问题引向更深入。 主要的改写是引入了需要传递更多因素的情况,来表达这种灵活性在使用 control flow module 之后的丧失。 http://gist.github.com/4214679.js 引入之后,感觉有两个问题。 传参自由度的问题: 第 6 行分别引用了两个外层里定义的 id 和 val 。对应在 async 写法的第 24 行,是不 work 的。要解决这个问题,需要显式地向外用 callback 传递或者引入一个 params object 来解决问题。需要传递的参数更多的话,可能更麻烦。 async 的思路是通过 callback 的参数传递给下一个 function 。各个 function 的变量作用域是并列的,也就是说,嵌套层次的扁平是以引用外层变量的能力作为代价的。 错误处理自由度的问题: 上述每一个错误都给了一个不同的错误处理,之前的例子是全都用 next(e) 来处理,所以,体现不出这种约束来。在 async 的版本里,所有的错误都汇集到 27 行来处理,要如何区分这许多种错误呢? 我也有用 async ,主要是用它抽象数据结构的相关方法,比如 map 之类,但 control flow 的因为上述的问题,就暂时还没有用。 @linlinqi 这是否意味着需要在 callback(e,r) 的 node.js 标准风格和 deferred 风格之间做适配呢?这个适配,在 node 没有推出标准的 deferred 风格之前,似乎是没有动力去完成的。 还是那句话,要追求漂亮,但不能以失去灵活性为代价。我知道这可能有些不切实际,必然要 trade off 什么的。不过,思考一下倒也有益。 |
25
jackyz 2012-12-07 17:10:26 +08:00
这个问题踢到铁板讨论不下去了吗?
|
26
robhsiao OP sorry, 楼主读书不多,只能围观 :(
各位请继续... |