为何返回的回调函数无法像闭包一样访问外部上下文?附场景示例
这问题问得太戳痛点了!刚摸闭包和回调的时候,很多人都会觉得“这俩看起来差不多啊,为啥结果天差地别”,咱们掰开揉碎了说清楚。
先看场景1:柯里化为啥能成?
你写的柯里化代码:
const foo = a => b => a + b; const bar = foo('a'); bar('b') // 输出'ab'
关键点在于:当你调用foo('a')时,它返回的那个b => a + b函数,是在foo的作用域里被创建的——这个内部函数会“记住”外层foo作用域里的a值,形成了闭包。哪怕foo执行完退出了,这个返回的函数依然能访问到当时的a,所以后面调用bar('b')时,a是确定存在的,自然能算出结果。
再看场景2:问题出在哪?
你的场景2代码:
const foo = a => a + b; const wrap = (callback, b) => callback; const wrapped = wrap(foo, 'b'); wrapped('a') // b未定义
这里的坑在于:wrap函数只是把传入的callback(也就是foo)原封不动地返回了,完全没让foo和wrap里的b产生任何作用域关联。
foo里的b是个“自由变量”——它既不是foo的参数,也不是foo内部定义的变量,所以当foo被调用时,会沿着自己的作用域链往上找b。但foo是在全局作用域(或者你定义它的那个作用域)里创建的,它的作用域链里根本没有wrap函数的b参数,自然会报b is not defined。
简单说:柯里化是让内部函数主动捕获了外层的变量,而场景2里的foo根本没机会接触到wrap里的b。
怎么改场景2才能达到类似效果?
有两种常见的思路,都能让foo访问到b:
思路1:让wrap返回一个闭包,把b“包”进去
修改wrap,返回一个新函数,这个新函数调用原callback时把b传进去:
const foo = (a, b) => a + b; const wrap = (callback, b) => (a) => callback(a, b); const wrapped = wrap(foo, 'b'); wrapped('a'); // 输出'ab'
这里wrap返回的新函数捕获了b,调用时把它作为参数传给foo,foo就能拿到b了。
思路2:让foo在包含b的作用域里被定义
或者直接在wrap内部定义foo,这样foo就能自然捕获wrap的b:
const wrap = (b) => { const foo = a => a + b; // foo现在在wrap的作用域里,能直接访问b return foo; }; const wrapped = wrap('b'); wrapped('a'); // 输出'ab'
也可以用bind来绑定b:
const foo = (b, a) => a + b; const wrap = (callback, b) => callback.bind(null, b); const wrapped = wrap(foo, 'b'); wrapped('a'); // 输出'ab'
核心总结
闭包的关键是:函数要在包含目标变量的作用域中被创建(或者通过bind等方式绑定了变量),这样它才能捕获到那个变量。柯里化天然满足这个条件,而你原来的场景2里,回调函数和外部变量完全是“两条平行线”,自然没法访问到对方。
内容的提问来源于stack exchange,提问作者Henrique Guerra

