function closure: 了解函数闭包和它的实现原理

作者 : 开心源码 本文共4049个字,预计阅读时间需要11分钟 发布时间: 2022-05-12 共228人阅读

参考:
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引发的坑

  1. 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

  1. 局部变量只需还被子函数引用,在子函数释放前就不会被释放:
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个问题:

  1. 为什么外层函数执行完,局部变量(弹出stack)还能被访问?

    • 由于: 局部变量根本不在stack上而是在heap上, stack只放了指向局部变量表的指针
      • 必须支持GC: 需要靠GC来释放这段被分配在heap上的局部变量表
  2. 为什么函数在其余地方调用时却能访问到这些外层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

模拟编译器:

  1. 把closure引用改成显示的引用
  2. 把局部变量表分配在heap上而不是stack上
  3. 公告函数的地方创立函数对象,并且把父级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: 了解函数闭包和它的实现原理

发表回复