图解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
图解Javascript——作用域、作用域链、闭包相关推荐
- 图解 | JavaScript的作用域和作用域链
文 / 景朝霞 来源公号 / 朝霞的光影笔记 ID / zhaoxiajingjing 图 / 自己画 ❥❥❥❥点个赞,让我知道你来过~❥❥❥❥ 前情提要: 题目 | let和var的区别(一.二) ...
- Javascript的作用域,作用域链,闭包
1,作用域和作用域链概念 作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.在JavaScript中,变量的作用域有全局作用域和局部作用域两种. 1.1 全局作用域,在代码 ...
- javascript函数作用域与闭包
8.8. 函数作用域与闭包 如第四章所述,JavaScript函数的函数体在局部作用域中执行,局部作用域不同于全局作用域.本章将解释这些内容和相关的作用域问题,包括闭包.[*] [*] ...
- JavaScript从作用域到闭包
目录 作用域 全局作用域和局部作用域 块作用域与函数作用域 作用域中的声明提前 作用域链 函数声明与赋值 声明式函数.赋值式函数与匿名函数 代码块 自执行函数 闭包 作用域(scope) 全局作用域 ...
- 重温JavaScript(lesson4):作用域和闭包(2)
在lesson3中我们重温了JS作用域有关的内容,理解了JS作用域再来看闭包就非常easy了. 1.闭包的概念 先来补充一个知识点(PS:如果你觉得不好理解,就看之后代码吧~),词法作用域:" ...
- javaScript的作用域、闭包
前言 JavaScript 中的闭包是相当重要的概念,并且与作用域相关知识的指向密切相关. 那么, JavaScript 中的作用域是什么? 闭包会在哪些场景中使用? 作用域 JavaScript 的 ...
- JavaScript的作用域与闭包
JavaScript的作用域以函数为界,不同的函数拥有相对独立的作用域.函数内部可以声明和访问全局变量,也可以声明局部变量(使用var关键字,函数的参数也是局部变量),但函数外部无法访问内部的局部变量 ...
- javascript中作用域、全局作用域、局部作用域、隐式全局变量、块级作用域、作用域链、预解析
作用域 作用域指的是代码的作用范围,按照作用域划分变量可分为全局变量和局部变量:作用域可分为: 全局作用域: 指全局变量作用的范围:全局变量指的是通过var在函数外面声明的变量,在js中任何位置都可以 ...
- 【金三银四】 一文弄懂 js 数据类型、堆栈内存、作用域(链)、闭包知识拓展 (一)
引言 对答如流系列篇,关于基本数据类型.堆栈内存.作用域作用域链.闭包 大家好,这里是lionLoveVue,基础知识决定了编程思维,学如逆水行舟,不进则退.金三银四,为了面试也还在慢慢积累知识,Gi ...
- JavaScript中的链(作用域链、原型链)
JavaScript中的链(作用域链.原型链) Js中存在两种链,作用域链和原型链.作用域链是为了访问变量而存在的链,原型链是访问对象的属性而存在的链. 作用域链 说到作用域链,首先来说下作用域的概念 ...
最新文章
- python界面长什么样子-这可能是最好玩的python GUI入门实例(推荐)
- 简单的相似图片搜索的原理
- pandas 常用操作
- EChart.js 简单入门
- 阿里云混合云的政企上云新路径
- 快速傅里叶变换python_【原创】OpenCV-Python系列之傅里叶变换(三十八)
- HTML5清除2个div标签的空白,DIV标签里面IMG图片下方留有空白怎么办
- DAY1-Workstation and CentOS7.x 快照
- Arm汇编 位置无关代码 adr 指令
- HTML5CSS3笔记:CSS3选择器、字体和颜色模式
- 编程这工作究竟是在干什么?
- 美国计算机硕士毕业在美薪资,美国计算机硕士留学费用全都在这了!
- html背景对联效果恭贺新春,恭贺新春的对联
- 利用python实现蚂蚁森林自动偷能量
- 华为云数据中心建设全面复工进行中,预计2021年投入使用
- android studio视频路径,Android studio相关设置及实现存在于工程目录中的视频播放...
- 前端性能优化 —— 前端性能分析
- js中单引号和双引号的使用区别
- BERT预训练模型的演进过程!(附代码)
- 电脑软件:推荐八款电脑必备效率软件
热门文章
- python自由落体_VPython - example - 模拟自由落体运动
- python连连看小游戏_python实现连连看游戏
- python 交互式可视化库_Python 交互式可视化库
- LeetCode 685. 冗余连接 II(并查集)
- LeetCode 1290. 二进制链表转整数
- LeetCode 1368. 使网格图至少有一条有效路径的最小代价(BFS最短路径,难)
- php实现文字向左跑马灯,js实现文字跑马灯效果
- 就业技术书文件表格_就业申请书
- 刷新页面,无论点击多少次让Element UI的Message消息提示弹出一个
- XPath和lxml类库