javascript 的执行上下文与作用域

目录

    • 1. 初步了解 上下文(context)
    • 2. 全局上下文(global context)
    • 3. 上下文栈 (context stack)
    • 4. 作用域链( scope chain)
    • 5. 作用域(scope)
    • 6. 作用域链增强
    • 7. 变量声明
      • 7.1 var 声明变量
      • 7.2 let 声明变量
      • 7.3 const 常量声明

1. 初步了解 上下文(context)

   上下文(context) 全称 执行上下文 (execution context) 。

  变量和函数的上下文 决定了它们可以访问哪些数据,以及它们的行为。所以 上下文 也可以说是 变量和函数所处的环境

  每个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据总是用到它。

2. 全局上下文(global context)

  全局上下文是最外层的上下文。浏览器中,全局上下文就是 window 对象,所有通过 var 定义的全局变量和函数都会成为window对象的属性和方法
  使用 let 和const 的顶级声明不会定义在全局上下文中,但在作用链解析上效果是一样的。上下文在其所有代码都执行完毕后会被销毁,包含定义在它上面的所有变量和函数,全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器。

3. 上下文栈 (context stack)

  每个函数调用都有自己的上下文,当代码执行到某个函数时,函数的上下文被推到(push)一个 上下文的栈结构 中,忘了栈结构的,可以查看 栈的基本操作。在函数执行完之后,上下文栈会弹出(pop)该函数的上下文,将控制权返还给之前的执行上下文。ECMAScript程序的执行流就是通过这个上下文栈进行控制的。

4. 作用域链( scope chain)

   学习作用域,先了解一下做应用于链。这个链,其实就是上面说的上下文栈结构。
  上下文中的代码在执行的时候,会创建变量对象的一个作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终处于作用域链的最前端。如果上下文是函数,则其活动对象(activation object) 用作 变量对象。活动对象最初只有一个变量:参数(arguments)。题外话:上面提到的全局上下文是没有这个变量的。作用域链中的下一个变量对象来自包含上下文,再下一个对象来自下一个包含上下文。依次类推直至全局上下文。全局上下文的变量对象始终是作用域链的最后一个变量对象。

  代码执行的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。搜索过程始终从作用域链的最前端开始,然后逐级往后,直到找到标识符。(未找到标识符,通常会报错)

5. 作用域(scope)

作用域,就是 变量可用性的范围。通俗的讲,就是哪些函数可以访问到这个变量。
先看下面的例子:


var color = "blue";
function changeColor() {if (color === "blue") {color = "red";} else{color = "blue";}
}
changeColor();

上述函数 changeColor() 的作用域包含两个对象:一个是它自己的变量对象(即 arguments 对象的那个),另一个是全局上下文的变量对象。这个函数内部之所以能够访问变量color,就是因为可以在作用域链中找到它。
局部作用域中定义的变量可用于在局部上下文中替换全局变量。看看下面这个例子:


var color = "blue";
function changeColor() {let anotherColor = "red";function swapColors(){let tempColor = anotherColor;anotherColor = color;color = tempColor;//此处为swapColors内部,可以访问color、anotherColor 和tempColor}//此处为changeColor内部,可以访问 color 和anotherColor ,但访问不到tempColor
}
// 这里只能访问color 
changeColor();

   以上代码涉及到3个上下文:全局上下文、changeColor()的局部上下文和 swapColors()的局部上下文。他们的关系是全局上下文包含 changeColor()上下文,changeColor()包含 swapColors()。
他们的关系如下图:
在这里插入图片描述

   对于swapColos来说,发现变量color, 先在自己的局部上下文 swapColors() 中搜索,发现自己并没有color的定义。往父级上下文changeColor () 中搜索,还是没有搜索到,则到全局作用域 window中搜索,本次搜索到了。

6. 作用域链增强

上下文包含全局上下文和函数上下文两种方式,按照栈的结构形成作用域链,按照顺序向上级搜索,但是有两种情况增强作用域链:

 a.  try/catch 语句的catch块b.  with 语句

这两种情况 直接在作用域链前端临时添加一个上下文,在代码执行后被删除。
对于with语句来说,会想作用域链前端添加一个指定的对象;
对于catch语句而言,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明。

function buildUrl(){let qs = "?debug=true";let locHost;with (location) {let url = href + qs;locHost = host;}return url;  // 这里的url为 undefined
}

   上面代码中,with 语句将 location 对象作为上下文,因此 location会被添加到作用域链前端。buildUrl函数中定义了一个变量 qs。 当 with 语句中的代码应用变量 href 时, 实际上引用的是 location.href,也就是自己变量对象的属性。。
在这里插入图片描述

   在引用 qs 时,引用的则是定义在 buildUrl() 中的那个变量,它定义在函数上下文的变量对象上。而在with 语句中如果使用 var声明的变量 url 会成为函数上下文的一部分,可以作为函数的值返回;但这里使用 let 声明的变量url ,因为被限制在块级作用域,所以在with 块之外没有定义。

7. 变量声明

7.1 var 声明变量

   在使用 var 声明变量时,变量会被自动添加到最接近的上下文。在函数中,最近的上下文就是函数的局部上下文;在with语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文。

	function add(num1,num2){var sum = num1 + num2 ;return sum;}let  result = add(10,20); // 30 console.log(sum); //这里会报错,因为变量 sum 是在 函数 add() 的上下文中。

   上面例子中,函数 add() 定义的局部变量 sum ,在函数外部是访问不了的。因为sum是在函数 add() 的上下文中。再看下面的例子:

	function add(num1,num2){sum = num1 + num2 ;return sum;}let  result = add(10,20);  // 30 console.log(sum); 				// 30,这里不会报错

   这一次 函数外部,就能直接访问sum。因为sum没有声明,直接初始化,就被当做全局变量了。

注意 未经声明而初始化变量是 JavaScript 编程中一个非常常见的错误,会导致很多问题。
为此,读者在初始化变量之前一定要先声明变量。在严格模式下,未经声明就初始化变量
会报错 。(摘自 《javascript 高级程序设计》)

   var 声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前。这个现象叫作“提升”(hoisting)。提升让同一作用域中的代码不必考虑变量是否已经声明就可以直接使用。可是在实践中,提升也会导致合法却奇怪的现象,即在变量声明之前使用变量。下面的例子展示了在全局作用域中两段等价的代码:

var name = "bourne";
//等价于
name = "bourne";
var name;

两个等价函数:

	function func1(){var name ="bourne";}//等价于function func2(){var name;name = "bourne" ;}

   通过在声明之前打印变量,可以验证变量会被提升。由于声明的提升会输出 未赋值(undefined) ,而不是 引用错误(Reference Error)

 console.log(name);		//undefined(未赋值), 而不是 Reference Error (引用错误)var name = "bourne";function (){console.log(name); //undefined(未赋值), 而不是 Reference Error (引用错误)var name = "bourne";}

7.2 let 声明变量

ES6新增 的 let 关键字。 let 块级作用域, var 为函数级作用域。块级作用域由最近的一对包含花括号 { } 界定 。这也就意味着 ,if 块,while 块,function 块,甚至 单独的只有花括号的内部代码块 也是 let 声明 变量的作用域。


if (true) {let a ;
}
console.log(a); // Reference Error : a 无定义while (true){let b;
}
console.log(b); // Reference Error : b 无定义function func() {let c;
}
console.log(c); // Reference Error : c 无定义//下面只有花括号的情况,(js解释器默认其合法的)
{let d;
}
console.log(d); // Reference Error : d 无定义

   let 与 var 还有不同之处是 同一作用域内不能声明两次。重复的 var 声明 会被忽略(个人认为这是 js 设计之初考虑不周的地方),而重复的 let 声明 会抛出 语法错误(Syntax Error)。

var a ;
var a ; //这里不会报错
let b ;
let b ; // Syntax Error ,因为 标识符 b 已经声明过了

let 的特性使它非常适合在循环中声明迭代变量。使用 var 声明的迭代变量会泄漏到循环外部,这种情况应该避免。来看下面两个例子:

for (var i = 0 ; i < 5 ; ++i){}
console.log(i); //5 此处仍然可以识别到 ifor (let j = 0; j < 10; ++j){}
console.log(j);		//Reference Error, 无法识别 j了

严格来讲, let 在 JavaScript 运行时中也会被提升,但由于“暂时性死区”(temporal dead zone)的缘故,实际上不能在声明之前使用 let 变量。因此,从写 JavaScript 代码的角度说, let 的提升跟 var是不一样的。(这一段摘自《javascript 高级程序设计》中原文,因为没看懂,直接摘抄过来了)

7.3 const 常量声明

ES6 新增关键字 const。使用const 声明变量必须同时初始化(因为常量放在内存的数据区中的常量区(只读的,所以不能后面修改))。声明以后,在其生命周期的任何时候都不能再重新赋予新值。

const a ;  //Syntax Error,语法错误,因为常量声明时,没有初始化
const b = 3;
console.log(b); //3
b = 4 ; //TypeError :给常量赋值

const 也是块级作用域,和 let 的作用域一样:

if (true) {const a ;
}
console.log(a); // Reference Error : a 无定义while (true){const b;
}
console.log(b); // Reference Error : b 无定义function func() {const c;
}
console.log(c); // Reference Error : c 无定义//下面只有花括号的情况,(js解释器默认其合法的)
{const d;
}
console.log(d); // Reference Error : d 无定义

const 声明 引用类型变量,是否可以修改其属性?看一下下面的例子:


//第一种情况
const people = {};
people = {};	// TypeError: 类型错误,因为给常量赋值了//第二种情况
const people2 = {name:"bourne"};
people2.name = "pitt"; 		
console.log(people2.name); 	//输出 pitt ,属性被修改了

上面第二种情况,之所以能够修改对象的属性,是因为 变量 people 内保存的其实是 {name:“bourne”} 这个对象的地址。地址是常量,故不可以修改,但是对象存放在内存堆区,它的属性是可以修改的。

那么想要连对象的属性也不能修改,有没有办法呢? 答案:有。就是 Object.freeze()。
看看下面代码:

 const people3 =Object.freeze({name:"bourne"});people3.name ="Pitt";console.log(people2.name);  //输出 bourne, 属性没有被修改。

由于 const 声明暗示变量的值是单一类型且不可修改,JavaScript 运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找。(虽然 js是解释型语言,但是 谷歌的V8引擎 采用先编译后运行)

注意 :开发实践表明,如果开发流程并不会因此而受很大影响,就应该尽可能地多使用 const 声明,除非确实需要一个将来会重新赋值的变量。这样可以从根本上保证提前发现重新赋值导致的 bug。摘自《javascript 高级程序设计》

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

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

相关文章

React前端面试每日一试 2.JSX是什么?JSX如何工作?

JSX是什么&#xff1f; JSX&#xff08;JavaScript XML&#xff09;是React引入的一种语法扩展&#xff0c;用于在JavaScript中编写类似HTML的结构。它让我们能够直观地描述UI的结构&#xff0c;同时保留JavaScript的编程能力。尽管JSX看起来像HTML&#xff0c;但它最终会被编…

轨迹优化 | 基于ESDF的共轭梯度优化算法(附ROS C++/Python仿真)

目录 0 专栏介绍1 数值优化&#xff1a;共轭梯度法2 基于共轭梯度法的轨迹优化2.1 障碍约束函数2.2 曲率约束函数2.3 平滑约束函数 3 算法仿真3.1 ROS C实现3.2 Python实现 0 专栏介绍 &#x1f525;课程设计、毕业设计、创新竞赛、学术研究必备&#xff01;本专栏涉及更高阶的…

Unity3D UGUI适配不同分辨率详解

前言 在Unity3D开发中&#xff0c;UGUI&#xff08;Unitys Graphical User Interface&#xff09;是构建用户界面&#xff08;UI&#xff09;的重要工具。然而&#xff0c;随着移动设备和桌面设备的分辨率日益多样化&#xff0c;确保UI能够在不同分辨率下良好显示变得尤为重要…

CAS乐观锁原理

1、什么是CAS&#xff1f; compare and swap也就是比较和交换&#xff0c;他是一条CPU的并发原语。 他在替换内存的某个位置的值时&#xff0c;首先查看内存中的值与预期值是否一致&#xff0c;如果一致&#xff0c;执行替换操作。 这个操作是一个原子性操作。 Java中基于Un…

手机免费恢复照片的软件有哪些?这2个工具来帮忙

照片是我们情感的载体&#xff0c;是记忆的碎片。它们无声地诉说着过去的故事&#xff0c;记录着生活中的点点滴滴。但意外常常是突如其来的&#xff0c;当发现手机照片丢失时&#xff0c;我们往往心痛不已。 不用担心&#xff0c;这场看似绝望的危机&#xff0c;实则有解决之…

C++ OpenCV 实现多张图片叠加 叠加文字

C OpenCV 实现多张图片叠加 叠加文字 在C中使用OpenCV叠加多张图片以及添加文字的基本步骤如下&#xff1a; 加载多张图片。 确定叠加位置。 使用cv::addWeighted叠加图片&#xff0c;可以为叠加的图片添加透明度。 使用cv::putText在图片上添加文字。 显示或保存结果图片…

Sql Server缓冲池、连接池等基本知识(附Demo)

目录 前言1. 缓存池2. 连接池3. 彩蛋 前言 基本的知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;Mysql优化高级篇&#xff08;全&#xff09;Mysql底层原理详细剖析常见面试题&#xff08;全&#xff09; 1…

Go 环境安装配置

1、下载 wget https://go.dev/dl/go1.21.6.linux-amd64.tar.gz 2、安装 sudo tar -xvf go1.21.6.linux-amd64.tar.gz -C $HOME/3、设置环境变量及代理 # 打开 ~/.bash_profile,输入&#xff1a; export PATH$PATH:$HOME/go/bin # 设置 Go 语言代理 export GOPROXYhttps://go…

AI发展下的伦理挑战

AI发展下的伦理挑战&#xff0c;应当如何应对&#xff1f; 人工智能飞速发展的同时&#xff0c;也逐渐暴露出侵犯数据隐私、制造“信息茧房”等种种伦理风险。随着AI技术在社会各个领域的广泛应用&#xff0c;关于AI伦理和隐私保护问题日趋凸显。尽管国外已出台系列法规来规范…

ls lsattr lsblk lscpu lsdiff lshw lsinitrd lsipc lslocks lslogins

ls: 描述&#xff1a;列出目录内容。用法&#xff1a;ls [选项] [文件或目录]示例&#xff1a; ls&#xff1a;列出当前目录的文件和子目录。ls -l&#xff1a;以详细列表形式显示目录内容。ls /path/to/directory&#xff1a;列出指定路径下的内容。 lsattr: 描述&#xff1a;…

【VSCode】安装 【ESP-IDF】插件及【ESP32-S3】新建工程和工程配置

一、搭建基础工程 二、基础工程的文件架构解析 三、调试相关工具介绍 1、串口下载2、JTAG 下载与调试 四、工程的文件架构解析 五、基础工程配置 一、搭建基础工程 在 VS Code 中新建 ESP-IDF 基础工程的步骤如下&#xff1a; 1、启动 VS Code 并打开命令面板 按下“Ctrl…

逆向案例二十八——某高考志愿网异步请求头参数加密,以及webpack

网址&#xff1a;aHR0cDovL3d3dy54aW5nYW9rYW90Yi5jb20vY29sbGVnZXMvc2VhcmNo 抓包分析&#xff0c;发现请求头有参数u-sign是加密的&#xff0c;载荷没有进行加密&#xff0c;直接跟栈分析。 进入第二个栈&#xff0c;打上断点&#xff0c;分析有没有加密位置。 可以看到参数…

Chapter17 表面着色器——Shader入门精要学习

Chapter17 表面着色器 一、编译指令1.表面函数2.光照函数3.其他可选参数 二、两个结构体1.Input 结构体&#xff1a;数据来源2.SurfaceOutput 三、Unity背后做了什么四、表面着色器的缺点 一、编译指令 作用&#xff1a;指明该表面着色器的 表面函数 和 光照函数&#xff0c;并…

【React】深入探索React:表单控制、组件通信、副作用管理、自定义Hook

一、React表单控制 表单控制是React应用中常见的需求&#xff0c;React Hooks提供了两种方式来实现&#xff1a;受控组件和非受控组件。 1.1 受控组件 受控组件是将表单输入的值保存在组件的状态中。这样&#xff0c;表单数据就完全由React控制。 import React, { useState…

【系统架构设计 每日一问】三 Redis支持事务么,Redis的事务如何保证

实际上&#xff0c;关于Redis事务的说法“Redis 的事务只能保证隔离性和一致性&#xff08;I 和 C&#xff09;&#xff0c;无法保证原子性和持久性&#xff08;A 和 D&#xff09;”并不完全准确。下面我将分别解释Redis事务的四个特性&#xff1a;原子性&#xff08;Atomicit…

探索Mojo模型的超参数优化:自定义搜索策略全解析

探索Mojo模型的超参数优化&#xff1a;自定义搜索策略全解析 在机器学习领域&#xff0c;超参数的调整是提高模型性能的关键步骤。Mojo模型&#xff0c;作为一种高效的模型部署方式&#xff0c;其超参数的搜索同样至关重要。本文将深入探讨如何在Mojo模型中实现自定义的超参数…

5.波士顿房价预测(KNN,决策树,线性回归)

波士顿房价预测 1. 机器学习中的任务分类2. 波士顿房价预测2.1 分析数据2.2 比较 MAE 和 MSE2.2 代码 1. 机器学习中的任务分类 有监督学习&#xff08;supervised&#xff09;&#xff1a;有特征也有标签 分类问题 classification预测离散量 回归问题 regression预测连续量 …

DPDK收包流程和Linux内核收包流程对比

DPDK 网卡收包流程-腾讯云开发者社区-腾讯云NIC 在接收到数据包之后&#xff0c;首先需要将数据同步到内核中&#xff0c;这中间的桥梁是 rx ring buffer。它是由 NIC 和驱动程序共享的一片区域&#xff0c;事实上&#xff0c;rx ring buffer 存储的并不是实际的 packet 数据&a…

【Gin】精准应用:Gin框架中工厂模式的现代软件开发策略与实施技巧(上)

【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(上) 大家好 我是寸铁&#x1f44a; 【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(上)✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 本次文章分为上下两部分&…

Golang使用docker sdk管理docker

包括列出容器、创建容器、删除容器、进入容器、构建镜像等操作。 package dockertoolimport ("context""fmt""github.com/docker/docker/api/types""github.com/docker/docker/api/types/container""github.com/docker/docker/ap…