JS变量和函数提升

JS变量和函数提升

  • JS变量提升
    • 编译阶段
    • 执行阶段
    • 相同变量或函数
  • 变量提升带来的问题
    • 变量容易不被察觉的遭覆盖
    • 本应销毁的变量未被销毁
  • 如何解决变量提升带来的问题

JS变量提升

sayHi()

console.log(myname)

var myname = 'yy'

function sayHi() {
  console.log('Hi')
}

// 执行结果:
// Hi
// undefined

相信学过 JavaScript 的都知道这个执行结果的原理:JS的变量提升特性

注意:这里必须使用 var 来定义变量,如果使用 let 或者 const 来定义的变量不会有变量提升,会报错未定义。只有用声明方式定义的函数才具有变量提升特性。

所谓的变量提升,是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined

在这里插入图片描述

上图是我们模拟的变量提升的效果,从概念的字面意义上看变量和函数声明都会被移动到代码的最前面,但,这并不准确。实际上变量和函数声明在代码里的位置是不会改变的,一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译,编译完成后才会进入执行阶段。执行流程细化图如下

在这里插入图片描述

编译阶段

一段 JavaScript 代码,经过编译后,会生成两部分:执行上下文Execution context)和可执行代码

执行上下文是 JavaScript 执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如 this、变量、对象以及函数等。

在执行上下文中存在一个变量环境的对象Variable Environment),该对象中保存了变量提升的内容。下面来分析这个变量环境对象是如何生成的:

  1. 第1行和第2行,由于这两行都不是声明操作,所以 JavaScript 引擎不做任何处理
  2. 第3行,由于是经过 var 声明的,因此 JavaScript 引擎将在环境对象中创建一个名为 myname 的属性,并使用 undefined 对其初始化
  3. 第4行,JavaScript 引擎发现了一个通过 function 定义的函数,所以它将函数定义存储到堆(HEAP)中,并在环境对象中创建一个名为 sayHi 的属性,然后将该属性值指向堆中函数的位置。

就这样生成了变量环境对象。接下来 JavaScript 引擎会把声明以外的代码编译为字节码(可以类比可执行代码内容)。现在有了执行上下文和可执行代码,接下来就到了执行阶段了。

执行阶段

JavaScript 引擎会按照顺序一行一行地执行“可执行代码”,下面是整个执行过程:

  1. 当执行到 sayHi 函数时,JavaScript 引擎便开始在变量环境对象中查找该函数,由于变量环境对象中存在该函数的引用,所以JavaScript 引擎便开始执行该函数,打印 Hi 结果
  2. 接下来打印 myname 信息,JavaScript 引擎查找到该变量值在变量环境对象中的值为 undefined,打印 undefined 结果
  3. 接下来执行赋值操作,将 yy 赋值给 myname 变量,赋值后变量环境对象中的 myname 属性值变为 yy

相同变量或函数

那么如果代码中出现了重名的函数或者变量,JavaScript 引擎会如何处理呢?思考一下,如下代码输出的内容会是什么?

function sayHi() {
  console.log('Hi')
}

sayHi()

function sayHi() {
  console.log('Hello')
}

sayHi()

如果你理解了上面所说的 JavaScript 的执行机制:先编译,再执行。那么你一定能理解这段代码的输出结果

// Hello
// Hello
  1. 遇到第一个 sayHi 函数,会将函数定义存储到堆(HEAP)中,并在环境对象中创建一个名为 sayHi 的属性,然后将该属性值指向堆中函数的位置
  2. 遇到第二个 sayHi 函数,此时变量环境中已存在 sayHi 属性,此时将 sayHi 属性重新赋值指向堆中第二个函数的位置,这样变量环境中就只存在第二个函数了
  3. 执行第一次和执行第二次其实都是在执行最后的那个函数

再来看一个实例

sayHi() // Hello

var sayHi = function() {
  console.log('Hi')
}

// sayHi() // Hi

function sayHi() {
  console.log('Hello')
}
  1. 变量提升 sayHi 属性并初始化值为 undefined
  2. 函数提升 sayHi 属性指向 “Hello函数”
  3. 执行 sayHi() 函数等于执行 “Hello函数”,输出 Hello
  4. 变量 sayHi 赋值为 “Hi函数”

如果在 sayHi 函数声明前执行 sayHi() 函数,就会打印出 Hi,因为变量和函数提升后,就进行就是执行阶段,代码会一行一行往下执行,到这里时,sayHi 属性已经是 “Hi函数”了

模拟变量提升过程如下:

// 变量提升部分
var sayHi = undefined
function sayHi() {
  console.log('Hello')
}
// 可执行代码部分
sayHi() // Hello
sayHi = function() {
  console.log('Hi')
}
//sayHi() // Hi

变量提升带来的问题

变量容易不被察觉的遭覆盖

var myname = 'yy'
function sayHi() {
  console.log(myname)
  if(0) {
    var myname = 'qq'
  }
}
sayHi()
// 执行结果:
// undefined

undefined 结果有没有震惊到你!下面我们来分析一下这段代码的调用栈

关于JS的调用栈你可以去看看这篇JS调用栈

首先来看下在执行 sayHi 函数时的调用栈

在这里插入图片描述

sayHi 函数的执行上下文创建后,JavaScript 引擎便开始执行 sayHi 函数内部代码,由于 var 关键字具有变量提升特性,且 var 定义的变量没有块级作用域概念,可以跨块级作用域访问,也就是说他可以跨 if{} 的块级作用域,所以在函数执行上下文中首先会将 myname 变量提升并初始化值为 undefined,之后逐行执行可执行代码 console.log(myname),这行这段代码需要变量 myname ,结合调用栈状态图可以看到这里有两个 myname 变量,那在函数执行上下文中肯定首选自己变量环境中的同名变量。所以打印结果是 undefined

如果你对JS调用栈不了解可以先看看这篇JS调用栈
如果你对JS中块级作用域不了解可以看看这篇JS作用域:全局作用域,函数作用域,块级作用域
相信会对你有所帮助

如果对以上代码做如下改动呢?

var myname = 'yy'
function sayHi() {
  console.log(myname)
  if(1) {
    var myname = 'qq'
  }
  console.log(myname)
}
sayHi()

如果你理解了上面的执行过程,相信你不难得出执行结果为

// undefined
// qq

本应销毁的变量未被销毁

function foo() {
  for (var i = 0; i < 7; i++) {

  }
  console.log(i)
}
foo()

如果你使用 C 语言或者其他大部分语言实现类似代码,在 for 循环结束之后,i 就会被销毁了,但是在 JavaScript 代码中,同样因为 var 的变量提升和可以跨块级作用域,在创建函数执行上下文阶段,变量 i 就已经被提升了,所以即使 for 循环结束,它还存在函数作用域中,所以最后打印出来的是 7

如何解决变量提升带来的问题

参考文章JS作用域:全局作用域、函数作用域、块级作用域

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

我的2023年,平淡中寻找乐趣

文章目录 两个满意我学会了自由泳。学习英语 一个较满意写博客 2024的期望 2023年&#xff0c;我有两个满意&#xff0c;一个较满意。 两个满意 我学会了自由泳。 开始练习自由泳是从2023年3月份&#xff0c;我并没有请教练&#xff0c;而是自己摸索。在抖音上看自由泳的视频…

【一分钟】ThinkPHP v6.0 (poc-yaml-thinkphp-v6-file-write)环境复现及poc解析

写在前面 一分钟表示是非常短的文章&#xff0c;只会做简单的描述。旨在用较短的时间获取有用的信息 环境下载 官方环境下载器&#xff1a;https://getcomposer.org/Composer-Setup.exe 下载文档时可以设置代理&#xff0c;不然下载不上&#xff0c;你懂的 下载成功 cmd cd…

JavaWeb——前端之HTMLCSS

学习视频链接&#xff1a;https://www.bilibili.com/video/BV1m84y1w7Tb/?spm_id_from333.999.0.0 一、Web开发 1. 概述 能通过浏览器访问的网站 2. Web网站的开发模式——主流是前后端分离 二、前端Web开发 1. 初识 前端编写的代码通过浏览器进行解析和渲染得到我们看到…

Java多线程常见的成员方法(线程优先级,守护线程,礼让/插入线程)

目录 1.多线程常见的成员方法2.优先级相关的方法3.守护线程&#xff08;备胎线程&#xff09;4.其他线程 1.多线程常见的成员方法 ①如果没有给线程设置名字&#xff0c;线程是有默认名字 的&#xff1a;Thread-X(X序号&#xff0c;从0开始) ②如果要给线程设置名字&#xff0c…

【SAM系列】Auto-Prompting SAM for Mobile Friendly 3D Medical Image Segmentation

论文链接&#xff1a;https://arxiv.org/pdf/2308.14936.pdf 核心&#xff1a; finetune SAM,为了不依赖外部prompt&#xff0c;通过将深层的特征经过一个编-解码器来得到prompt embedding&#xff1b;finetune完之后做蒸馏

苯酚,市场预计将以5%左右的复合年增长率

苯酚是一种重要的化合物&#xff0c;用于广泛的工业应用&#xff0c;包括塑料、树脂和合成纤维的生产。在建筑、汽车和电子行业不断增长的需求推动下&#xff0c;苯酚市场在过去十年中经历了稳步增长。全球苯酚市场分析&#xff1a; 在 2021-2026 年的预测期内&#xff0c;全球…

Java并发编程(三)

并发编程的三个特性 并发编程的三个重要特性是原子性、可见性和有序性。 原子性&#xff1a;原子性指的是一个操作是不可中断的&#xff0c;要么全部执行成功&#xff0c;要么全部不执行&#xff0c;是不可再分割的最小操作单位。保证原子性可以避免多个线程同时对共享数据进行…

《深入理解计算机系统》学习笔记 - 第七课 - 机器级别的程序三

Lecture 07 Machine Level Programming III Procedures 机器级别的程序三 文章目录 Lecture 07 Machine Level Programming III Procedures 机器级别的程序三概述程序机制 栈结构栈说明栈定义推入数据弹出数据 调用控制代码示例程序控制流程%rip 传递数据ABI 标准示例 管理局部…

WPF Button使用漂亮 控件模板ControlTemplate 按钮使用控制模板实例及源代码 设计一个具有圆角边框、鼠标悬停时颜色变化的按钮模板

续前两篇模板文章 模板介绍1 模板介绍2 WPF中的Button控件默认样式简洁&#xff0c;但可以通过设置模板来实现更丰富的视觉效果和交互体验。按钮模板主要包括背景、边框、内容&#xff08;通常为文本或图像&#xff09;等元素。通过自定义模板&#xff0c;我们可以改…

JVM篇:JVM的简介

JVM简介 JVM全称为Java Virtual Machine&#xff0c;翻译过来就是java虚拟机&#xff0c;Java程序&#xff08;Java二进制字节码&#xff09;的运行环境 JVM的优点&#xff1a; Java最大的一个优点是&#xff0c;一次编写&#xff0c;到处运行。之所以能够实现这个功能就是依…

Docker自建私人云盘系统

Docker自建私人云盘系统。 有个人云盘需求的人&#xff0c;主要需求有这几类&#xff1a; 文件同步、分享需要。 照片、视频同步需要&#xff0c;尤其是全家人都是用的同步。 影视观看需要&#xff08;分为家庭内部、家庭外部&#xff09; 搭建个人网站/博客 云端OFFICE需…

TiDB在WMS物流系统的应用与实践 (转)

业务背景 北京科捷物流有限公司于2003年在北京正式成立,是ISO质量管理体系认证企业、国家AAAAA级物流企业、海关AEO高级认证企业,注册资金1亿元,是中国领先的大数据科技公司——神州控股的全资子公司。科捷物流融合B2B和B2C的客户需求,基于遍布全国的物流网络与自主知识产…

电话号码的字母组合[中等]

一、题目 给定一个仅包含数字2-9的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意1不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出&am…

【MySQL学习笔记008】多表查询及案例实战

1、多表关系 概述&#xff1a;项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也存在着各种联系&#xff0c;基本上可分为三种&a…

什么是 NLP (自然语言处理)

NLP&#xff08;自然语言处理&#xff09;到底是做什么&#xff1f; NLP 的全称是 Natural Language Processing&#xff0c;翻译成中文称作&#xff1a;自然语言处理。它是计算机和人工智能的一个重要领域。顾名思义&#xff0c;该领域研究如何处理自然语言。 自然语言就是我…

小狐狸ChatGPT付费创作系统小程序端开发工具提示打开显示无法打开页面解决办法

最新版2.6.7版下载&#xff1a;https://download.csdn.net/download/mo3408/88656497 很多会员在上传小程序前端时经常出现首页无法打开的情况&#xff0c;错误提示无法打开该页面&#xff0c;不支持打开&#xff0c;这种问题其实就是权限问题&#xff0c;页面是通过调用web-v…

实习知识整理13:在购物车界面点击提交订单进入订单信息界面

在这块主要就是对前端传到后端的数据的处理&#xff0c;然后由后端再返还到新的前端界面 首先点击下单按钮后&#xff0c; 提交购物车中所选中的信息 因为前端是将name定义为 cartList[0].cartId &#xff0c;cartList[1].cartId 形式的 所以后端需要重新定义一个类来进行封装…

C语言中宏定义的一种妙用

1.前言 最近分析了一个宏定义的妙用方法&#xff0c;利用宏定义来构建一个枚举类型&#xff0c;通过自己代码测试验证&#xff0c;方法可行&#xff0c;分享给大家。 2.源码 实验源码如下所示&#xff1a; head1.h DEF_TEST(name1) DEF_TEST(name2) DEF_TEST(name3) #unde…

Redis哨兵sentinel

是什么&#xff1f; 哨兵巡查监控后台master主机是否故障&#xff0c;如果故障根据投票数自动将某一个slave库变为master&#xff0c;就行对外服务&#xff0c;称为无人值守运维 能干嘛&#xff1f; 主从监控&#xff1a;监控主从redis库是否正常工作 消息通知&#xff1a;…

带大家做一个,易上手的家常红烧茄子

我们先准备茄子 我这里用的一个大茄子 建议大茄子两个 一个做出来 还是看着有点少 茄子切成滚刀块 茄子倒入 小半勺盐 然后用手抓拌均匀 腌制十分钟 准备一根半小葱 三瓣蒜 蒜切成 蒜末 葱切碎 调一个料汁 两勺生抽 半勺老抽 半勺白砂糖 半勺盐 倒一点蚝油 半勺淀粉 小半…