function closure: 了解函数闭包和它的实现原理
参考:
https://en.wikipedia.org/wiki/Closure_(computer_programming)#Implementation_and_theory
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
https://stackoverflow.com/questions/111102/how-do-javascript-closures-work
closure是什么?
function closure是一个语言特性, 1960s出现在schema等函数式语言上,现代语言(ruby/python/js/java …)大多支持。
closure(特性)指的是 — 函数可以读写(公告它的)外层函数的局部变量, 即便外层函数已经执行完毕。
以js为例看几个例子:
let print = console.log;// Example 1: callback functions:let count = 0;let id = setInterval(()=>{ print(++count) }, 1000); // callback access outer var: countsetTimeout(()=> clearInterval(id), 5000); // callback access outer var: id// Example 2: high order function:let mul = a => b => a*b;// closure: doub, trip functions(b=>a*b) can access outer local variable: alet doub = mul(2);let trip = mul(3);print(doub(10)); // 20print(trip(10)); // 30// Example 3: function builder:function makeCounter(count=0, step=1){ let calls = 0; return { inc: () => { calls++; return count+=step; }, dec: () => { calls++; return count-=step; }, getCalls: ()=> calls, getCount: ()=> count, }}// closure: inc, dec, getCalls as functions can access outer local variables: count, step, callslet {inc, dec, getCalls} = makeCounter();print(inc()); // 1print(inc()); // 2print(dec()); // 1print(getCalls()); // 3- 注意:
- ‘外层函数’指的是公告它的函数,也就是肉眼看到的外层函数, 而不是调用它的函数
- 我们把每一层函数(局部变量表)称为一个lexical scope
- 不只是父级外层,所有祖先的外层的lexical scope都能访问
- block也算一层
closure引发的坑
- closure中,函数引用到的是外部局部变量本身,而不是外部局部变量的值
// x has become 3 for all 3 callbacks:for(var x=0; x<3; x++) setTimeout(() => console.log(x));这个例子中3个callbacks被调用时,x已经变成3了,所以输出的都是3
- 局部变量只需还被子函数引用,在子函数释放前就不会被释放:
function x(a){ function foo(){... a ...} // closure: access var a doSomething(foo); //'big' also be hold by foo, because 'big' is also x's local variable let big = fetchBigObject(); run1(big); run2(big);}// improved:function x(a){ function foo(){... a ...} // closure: access var a doSomething(foo); { // inside nested block, 'big' no longer belongs to x's local variables let big = fetchBigObject(); run1(big); run2(big); }}编译器如何实现closure的?
先思考2个问题:
为什么外层函数执行完,局部变量(弹出stack)还能被访问?
- 由于: 局部变量根本不在stack上而是在heap上, stack只放了指向局部变量表的指针
- 必须支持GC: 需要靠GC来释放这段被分配在heap上的局部变量表
- 由于: 局部变量根本不在stack上而是在heap上, stack只放了指向局部变量表的指针
为什么函数在其余地方调用时却能访问到这些外层lexical scope的局部变量?
- 由于: 每次定义(公告)函数实际上创立了一个新的函数对象, 不仅保存代码位置的引用(相同代码段),还保存指向父函数此刻的局部变量表的引用(各不相同:由于父函数每次执行都创立一个新的局部变量表)
根据以上以上2个结论,我们已经可以模拟编译器来实现closure。
以下面的js代码(采用了closure)为例,我们模拟编译器加塞额外逻辑来去掉closure引用,使得改造后的代码不仅没用到closure而且执行时仍然保持原来的逻辑。
原始代码:
function foo(){ let a = 1; function bar(){ let b = 2; a++; function baz(){ return a+b; } b++; return baz; } a++; return bar;}let bazFunc = foo()();console.log(bazFunc()); //6模拟编译器:
- 把closure引用改成显示的引用
- 把局部变量表分配在heap上而不是stack上
- 公告函数的地方创立函数对象,并且把父级scope存进函数对象
// step 1: change implicit references to explicit onesfunction foo(){ let a = 1; function bar(){ let b=2; parent_scope.a++; function baz(){ return parent_scope.parent_scope.a + parent_scope.b; } b++; return baz; } a++; return bar;}// step 2: allocate var_table on heapfunction foo(){ let var_table = {}; var_table.a = 1; function bar(){ let var_table={}; var_table.b=2; parent_scope.a++; function baz(){ return parent_scope.parent_scope.a + parent_scope.b; } var_table.b++; return baz; } var_table.a ++; return bar;}// step 3(complete): assign parent_scope when create function object// (you can ignore 'this' in the following example)let global = this;function build(parent_scope, func){ return { parent_scope: parent_scope, code: func, run: function(that, ...args){ return this.code( {parent_scope: this.parent_scope, this: that}, ...args ) } }}const foo = build(global, function(scope, ...args){ scope.a = 1; const bar = build(scope, function(scope, ...args){ scope.b=2; scope.parent_scope.a++; const baz = build(scope, function(scope, ...args){ return scope.parent_scope.parent_scope.a + scope.parent_scope.b; }); scope.b++; return baz; }); scope.a ++; return bar;});let bazFunc = foo.run(this).run(this);console.log(bazFunc.run(this)); // 6至此,step 3中已经没有任何closure引用,但仍然保持原代码相同逻辑(以上例子中可忽略代码中的this,由于这个例子中并没有被用到)。
思考题
下面是一段redux的源码:你能了解为什么其中 {dispatch: (…args)=>dispatch(…args)} 不写成 {dispatch: dispatch} 吗?
// source code: reduxjs/redux/blob/master/src/applyMiddleware.js...let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) }const chain = middlewares.map(middleware => middleware({ ... dispatch: (...args)=>dispatch(...args) //!!why not "dispatch: dispatch" ? })) dispatch = compose(...chain)(store.dispatch)...1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » function closure: 了解函数闭包和它的实现原理