包阅导读总结
1. `JavaScript`、`执行机制`、`闭包`、`作用域链`、`变量提升`
2. 本文深入讲解了 JavaScript 的执行机制和闭包概念。包括执行机制中的前置知识、栈结构、作用域链,还通过实例介绍了闭包的定义、运用和作用,最后总结了相关的重要知识点。
3.
– 前置知识
– 原始类型数据存栈,引用类型数据存堆
– 变量提升:编译阶段将变量和函数声明提升到头部,默认 `undefined`
– JavaScript 的执行机制
– 栈结构:特殊数组,先进后出,用于追踪函数调用关系,可能栈溢出
– 作用域链:根据当前执行上下文变量环境中的 `outer` 指向确定
– 词法作用域:由函数声明位置决定,与调用位置无关
– 闭包
– 初识:内部函数能访问外部函数变量,外部函数执行完,内部函数对外部变量的引用以集合保存,即闭包
– 运用:实现变量私有化
– 知识点总结
– 变量提升、执行上下文、调用栈、作用域链、闭包的定义和作用
思维导图:
文章地址:https://juejin.cn/post/7385905203646464038
文章来源:juejin.cn
作者:midsummer18
发布时间:2024/6/30 22:01
语言:中文
总字数:2146字
预计阅读时间:9分钟
评分:88分
标签:JavaScript,执行机制,作用域链,闭包,编译原理
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
一:前置知识
二:js的执行机制
2.1:执行机制中的基础
先来一段简单的代码,感受一下v8在js中的执行机制
showName()console.log(myName)var myName = 'midsummer'function showName() { console.log('函数showName被执行')}
原始类型数据存放在栈里
引用类型数据存放在堆里
- 变量提升: js引擎在执行js的过程中把变量声明部分和函数声明部分提升到代码的头部,默认赋值为undefined,声明提升发生在编译阶段,执行阶段不会发生
- 预编译完成之后,开始执行:第二行会打印出undefined,之后myName被赋值为‘midsummer’,然后函数showName被执行,打印’函数showName被执行’
2.2:执行机制中的栈结构
- 栈结构: 特殊的数组,先进后出
- 调用栈: JS引擎用来追踪函数调用关系的
- 栈溢出: 调用栈的内存超过限制
函数执行完毕后,该函数的执行上下文会销毁(出栈)
var a = 2function add(b,c){ return b+c}function addAll(b,c){ var d=10 var result = add(b,c) return a + result +d}addAll(3,6)
以这份代码为例,在执行时,调用栈的变化如下:
1. 全局预编译之后,全局执行上下文入栈,进行全局的执行
2. addAll函数内部预编译之后,addAll执行上下文入栈,进行局部的执行
3. add函数内部预编译之后,add执行上下文入栈,进行局部的执行
4. add函数执行完成,add执行上下文出栈
5. addAll函数执行完成,addAll执行上下文出栈
6. 整份代码执行完毕,全局执行上下文出栈
2.3:执行机制中的作用域链
先上一个简单的例子引入一下
function foo(){ var a =1 let b =2 { let b =3 var c =4 let d =5 console.log(a) console.log(b) } console.log(b) console.log(c) console.log(d)}foo()
以上是一个关于块级作用域链的例子,接下来为大家展示一个关于函数作用域链的例子,让读者更深入了解以下:
作用域链:并不是在调用栈中从上往下查找,而是看当前执行上下文变量环境中的outer指向来定,而outer指向的规则是,词法作用域在哪里,outer就指向哪里
词法作用域:在函数定义时所在的作用域,由函数声明的位置来决定,跟函数在哪里调用没有关系
function bar () { console.log(myName);}function foo(){ var myName = 'Tom'; bar();}var myName = 'Jerry';foo();
所以,打印结果是根据作用域链得到的“jerry”,而非依据栈结构得到的“Tom”
三:闭包
3.1:作用域与词法作用域
词法作用域:由函数声明的位置来决定,跟函数在哪里调用没有关系
3.2:闭包初识
请读者仔细阅读实例代码,试着自己画出调用栈的动态变化
function foo(){ function bar(){ var age = 18 console.log(myName) } var myName = 'midsummer' return bar}var myName = 'Jerry'var fn = foo()fn()
读者们思路是不是这样:foo函数执行完成后,出栈,然后fn()的调用带来了bar()的执行,bar执行上下文入栈。可是这样有一个问题,此时在调用栈中的bar执行上下文的outer是指向foo执行上下文,foo执行上下文已经执行完毕出栈了,那不是会报错?其实不然,接下来为大家介绍闭包
在javaScript中,根据词法作用域的规则,内部函数一定能访问外部函数中的变量,当内部函数被拿到外部函数之外调用时,即使外部函数执行完毕,但是内部函数对外部函数中的变量依然存在引用,那么这些被引用的变量会以一个集合的方式保存下来,这个集合就是闭包
在foo执行上下文出栈时,留下bar执行上下文需要的变量集合就是闭包
3.3:闭包的运用
阅读下面代码,请读者们思考是否可以实现count的累加,输出1,2,3?
function add(){ let count = 0 count++ return count}console.log(add())console.log(add())console.log(add())
显然是不可以的,会输出1,1,1。因为调用一次add之后,执行完毕后,执行上下文会被销毁,再次调用add,会重新预编译和执行,所以还是会输出1。想要达到预期效果,可以将count声明在全局,add执行上下文的销毁不会影响全局变量,但这种做法会带来命名重复,项目维护成本大,这时候闭包就是很好的封装手段,读者可以自行尝试用闭包修改代码,并画出调用栈
function add(){ let count = 0 function fn (){ count++ return count } return fn}var res = add()console.log(res())console.log(res())console.log(res())
那么,闭包的作用也就不言而喻了
闭包作用:实现变量私有化
四:知识点get
-
变量提升:js引擎在执行js的过程中把变量声明部分和函数声明部分提升到代码的头部,默认赋值为undefined,声明提升发生在编译阶段,执行阶段不会发生
-
执行上下文:
- 变量环境:var声明的变量
- 词法环境:let,const声明的变量
-
调用栈:
- 栈结构:特殊的数组,先进后出
- 调用栈:JS引擎用来追踪函数调用关系的
- 栈溢出:调用栈的内存超过限制
-
作用域链: 并不是在调用栈中从上往下查找,而是看当前执行上下文变量环境中的outer指向来定,而outer指向的规则是,我的词法作用域在哪里,outer就指向哪里
-
词法作用域:在函数定义时所在的作用域,由函数声明的位置来决定,跟函数在哪里调用没有关系
-
闭包: 在javaScript中,根据词法作用域的规则,内部函数一定能访问外部函数中的变量,当内部函数被拿到外部函数之外调用时,即使外部函数执行完毕,但是内部函数对外部函数中的变量依然存在引用,那么这些被引用的变量会以一个集合的方式保存下来,这个集合就是闭包
-
闭包作用: 实现变量私有化