python闭包的原理_function closure: 理解函数闭包和它的实现原理

closure是什么?

function closure是一个语言特性, 1960s出现在schema等函数式语言上,现代高级语言(ruby/js/kotlin ...)大多支持(注意: java 不支持, python需声明nonlocal)。

closure(特性)指的是 -- 函数可以读写(声明它的)外层函数的局部变量, 即使外层函数已经执行完毕。

以js为例看几个例子:

let print = console.log;

// Example 1: callback functions:

let count = 0;

let id = setInterval(()=>{ print(++count) }, 1000); // callback access outer var: count

setTimeout(()=> 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: a

let doub = mul(2);

let trip = mul(3);

print(doub(10)); // 20

print(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, calls

let {inc, dec, getCalls} = makeCounter();

print(inc()); // 1

print(inc()); // 2

print(dec()); // 1

print(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上的局部变量表

为什么函数在其他地方调用时却能访问到这些外层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 ones

function 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 heap

function 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: https://github.com/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)

...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值