图解Javascript——作用域、作用域链、闭包

什么是作用域?


       作用域是一种规则,在代码编译阶段就确定了,规定了变量与函数的可被访问的范围。全局变量拥有全局作用域,局部变量则拥有局部作用域。 js是一种没有块级作用域的语言(包括if、for等语句的花括号代码块或者单独的花括号代码块都不能形成一个局部作用域),所以js的局部作用域的形成有且只有函数的花括号内定义的代码块形成的,既函数作用域。

 

什么是作用域链?


       作用域链是作用域规则的实现,通过作用域链的实现,变量在它的作用域内可被访问,函数在它的作用域内可被调用。

作用域链是一个只能单向访问的链表,这个链表上的每个节点就是执行上下文的变量对象(代码执行时就是活动对象),单向链表的头部(可被第一个访问的节点)始终都是当前正在被调用执行的函数的变量对象(活动对象),尾部始终是全局活动对象。

 

作用域链的形成?


       我们从一段代码的执行来看作用域链的形成过程。

复制代码
 1 function fun01 () {2     console.log('i am fun01...');3     fun02();4 }5 6 function fun02 () {7     console.log('i am fun02...');8 }9 
10 fun01(); 
复制代码

 

数据访问流程


       如上图,当程序访问一个变量时,按照作用域链的单向访问特性,首先在头节点的AO中查找,没有则到下一节点的AO查找,最多查找到尾节点(global AO)。在这个过程中找到了就找到了,没找到就报错undefined。

 

延长作用域链


       从上面作用域链的形成可以看出链上的每个节点是在函数被调用执行是向链头unshift进当前函数的AO,而节点的形成还有一种方式就是“延长作用域链”,既在作用域链的头部插入一个我们想要的对象作用域。延长作用域链有两种方式:

1.with语句

复制代码
1 function fun01 () {
2     with (document) {
3         console.log('I am fun01 and I am in document scope...')
4     }
5 }
6 
7 fun01();
复制代码

 

 

 

2.try-catch语句的catch块

复制代码
1 function fun01 () {
2     try {
3         console.log('Some exceptions will happen...')
4     } catch (e) {
5         console.log(e)
6     }
7 }
8 
9 fun01();
复制代码

 

 

ps:个人感觉with语句使用需求不多,try-catch的使用也是看需求的。个人对这两种使用不多,但是在进行这部分整理过程中萌发了一点点在作用域链层面的不成熟的性能优化小建议。

 

由作用域链引发的关于性能优化的一点不成熟的小建议


1.减少变量的作用域链的访问节点

       这里我们自定义一个名次叫做“查找距离”,表示程序访问到一个非undefined变量在作用域链中经过的节点数。因为如果在当前节点没有找到变量,就跳到下一个节点查找,还要进行判断下一个节点中是否存在被查找变量。“查找距离”越长,要做的“跳”动作和“判断”动作也就越多,资源开销就越大,从而影响性能。这种性能带来的差距可能少数的几次变量查找操作不会带来太多性能问题,但如果是多次进行变量查找,性能对比则比较明显了。

复制代码
 1 (function(){2     console.time()3     var find = 1      //这个find变量需要在4个作用域链节点进行查找4     function fun () {5         function funn () {6             var funnv = 1;7             var funnvv = 2;8             function funnn () {9                 var i = 0
10                 while(i <= 100000000){
11                     if(find){
12                         i++
13                     }
14                 }
15             }
16             funnn()
17         }
18         funn()
19     }
20     fun()
21     console.timeEnd()
22 })()

 
复制代码

 

复制代码
 1 (function(){2     console.time()3     function fun () {4         function funn () {5             var funnv = 1;6             var funnvv = 2;7             function funnn () {8                 var i = 09                 var find = 1      //这个find变量只在当前节点进行查找
10                 while(i <= 100000000){
11                     if(find){
12                         i++
13                     }
14                 }
15             }
16             funnn()
17         }
18         funn()
19     }
20     fun()
21     console.timeEnd()
22 })()

 
复制代码

 

       在mac pro的chrome浏览器下做实验,进行1亿次查找运算。

       实验结果:前者运行5次平均耗时85.599ms,后者运行5次平均耗时63.127ms。

 

2.避免作用域链内节点AO上过多的变量定义

       过多的变量定义造成性能问题的原因主要是查找变量过程中的“判断”操作开销较大。我们使用with来进行性能对比。

复制代码
 1 (function(){2     console.time()3     function fun () {4         function funn () {5             var funnv = 1;6             var funnvv = 2;7             function funnn () {8                 var i = 09                 var find = 10
10                 with (document) {
11                     while(i <= 1000000){
12                         if(find){
13                             i++
14                         }
15                     }
16                 }
17             }
18             funnn()
19         }
20         funn()
21     }
22     fun()
23     console.timeEnd()
24 })()

 
复制代码

 

       在mac pro的chrome浏览器下做实验,进行100万次查找运算,借助with使用document进行的延长作用域链,因为document下的变量属性比较多,可以测试在多变量作用域链节点下进行查找的性能差异。

       实验结果:5次平均耗时558.802ms,而如果删掉with和document,5次平均耗时0.956ms。

       当然,这两个实验是在我们假设的极端环境下进行的,结果仅供参考!

 

关于闭包


1.什么是闭包?

       函数对象可以通过作用域链相互关联起来,函数体内的数据(变量和函数声明)都可以保存在函数作用域内,这种特性在计算机科学文献中被称为“闭包”。既函数体内的数据被隐藏于作用于链内,看起来像是函数将数据“包裹”了起来。从技术角度来说,js的函数都是闭包:函数都是对象,都关联到作用域链,函数内数据都被保存在函数作用域内。

2.闭包的几种实现方式

       实现方式就是函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。如下:

 

 

       如上两图所示,是在chrome浏览器下查看闭包的方法。两种方式的共同点是都有一个外部函数outerFun(),都在外部函数内定义了内部函数innerFun(),内部函数都访问了外部函数的数据。不同的是,第一种方式的innerFun()是在outerFun()内被调用的,既声明和被调用均在同一个执行上下文内。而第二种方式的innerFun()则是在outerFun()外被调用的,既声明和被调用不在同一个执行上下文。第二种方式恰好是js使用闭包常用的特性所在:通过闭包的这种特性,可以在其他执行上下文内访问函数内部数据。

我们更常用的一种方式则是这样的:

复制代码
 1 //闭包实例2 function outerFun () {3     var outerV1 = 104     function outerF1 () {5         console.log('I am outerF1...')6     }7 8     function innerFun () {9         var innerV1 = outerV1
10         outerF1()
11     }
12     return innerFun   //return回innerFun()内部函数
13 }
14 var fn = outerFun()        //接到return回的innerFun()函数
15 fn()                    //执行接到的内部函数innerFun()
复制代码

此时它的作用域链是这样的:

 

3.闭包的好处及使用场景

       js的垃圾回收机制可以粗略的概括为:如果当前执行上下文执行完毕,且上下文内的数据没有其他引用,则执行上下文pop出call stack,其内数据等待被垃圾回收。而当我们在其他执行上下文通过闭包对执行完的上下文内数据仍然进行引用时,那么被引用的数据则不会被垃圾回收。就像上面代码中的outerV1,放我们在全局上下文通过调用innerFun()仍然访问引用outerV1时,那么outerFun执行完毕后,outerV1也不会被垃圾回收,而是保存在内存中。另外,outerV1看起来像不像一个outerFun的私有内部变量呢?除了innerFun()外,我们无法随意访问outerV1。所以,综上所述,这样闭包的使用情景可以总结为:

(1)进行变量持久化。

(2)使函数对象内有更好的封装性,内部数据私有化。

进行变量持久化方面举个栗子:

       我们假设一个需求时写一个函数进行类似id自增或者计算函数被调用的功能,普通青年这样写:

1 var count = 0
2 function countFun () {
3     return count++
4 }

       这样写固然实现了功能,但是count被暴露在外,可能被其他代码篡改。这个时候闭包青年就会这样写:

复制代码
1 function countFun () {
2     var count = 0
3     return function(){
4         return count++
5     }
6 }
7 
8 var a = countFun()
9 a()
复制代码

 

       这样count就不会被不小心篡改了,函数调用一次就count加一次1。而如果结合“函数每次被调用都会创建一个新的执行上下文”,这种count的安全性还有如下体现:

复制代码
 1 function countFun () {2     var count = 03     return {4         count: function () {5             count++6         },7         reset: function () {8             count = 09         },
10         printCount: function () {
11             console.log(count)
12         }
13     }
14 }
15 
16 var a = countFun()
17 var b = countFun()
18 a.count()
19 a.count()
20 
21 b.count()
22 b.reset()
23 
24 a.printCount()        //打印:2    因为a.count()被调用了两次
25 b.printCount()        //打印出:0    因为调用了b.reset()
复制代码

       以上便是闭包提供的变量持久化和封装性的体现。

 

4.闭包的注意事项

       由于闭包中的变量不会像其他正常变量那种被垃圾回收,而是一直存在内存中,所以大量使用闭包可能会造成性能问题。

转载于:https://www.cnblogs.com/mumusen/p/6593154.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/488838.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

除了欧拉公式,这8个数学公式也足够美丽且神奇

来源&#xff1a;算法与数学之美1概率分布公式

canvas rotate 累加旋转_震惊,canvas文字粒子效果,只需要100行代码,简单易懂。

震惊,canvas文字粒子效果&#xff0c;只需要100行代码&#xff0c;简单易懂。canvas是使用JavaScript程序绘图(动态生成),相比于css&#xff0c;可以更加简单方便的绘制细节的样式。其中最强大的功能莫过去像素的处理。一个像素一个像素去绘制任何想要的展示效果。接下来&#…

CES 2020前瞻:一份最全的趋势预测报告

来源&#xff1a; CES20202020年&#xff0c;消费级技术领域的总体趋势可以总结为&#xff1a;最顶尖的产品将变得更强大、更完善。虽然我们并不喜欢“溢价”这个已经被用滥了的字眼&#xff0c;但不得不承认&#xff0c;新一年中溢价会成为新的常态。最好的东西会变得更好&…

Android pda出入库管理,出入库PDA管理系统软件

随着经济的高速发展&#xff0c;市场的日新月异&#xff0c;仓库管理越来越重要起来&#xff0c;企业里从原料的入库到成品的出库都需经过仓库来管理控制&#xff0c;仓库工作责任重大、数据即时准确犹为关键&#xff0c;现代企业大多都借助出入库管理系统软件来管控仓库&#…

一棵树的生物量怎么算_桂花开花小、开花少怎么办?四点建议送给你!

原标题&#xff1a;桂花开花小、开花少怎么办&#xff1f;四点建议送给你&#xff01;虽然冬天绝大多数桂花都处在休眠期&#xff0c;生长缓慢&#xff0c;不开花&#xff0c;但四季桂花是个例外&#xff0c;只要温度合适养护得当&#xff0c;冬天也能让我们闻到桂花香。桂花开…

28篇标志性论文见证「自然语言处理NLP」2019-2020年度亮点进展

来源&#xff1a;专知【导读】自然语言处理专家elvis在medium博客上发表了关于NLP在2019年的亮点总结。对于自然语言处理&#xff08;NLP&#xff09;领域而言&#xff0c;2019年是令人印象深刻的一年。在这篇博客文章中&#xff0c;我想重点介绍一些我在2019年遇到的与机器学习…

spark while_05_尚硅谷大数据技术之Spark内核解析(1.1) - 十一vs十一

尚硅谷大数据课程之Spark内核解析(作者&#xff1a;尚硅谷大数据研发部)官网&#xff1a;版本&#xff1a;V1. Spark 内核概述Spark内核泛指Spark的核心运行机制&#xff0c;包括Spark核心组件的运行机制、Spark任务调度机制、Spark内存管理机制、Spark核心功能的运行原理等&am…

AI初创公司都去哪了?2019年科技公司“五巨头”收购盘点

大数据文摘出品来源&#xff1a;venturebeat人工智能人才的争夺大战愈演愈烈。 今年&#xff0c;Pinterest的首席技术官Vanja Josifovski离职加入了Airbnb&#xff0c;而Pinterest则聘请了沃尔玛的首席技术官Jeremy King来领导工程团队。此外&#xff0c;包括谷歌和苹果在内的所…

angular element()

使用angular.element()获取一个dom的方法。 1.可以使用jquery的选择器 2.可以使用javascript的原生查找元素的方法 下面是angular.element()提供的方法 <input type"checkbox" class"input" /><input type"text" class"input1&quo…

【前沿科技】看完这篇文章前,你绝对想象不到欧美航空机器人竟然发展到这个程度了!...

来源&#xff1a;中国机器人网《工程》杂志撰文认为&#xff0c;工业机器人在许多行业中已经建立了良好的基础&#xff0c;并且通常与现代化的先进制造系统相关联&#xff0c;但是&#xff0c;航空制造仍然严重依赖熟练的手工作业。航空制造中采用机器人的一个主要障碍是缺乏精…

beautifulsoup网页爬虫解析_Python爬虫快速入门,静态网页爬取

在开始之前&#xff0c;请确保你的电脑上已经安装好了BeautifulSoup库&#xff0c;可以通过在命令行中输入pip install beautifulsoup4来进行安装。一、数据解析在爬取之前&#xff0c;我们需要检测下响应状态码是否为200&#xff0c;如果请求失败&#xff0c;我们将爬取不到任…

当超级智能崛起时……

来源&#xff1a;资本实验室“大雷&#xff0c;你脑子没出啥毛病吧&#xff1f;”李春花轻声问道&#xff0c;眼睛里满是关切的神色。“喔&#xff0c;你放心&#xff0c;俺没事&#xff01;上次的事故之后&#xff0c;我的芯片和操作系统都已经升级到最新版本啦&#xff01;”…

小米小爱音箱Pro8安装app_小米小爱音箱HD获DXO评为第二!低音准确度很高

前几天&#xff0c;知名评测机构DXOMARK公布了用于测试无线音箱音质的新基准——DXOMARK Speaker。据了解&#xff0c;这个基准由百个测试汇集而成&#xff0c;该机构称这个基准为针对音箱产品唯一的科学评分。11月9日&#xff0c;该机构发文称&#xff0c;小米小爱音箱HD获得了…

Lua的垃圾回收机制详解

Lua 是一种轻量级的编程语言&#xff0c;广泛用于嵌入到其他应用程序中&#xff0c;尤其是在游戏开发领域。Lua 的内存管理机制采用了自动垃圾收集&#xff08;Garbage Collection&#xff09;的方法。以下是Lua内存管理的一些关键方面&#xff1a; 垃圾收集原理概述 Lua 使用…

2020年AI怎么发展?听加州大学、谷歌、英伟达、IBM怎么说

来源&#xff1a;机器之心AI 领域最杰出的头脑如何总结 2019 年技术进展&#xff0c;又如何预测 2020 年发展趋势呢&#xff1f;本文介绍了 Soumith Chintala、Celeste Kidd、Jeff Dean 等人的观点。人工智能不是将要改变世界&#xff0c;而是正在改变世界。在新年以及新的十年…

ip打包后如何加入 xilinx_科普!插上USB设备后电脑是怎么识别的呢?

欢迎FPGA工程师加入官方微信技术群每次当插上鼠标或者U盘的时候&#xff0c;电脑是怎么知道是什么设备的呢&#xff1f;这里用到的就是枚举了。枚举&#xff0c;其实就是让HOST认识这个USB涉笔&#xff0c;并且为该设备准备资源&#xff0c;建立好主机和设备之间的数据传递机制…

两院院士评选2019年中国、世界十大科技进展新闻揭晓

来源&#xff1a;科学网由中国科学院、中国工程院主办&#xff0c;中国科学院学部工作局、中国工程院办公厅、中国科学报社承办&#xff0c;中国科学院院士和中国工程院院士投票评选的2019年中国十大科技进展新闻、世界十大科技进展新闻&#xff0c;1月11日在京揭晓。此项年度评…

android炫酷叼ui,XUI: 一个简洁而优雅的Android原生UI框架,解放你的双手!

XUI一个简洁而又优雅的Android原生UI框架&#xff0c;解放你的双手&#xff01;还不赶紧点击使用说明文档&#xff0c;体验一下吧&#xff01;涵盖绝大部分的UI组件&#xff1a;TextView、Button、EditText、ImageView、Spinner、Picker、Dialog、PopupWindow、ProgressBar、Lo…

笔记本电脑麦克风在哪里_定制款MacBook Pro? 13.3 体验 “旧”时代的 One Pecie_笔记本电脑...

2020-11-11 13:19:377点赞18收藏18评论9月28日 - 11月12日&#xff0c;参与#双11购物攻略#征稿活动&#xff0c;赢取苹果全家桶8888元超级锦鲤大奖&#xff01;瓜分十万金币&#xff0c;值得买周边一次全攒齐&#xff01;品类、品牌、场景、价格&#xff0c;快来分享你的购物心…

杨振宁眼中的物理学之美

来源&#xff1a;EETOP本文是当代物理学大师杨振宁教授1997年1月17日在香港中华科学与社会协进会与香港中文大学主办的演讲会上的演讲词&#xff0c;讲题原为“科学工作有没有风格”。转载于香港《二十一世纪》杂志1997 年 4 月号&#xff0c;总第40期&#xff1b;也收入杨振宁…