专栏首页全栈码农画像全网最通透的“闭包”认知 · 跨越语言

全网最通透的“闭包”认知 · 跨越语言

闭包作为前端面试的必考题目,常让1-3年工作经验的JavaScripter感到困惑,其实主流语言都有闭包。

今天我们深入聊一聊[闭包], 查缺补漏!

1. 以面试题 · 投石问路 2. 以C#闭包 · 庖丁解牛 3. 跨越语言 ·追本溯源 • 头等函数 •自由变量 •词法作用域4. 答面试题 · 返璞归真

1. 投石问路

调用下面函数,输出结果是什么样呢?

static void Closure1(){   for (int i = 0; i < 5; i++)   {                      Task.Run(()=> Console.WriteLine(i));   }}//  输出:55555

是不是很意外?如何输出原本预期的 0,1,2,3,4。

bingo, 加一个临时变量就可以解决。

static void Closure2(){   for (int i = 0; i < 5; i++)   {      int j = i;      Task.Run(() => Console.WriteLine(j));   }}// 输出:30142//  多次执行的结果不一样,但是总是会保持输出 0,1,2,3,4 的乱序组合

以上闭包概念涉及到 Task任务,理解起来更加复杂,我们来看一个基础的C#闭包。

2. 庖丁解牛

一个闭包就是一个“捕获”了其生成的环境中、所引用的自由变量的函数。 这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

 static void Closure() {    var x = 1;    Action action= () =>      {         var y = 1;         var result = x + y;         Console.WriteLine(result);         x++;      };    action();    action();}   // 输出:  2  3

我们首先定义了一个委托action,它引用了“x”变量(x变量既不是入参,也不是委托内的局部变量), 这个变量将被action"捕获”,被自动添加到action 的运行环境。

当我们执行action时,原始的“x”已经脱离了它被引用时的作用域环境,但是两次执行能输出2,3 说明它脱离原引用环境仍然能用。

当你在代码调试器(debugger)里观察“action”时,可以看到C#编译器为我们创建了一个Target属性,里面封装了 x 变量:

源码追溯,委托继承自Delegate抽象类,Delegate类有个Target 属性(获取当前委托调用实例方法的实例类) 。 至此可以猜想: 我们每次执行委托,实际是是执行某个匿名类上的实例方法。

都说了闭包是跨越语言的设计, 至少我知道 JavaScript C# Go都有闭包。

3. 追本溯源

闭包是词法闭包的简称,维基百科上是这样定义的: “在计算机编程中,闭包是在词法环境中绑定自由变量的头等函数”。

头等函数

头等函数( First Class)意味着语言将其视为第一类数据类型的函数, 意味着你可以将函数分配给一个变量(或作为参数传递),然后像正常函数一样调用。

很明显,C#常使用的委托(C#委托的演进:匿名函数-->lambda表达式)是头等函数。

Func<string,string> myFunc = delegate(string var1)                                {                                    return "some value";                                   };Func<string,string> myFunc = var1 => "some value";  string myVar = myFunc("something");

自由变量

自由变量是在匿名函数/lambda表达式中被引用的变量,它不是函数的参数也不是函数的局部变量。

var myVar = "this is good";Func<string,string> myFunc = delegate(string var1)                                {                                    return var1 + myVar;                                   };

词法作用域引用的自由变量,注意,是引用自由变量,并不是使用当时自由变量的值

☺️通俗点, 就是告知这个变量环境,我这个匿名函数等会执行时要用到这个变量;如果我没被销毁,你不能销毁我引用的自由变量。

我们再回过头来看[投石问路]的面试题。

4. 返璞归真

首先你要知道:循环内开启的Task任务,并不保证执行顺序。

Demo1:输出5,5,5,5,5

这是因为在 for循环内,开启了5个Task任务,每个任务均引用了自由变量i (相对于每个任务执行环境,i 属于全局变量); for循环先执行完,i=5, 5个任务输出时自然得到值5。

为什么加上临时变量就能输出"预期"?

Demo2:输出乱序的0,1,2,3,4

这是因为 在for循环内,每次循环j均拷贝自当时的i,每个任务均引用了自由变量 j (每个任务执行环境均维护了一个变量j); 任务乱序执行时依旧能获取本任务绑定的自由变量j。


有这样的认知,理解JavaScript 闭包也就不难了。

# 总结

本文屏蔽语言差异,理清了[闭包]的概念核心: 头等函数、自由变量,不仅能帮助我们应对多语种有关闭包的面试题, 也帮助我们了解[闭包]在通用语言中的设计初衷。

本文分享自微信公众号 - Dotnet Plus(nodotnet),作者:小码甲

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-04-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • APP安全测试分越权,SQL,XSS漏洞 怎样进行检测?

    目前越来越多的APP遭受到黑客攻击,包括数据库被篡改,APP里的用户数据被泄露,手机号以及姓名,密码,资料都被盗取,很多平台的APP的银行卡,充值通道,聚合支付...

    技术分享达人
  • JDK7新特性概览JSR292:支持动态类型语言(InvokeDynamic)G1 垃圾回收器(Garbage-First Collector)JSR334:小的语言改进(Project Coin)核

    JavaEdge
  • [网络安全] 一.Web渗透入门基础与安全术语普及

    最近开始学习网络安全和系统安全,接触到了很多新术语、新方法和新工具,作为一名初学者,感觉安全领域涉及的知识好广、好杂,但同时也非常有意思。所以我希望通过这100...

    Gcow安全团队
  • 放眼未来五年,客户运营到了不得不变的时刻

    ? ? ? 本世纪前两个十年,也是互联网在我国肆意生长的20年,如果要为这个阶段打一个标签的话,非“流量”莫属。从PC互联网到移动互联网,每个时代都涌现出一批...

    腾讯企点
  • Rust 乱炖 | Rust 让恶意软件也变强了

    今天看到 proofpoint 发表了一篇题为Buer Loader 用 Rust 创造了新的变种 (New Variant of Buer Loader Wr...

    张汉东
  • 当游戏遇上NFT,是“鸡肋”还是“鸡腿”?

    近几个月来,NFT(non-fungible Token、非同质化通证)以其非同质化、不可拆分的特性,在艺术收藏品领域大放异彩,有关NFT数字艺术品卖出高价的新...

    用户7358413
  • 微软纳德拉为何连烧“三把火”?

    PC时代曾经叱咤风云“独孤求败”的微软在移动互联网时代过得并不太好,连续经历了公司换帅、XP停服、Windows8惨败、诺基亚裁员等动荡后,深陷移动转型泥潭的微...

    曾响铃
  • 这可能是最全的入门Web安全路线规划

    本次写的是一篇如何通过针对性系统学习Web安全或者说如何能成为一名渗透测试人员(有疑问或者错误的地方还望大家多多指正)。

    Gcow安全团队
  • 响铃:生态赋能,海尔COSMOPlat“双跨”助力“农业新旧动能转换”

    小雪是一位定居深圳的海归女孩,周末喜欢自己下厨。对食材挑剔的她,会把关每份食材的质量。因此,她选择在手机上一键定制了经海尔COSMOPlat农业生态诚信认证体系...

    曾响铃

扫码关注云+社区

领取腾讯云代金券