JavaScript中执行上下文与作用域链

news/2025/2/23 11:28:28/文章来源:https://www.cnblogs.com/wyl-k/p/18731039

1、执行上下文与调用栈

 JavaScript代码执行过程分为两个阶段:代码编译阶段代码执行阶段。 编译阶段由编译器完成,将代码编译为可执行代码,这个阶段会确定作用域规则;执行阶段由JS引擎完成,主要任务是执行可执行代码,这个阶段会创建执行上下文

 可执行代码主要分为全局代码函数代码

  • 执行上下文:每个函数调用都会创建一个新的执行上下文。执行上下文是JavaScript代码执行时的环境,有三个重要组成部分

    1. 变量环境:用于存储该上下文中声明的所有变量和函数
    2. 词法环境:用于确定当前上下文中可以访问的外部变量
    3. this值:确定当前上下文中this的指向
  • 调用栈:是一种数据结构,用于管理执行上下文。当JS开始执行可执行代码时,最先遇见的是全局代码,因此初始化的时候就会在栈中压入全局代码,它会在最底层,等函数调用时会将新的执行上下文推入栈顶,执行完毕后该上下文被弹出栈,最后弹出全局执行上下文
代码示例:
function first() {console.log("first function");    second();  
}
function second() {console.log("second function");  
}
first();

 

代码执行过程:
  1. 创建全局执行上下文压入调用栈
  2. first()函数被调用,创建一个新的执行上下文并将其压入调用栈顶部;
  3. second()函数在first()函数内部被调用,因此创建一个新的执行上下文并压入调用栈顶部;
  4. second()执行完毕后,它的执行上下文从调用栈中弹出;
  5. first()继续执行直到完成,它的执行上下文也从调用栈中弹出;
  6. 最终,只有全局执行上下文还在调用栈中,直到整个脚本执行结束

2.变量提升和暂时性死区

  • 变量提升:在JS中,用var关键字声明的变量会经历“变量提升”的过程。即无论在代码中的哪里使用了var声明变量,这个变量的声明都会被移动到当前作用域的最顶端。但是只有变量的声明部分被提升了,赋值(初始化)没有被提升。因此,在声明之前访问的变量值都为undefined
  • 函数提升:在JS中,函数提升是指无论函数声明在代码的哪个位置,都会被移动到当前作用域的最顶端,相当于可以在实际声明函数之前就调用它。这个特性适用于函数声明,但不直接适用于函数表达式
sayHello(); // 正常输出Hello!    相当于sayHello函数声明被调到了最顶端
function sayHello() {alert("Hello!")  
}
myFunction(); // 报错    var声明的变量只会将变量提升,其初始值为undefined
var myFunction = function sayHello() {alert("Hello!") 
} 
  • 暂时性死区:ES6引入了let和const来声明变量和常量,和var不同的是,在声明这些变量之前访问都会报错(引用错误)。因为从块级作用域的顶端到变量声明之间的区域被称为“暂时性死区,在这一区域中访问变量就会报错。

 

console.log('a') // 报错
let a = 10;
console.log('b') // 报错
const b = 10;

 

3.块级作用域

 在ES6之前,作用域只有两种:全局作用域和函数作用域。

  • 块级作用域:ES6中新增了块级作用域,只有let和const声明的变量具有块级作用域,即它们只在声明它们的块(如if语句、for循环等)内有效
  • 块级作用域的生命周期:
  1. 创建:当程序执行到带有let 或const声明的代码块时,该块级作用域被创建;
  2. 活动:只要代码还在该块内执行,这个作用域就处于活动状态,声明的变量可以被当前作用域内的代码访问;
  3. 销毁:一旦控制流离开该代码块(即执行了最后一个花括号}后),该块级作用域就“结束”了。

4.词法作用域(outer属性)和作用域链

  •  词法作用域:也叫静态作用域,是编程语言中确定变量和函数可访问范围的一种机制。它基于书写位置来确定作用域,而不是函数的调用位置或执行时的上下文。相当于函数的作用域是在定义时确定的,而不是调用时,所以函数内部可以访问其外部作用域中的变量,而外部不能访问函数内部的变量
  • 外层作用域链:函数可以通过outer属性访问其外层作用域。
  • 作用域链:它描述了一个函数被执行时,查找变量的过程。每个执行上下文都有自己的变量对象,这个变量对象会链接起来形成一个链条,从当前执行上下文开始向上追溯到全局执行上下文。如果当前上下文中找不到某个变量就会一直往上找,直到找到改变量或者到达全局执行上下文为止。
function bar() {console.log(myName, 'bar'); // lisi
}
function foo() {var myName  = 'zhangsan';bar();console.log(myName, 'foo'); // zhangsan
}
var myName = 'lisi';
foo();

由于bar是在全局作用域中定义的,即使是在foo()中被调用,outer指向的还是全局作用域,不会经过foo()。意味着如果在全局没有定义myName,会报错myName is not defined。

总结 

  •  执行上下文:管理代码的执行环境
  • 词法环境与环境变量:存储该上下文中声明的变量和函数,用于确认当前上下文中可以访问的外部变量
  • 调用栈:管理执行上下文的数据结构,推入全局执行上下文->推入函数执行上下文->函数执行完毕后弹出->推入新的函数执行上下文->函数执行完毕后弹出->...->弹出全局执行上下文。
  • 变量提升:var声明的变量会被提升到当前作用域所在的顶部。
  • 暂时性死区:let和const声明的变量在声明之前不可以访问是因为块的顶端到声明变量的区域之间存在暂时性死区。
  • 块级作用域:let和const声明的变量具有块级作用域。
  • 词法作用域:函数的作用域在定义时确定,而非调用或执行时。
  • 作用域链:变量查找从当前作用域逐级向上查找,直到找到变量或到达全局执行上下文为止。

 

 

 

 

 

 

 

 

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

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

相关文章

MyBatis-Plus 的自动填充 —— 值为null

MyBatis-Plus 的自动填充 MyBatis-Plus 提供的字段自动填充功能是一种非常实用的特性,它能够在插入或更新数据库记录时自动填充一些公共字段,如创建时间(createTime)、更新时间(updateTime)、创建人(createBy)、更新人(updateBy)等。这一功能极大地简化了开发过程,减…

[Jaav SE/程序生命周期] 优雅的Java应用程序的启停钩子框架

序了解 spring 生态及框架的 java er 都知道,spring 应用的生命周期管理及配套接口较为优雅、可扩展。但脱离 spring 的 java 应用程序,如何优雅地启停、管理程序的生命周期呢?(以便应用程序在我们需要的运行阶段中进行相应的动作)概述:Java普通应用程序的启停钩子框架 前…

迈金C416码表体验

之前一直在用黑鸟单车和小米健康记录骑行数据, 后来又发现strava和keep的骑行分享功能做的比较好. 于是使用的运动app就越来越多, 骑行时带着手机的同时还要开好几个app太麻烦了, 同时也想给车上加一个速度显示器, 由此考虑入一个码表.对功能要求不高, 最主要的需求是gps和速度…

ASP.NET Core Swagger 汉化

在wwwroot文件夹添加汉化的JS文件const translations = {"Failed to load API definition": "API定义加载失败","Errors": "错误信息","Hide": "隐藏","Fetch error": "获取错误","Inte…

Svelte 最新中文文档教程(17)—— 生命周期钩子

前言 Svelte,一个语法简洁、入门容易,面向未来的前端框架。从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1:Svelte 以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非常适合构…

[Java SE] FunctionInterface 注解: 函数式接口(JDK8+)

概述:FunctionInterface/函数式接口 简介函数式接口是指只包含一个抽象方法的接口,可以使用 Lambda 表达式来创建该接口的实例。@FunctionalInterface 注解是 Java Lang 包中的一个注解,用于标识一个接口是函数式接口。@FunctionalInterface 注解的作用:编译时检查:@Funct…

去中心化 AI:赋权还是混乱?

DeepSeek 如何在 AI 去中心化方面迈出了巨大的一步,以及为什么这比你想象的更重要Nadia Piet + AIxDESIGN & Archival Images of AI / Better Images of AI / AI Am Over It / CC-BY 4.02025 年 1 月 27 日星期一,DeepSeek —— 一家几乎凭空冒出来的中国初创公司 —— 发…

SmolLM2:多阶段训练策略优化和高质量数据集,小型语言模型同样可以实现卓越的性能表现

SmolLM2 采用创新的四阶段训练策略,在仅使用 1.7B 参数的情况下,成功挑战了大型语言模型的性能边界:在 MMLU-Pro 等测试中超越 Qwen2.5-1.5B 近 6 个百分点 数学推理能力(GSM8K、MATH)优于 Llama3.2-1B 在代码生成和文本重写任务中展现出色表现 支持 8K tokens 的长文本处…

2025.2.23 (java的学习)

。。。昨天星期六一整天都没学习。。做了一个哆啦a梦。✌附一个目前弄的哆啦a梦全家桶吧。

自动驾驶两个传感器之间的坐标系转换

有两种方式可以实现两个坐标系的转换。 车身坐标系下一个点p_car,需要转换到相机坐标系下,旋转矩阵R_car2Cam,平移矩阵T_car2Cam。点p_car在相机坐标系下记p_cam. 方法1:先旋转再平移 p_cam = T_car2Cam * p_car + T_car2Cam 需要注意的是,这里的平移矩阵T_car2Cam是车身坐…

8-2 MySQL 索引的设计原则(超详细说明讲解)

8-2 MySQL 索引的设计原则(超详细说明讲解) @目录8-2 MySQL 索引的设计原则(超详细说明讲解)1. 测试数据准备2. 哪些情况适合创建索引2.1 字段的数值有唯一性的限制2.2 频繁作为 WHERE 查询条件的字段2.3 经常 GROUP BY 和 ORDER BY 的列2.4 UPDATE、DELETE 的 中的WHERE 条件列…

北京时间同步

本地时间与北京时间 同步 参考:https://blog.csdn.net/qq_46092061/article/details/128748789 本地显示秒数while True:current_time = time.strftime("%H:%M:%S")if current_time >= "19:59:59":print("start")breaktime.sleep(0.5) # 减…