Posted in

深入理解 JavaScript 执行机制和闭包 – 掘金_AI阅读总结 — 包阅AI

包阅导读总结

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被执行')}

原始类型数据存放在栈里

引用类型数据存放在堆里

1.png

  • 变量提升: 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.png

2. addAll函数内部预编译之后,addAll执行上下文入栈,进行局部的执行

3.png

3. add函数内部预编译之后,add执行上下文入栈,进行局部的执行

4.png

4. add函数执行完成,add执行上下文出栈

5.png

5. addAll函数执行完成,addAll执行上下文出栈

6.png

6. 整份代码执行完毕,全局执行上下文出栈

7.png

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()

8.png

以上是一个关于块级作用域链的例子,接下来为大家展示一个关于函数作用域链的例子,让读者更深入了解以下:

作用域链:并不是在调用栈中从上往下查找,而是看当前执行上下文变量环境中的outer指向来定,而outer指向的规则是,词法作用域在哪里,outer就指向哪里

词法作用域:在函数定义时所在的作用域,由函数声明的位置来决定,跟函数在哪里调用没有关系

function bar () {    console.log(myName);}function foo(){    var myName = 'Tom';    bar();}var myName = 'Jerry';foo();

9.png

所以,打印结果是根据作用域链得到的“jerry”,而非依据栈结构得到的“Tom”

三:闭包

3.1:作用域与词法作用域

词法作用域:由函数声明的位置来决定,跟函数在哪里调用没有关系

10.jpg

3.2:闭包初识

请读者仔细阅读实例代码,试着自己画出调用栈的动态变化

function foo(){    function bar(){        var age = 18        console.log(myName)    }    var myName = 'midsummer'    return bar}var myName = 'Jerry'var fn = foo()fn()

11.png

读者们思路是不是这样:foo函数执行完成后,出栈,然后fn()的调用带来了bar()的执行,bar执行上下文入栈。可是这样有一个问题,此时在调用栈中的bar执行上下文的outer是指向foo执行上下文,foo执行上下文已经执行完毕出栈了,那不是会报错?其实不然,接下来为大家介绍闭包

在javaScript中,根据词法作用域的规则,内部函数一定能访问外部函数中的变量,当内部函数被拿到外部函数之外调用时,即使外部函数执行完毕,但是内部函数对外部函数中的变量依然存在引用,那么这些被引用的变量会以一个集合的方式保存下来,这个集合就是闭包

在foo执行上下文出栈时,留下bar执行上下文需要的变量集合就是闭包

12.png

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())

13.jpg

那么,闭包的作用也就不言而喻了

闭包作用:实现变量私有化

四:知识点get

  • 变量提升:js引擎在执行js的过程中把变量声明部分和函数声明部分提升到代码的头部,默认赋值为undefined,声明提升发生在编译阶段,执行阶段不会发生

  • 执行上下文

    • 变量环境:var声明的变量
    • 词法环境:let,const声明的变量
  • 调用栈:

    • 栈结构:特殊的数组,先进后出
    • 调用栈:JS引擎用来追踪函数调用关系的
    • 栈溢出:调用栈的内存超过限制
  • 作用域链: 并不是在调用栈中从上往下查找,而是看当前执行上下文变量环境中的outer指向来定,而outer指向的规则是,我的词法作用域在哪里,outer就指向哪里

  • 词法作用域:在函数定义时所在的作用域,由函数声明的位置来决定,跟函数在哪里调用没有关系

  • 闭包: 在javaScript中,根据词法作用域的规则,内部函数一定能访问外部函数中的变量,当内部函数被拿到外部函数之外调用时,即使外部函数执行完毕,但是内部函数对外部函数中的变量依然存在引用,那么这些被引用的变量会以一个集合的方式保存下来,这个集合就是闭包

  • 闭包作用: 实现变量私有化