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() // Hellovar sayHi = function() {console.log('Hi')
}// sayHi() // Hifunction 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作用域:全局作用域、函数作用域、块级作用域

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

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

相关文章

深度学习——PIL和OpenCV

PIL 官方文档 格式互转 opencv cv2.imread() 参数&#xff1a; filepath&#xff1a;读入imge的完整路径 flags&#xff1a;标志位&#xff0c;{cv2.IMREAD_COLOR&#xff0c;cv2.IMREAD_GRAYSCALE&#xff0c;cv2.IMREAD_UNCHANGED} cv2.IMREAD_COLOR&#xff1a;默认参数&…

Attention机制

前置知识&#xff1a;RNN&#xff0c;LSTM/GRU 提出背景 Attention模型是基于Encoder-Decoder框架提出的。Encoder-Decoder框架&#xff0c;也就是编码-解码框架&#xff0c;主要被用来处理序列-序列问题。 Encoder&#xff1a;编码器&#xff0c;将输入的序列<x1,x2,x3……

『番外篇十』SwiftUI 实战:打造一款“五脏俱全”的网络图片显示 App(下)

概览 在上篇文章中,我们初步实现了一款小巧的网络图片显示器。 我们先是创建了 json 数据对应的图片模型,然后将 App 界面“分而治之”划分为独立的三个组件以便“逐个击破”,最后我们将所有这些融合在一起。 不过,目前的实现仍有一些问题。比如我们添加了一层不必要的 …

详解Vue3中的鼠标事件mousemove、mouseover和mouseout

本文主要介绍Vue3中的常见鼠标事件mousemove、mouseover和mouseout。 目录 一、mousemove——鼠标移动事件二、mouseover——鼠标移入事件三、mouseout——鼠标移出事件 下面是Vue 3中常用的鼠标事件mousemove、mouseover和mouseout的详解。 一、mousemove——鼠标移动事件 鼠…

跟着cherno手搓游戏引擎【3】事件系统和预编译头文件

不多说了直接上代码&#xff0c;课程中的架构讲的比较宽泛&#xff0c;而且有些方法写完之后并未测试。所以先把代码写完。理解其原理&#xff0c;未来使用时候会再此完善此博客。 文件架构&#xff1a; Event.h:核心基类 #pragma once #include"../Core.h" #inclu…

JMeter使用

目录 启动JMeter 创建线程组 设置线程参数 设置http请求参数 ​编辑 创建查看结果树(显示成功/失败多少以及返回结果等信息) 创建聚合报告(显示响应时间、吞吐量、异常数等信息) 点击上方的执行按钮即可开始压力测试 结果树显示 聚合报告结果显示 启动JMeter 在JMete…

CSS-4

平面转换 整体认识 div {margin: 100px 0;width: 100px;height: 100px;background-color: pink;/* 过渡效果 */transition: all 1s;}/* 当鼠标悬停到div时&#xff0c;进行平面转换 */div:hover {transform: translate(800px) rotate(360deg) scale(2) skew(180deg);}作用&…

系统学习Python——装饰器:函数装饰器-[对方法进行装饰:使用嵌套函数装饰方法]

分类目录&#xff1a;《系统学习Python》总目录 如果想要函数装饰器在简单函数和类级别的方法上都能工作&#xff0c;最直接的解决办法在于使用前面文章介绍的状态保持方案之一&#xff1a;把自己的函数装饰器编写为嵌套的def&#xff0c;这样你就不会陷入单一的self实例参数既…

【unity学习笔记】捏人+眨眼效果+口型效果

一、vriod捏人 1.在vroidstudio软件中捏人 2.导出模型&#xff08;.vrm) 二、vrid导入unity的插件 1.在Git上搜索、打开univrm。 2.找到release页面找到合适的插件版本。&#xff08;VRM-0.116.0_0f6c&#xff09; 3.将univrm导入到工程中&#xff08;assets&#xff09;。 三…

前端显示json格式化

实现效果 在前端页面上展示格式化的JSON数据可以通过以下步骤完成&#xff1a; 获取JSON数据&#xff1a;首先&#xff0c;你需要获取要展示的JSON数据。你可以从后端API获取数据&#xff0c;或者直接在前端定义一个JSON对象。 格式化JSON&#xff1a;使用JavaScript的JSON对…

微服务雪崩问题及解决方案

雪崩问题 微服务中&#xff0c;服务间调用关系错综复杂&#xff0c;一个微服务往往依赖于多个其它微服务。 微服务之间相互调用&#xff0c;因为调用链中的一个服务故障&#xff0c;引起整个链路都无法访问的情况。 如果服务提供者A发生了故障&#xff0c;当前的应用的部分业务…

C++初阶------------------入门C++

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

【LMM 007】Video-LLaVA:通过投影前对齐以学习联合视觉表征的视频多模态大模型

论文标题&#xff1a;Video-LLaVA: Learning United Visual Representation by Alignment Before Projection 论文作者&#xff1a;Bin Lin, Yang Ye, Bin Zhu, Jiaxi Cui, Munan Ning, Peng Jin, Li Yuan 作者单位&#xff1a;Peking University, Peng Cheng Laboratory, Sun …

TCP中的三次握手和四次挥手

TCP中的连接和断开可以说是在面试中经常被问到的问题之一&#xff0c;正好有空就总结一下&#xff0c;首先回顾一下TCP的相关知识点 1. TCP的基础知识 1.1 TCP的基本概念 我们知道TCP是运输层的面向连接的可靠的传输协议。面向连接的&#xff0c;指的就是在两个进程发送数据…

简单FTP客户端软件开发——JavaFX开发FTP客户端

文章目录 导入外部包commons-net-3.10.0.jarJavaFX开发客户端 FTP客户端要求如下&#xff1a; 简单FTP客户端软件开发 网络环境中的一项基本应用就是将文件从一台计算机中复制到另一台可能相距很远的计算机中。而文件传送协议FTP是因特网上使用得最广泛的文件传送协议。FTP使用…

手机摄影学习

手机摄影学习 基础知识1&#xff0c;成像基本原理2&#xff0c;什么是焦距3&#xff0c;快门&#xff08;简称s&#xff09;4&#xff0c;上面功能之间的相互影响5&#xff0c;焦点6&#xff0c;过爆、欠曝7&#xff0c;cmos&#xff08;感光芯片&#xff09;、测光、聚焦&…

2023年“中银杯”安徽省网络安全B模块(部分解析)

前言 以下是2023年中银杯安徽省网络安全B模块题目&#xff0c;镜像可以私聊我 B模块安全事件响应/网络安全数据取证/应用安全&#xff08;400 分&#xff09; B-1&#xff1a;CMS网站渗透测试 任务环境说明&#xff1a; √服务器场景&#xff1a;Server2206&#xff08;关…

java SSM拖拉机售后管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM拖拉机售后管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源 代码和数据库&#xff0c;系统主要…

raid解析

raid原理是把各个硬盘组成一个组&#xff0c;是磁盘的集合&#xff0c;按照排列组合的方法给raid去不同的名字。 raid 0磁盘 读写性能都提高 在什么情况下使用1快盘raid 0 &#xff1f; 实际利用率 100% 系统中要不然都做raid 要不然都不做raid 当前6快盘都是raid 但是…

es简单入门

星光下的赶路人star的个人主页 努力努力再努力 文章目录 1、简介2、使用场景3、基本知识4、中文文档和官网链接5、增删改查&#xff08;php代码&#xff09;6、基本查询7、HTTP操作7.1 索引操作7.1.1 创建索引 7.2 文档操作7.2.1 创建文档7.2.2 查看文档7.2.3 修改文档7.2.4 修…