JavaScript不仅有变量声明,还有变量提升

news2025/1/23 10:23:41

起因:👇 一道面试题

最近,一位朋友参加面试时,遇到了这样一道笔试题,引起了我的兴趣:

var foo = 1;
function fn() {
    foo = 3;
    return;
    function foo() {
        // ...
    }
}
fn();
console.log(foo);

这个例子中包含了变量提升,还涉及到了函数声明与变量声明的提升差异。接下来,通过这个例子,我们探讨下JavaScript中的变量提升机制。

变量提升机制解析 🎉

JavaScript代码执行的两阶段

在了解变量提升之前,我们首先需要简单了解JavaScript代码执行的两个阶段:编译阶段和执行阶段。

  • 编译阶段,JavaScript引擎会对代码进行遍历,识别出所有的变量和函数声明,并将它们提升至它们所在作用域的顶部。
  • 紧接着,在执行阶段,代码会按照编写的逻辑顺序从上至下执行。
// 编译阶段
var a = 2; // 声明变量a并分配内存空间
function foo(b) { // 声明函数foo并分配内存空间
  return b * 2;
}
 
// 执行阶段
console.log(foo(a)); // 输出: 4

变量提升的细节

变量提升发生在JavaScript的编译阶段,具体细节我们接着往下看…

变量提升 ✨✨✨

变量提升是指var声明的变量会被提升到其作用域的最顶端。然而,值得注意的是,虽然声明被提升,但赋值操作不会提升。

这意味着,即便是变量在代码中后面被声明,其在编译阶段已经被确认,但直到执行到赋值操作时,这个变量才会被赋予实际的值。

重要提示:变量提升仅针对声明操作,而非赋值操作。

console.log(a); // undefined
var a = 10;
console.log(a); // 10

尽管变量a是在console.log(a)之后被声明的,但由于变量提升的效果,它已经在当前作用域的顶部“存在”了。因此,第一次调用console.log(a)时,输出的是undefined(因为此时尚未赋值),而非抛出引用错误。

函数声明提升 ✨✨✨

与变量提升类似,函数声明(使用function关键字的那种)也会被提升至它们所在作用域的顶端,不过不同的是,函数的提升包括函数名和函数体。

这意味着,在函数声明之前就可以调用该函数,因为在代码执行之前,JavaScript引擎已经知晓了函数的存在。

console.log(foo()); // "foo"
function foo() {
  return "foo";
}
console.log(foo()); // "foo"

函数foo被提升到全局作用域的顶部,因此在函数声明之前调用foo也能够正常获取到函数定义,而不会抛出引用错误。

变量提升与函数声明提升的区别

尽管变量提升和函数声明提升听起来类似,但它们之间存在着本质的区别:

  • 变量提升仅提升变量的声明,而不提升赋值操作。
  • 函数声明提升则将函数的整个声明(包括函数体)都提升到作用域顶部。

下表简要比较了两者的区别:

特征变量提升 (var)函数声明提升 (function)
提升内容仅变量名函数名及函数体
初始化值undefined函数定义
赋值提升
作用域作用域内部作用域内部
常见问题可能导致逻辑混乱较少导致混乱

面试题解析:😤 变量与函数声明的提升冲突

现在,让我们回到文章开头提到的面试题:

var foo = 1;
function fn() {
    foo = 3;
    return;
    function foo() {
        // todo
    }
}
fn();
console.log(foo); // 输出:1

在面试题中,fn 函数内部有一个函数声明 function foo() {},这个声明会被提升到 fn 函数作用域的顶部。

同时,foo = 3 这行代码是一个变量赋值操作,它会找到 fn 函数作用域内的 foo 变量,并尝试给它赋值。但是由于函数声明 function foo() {} 已经提升了,它成为了 fn 函数作用域内的 foo 变量,所以 foo = 3 实际上是在尝试给这个函数赋值,而不是修改全局变量 foo

然而,由于 return 语句紧随 foo = 3,这意味着 foo = 3 这行代码实际上从未被执行。return 语句会导致 fn 函数立即结束,任何在 return 之后的代码都不会执行。因此,foo = 3 这行代码被忽略了,函数内部的 foo 函数也没有被赋值。

foo = 3 这行代码并不会影响全局变量 foo 的值,因为它在 return 语句之后。因此,fn 函数执行后,全局变量 foo 的值仍然是 1。

ES6中的变量提升

let 和 const 关键字

ES6引入的letconst关键字为变量提升带来了新的规则。它们虽然也会被提升,但不会被初始化为undefined,而是处于“暂时性死区”(TDZ)直到实际的声明语句执行。这意味着,在声明之前尝试访问这些变量会抛出引用错误,从而避免了var带来的问题。

console.log(b); // 引用错误:b is not defined
let b = 10;
console.log(b); // 输出:10

大家看一下,这里使用了 let,结果是不是和 var 不一样了!

这里是一个表格,详细对比了 varletconst 在不同特性方面的区别:

特性varletconst
作用域函数作用域(function scope)块级作用域(block scope)块级作用域(block scope)
提升行为变量声明被提升,初始化不提升提升,但存在暂时性死区提升,但存在暂时性死区
重声明同一作用域内可重声明同一作用域内不能重声明同一作用域内不能重声明
可变性可重新赋值可重新赋值声明后不能重新赋值
使用建议避免使用需要变量可变时使用值不变时使用,推荐用于常量

说明:

  • var 的函数作用域意味着它在整个函数中都可见,甚至在声明前。
  • letconst 的块级作用域使它们只在定义它们的代码块(如:循环、条件语句等)内可见。

箭头函数 和 class

在 ES6 中,除了传统的函数声明,还引入了箭头函数和 class 语法。这些新的函数和类声明也遵循提升规则,但是与 ES5 中的函数声明有一些不同。

箭头函数是表达式,它们不会被提升到作用域的顶部。如果你尝试在声明之前调用箭头函数,你会得到一个引用错误。

console.log(arrowFn()); // ReferenceError: arrowFn is not defined
const arrowFn = () => console.log('Hello from arrow function');

arrowFn 作为一个常量声明(使用 const),它不会被提升。因此,尝试在声明之前调用它会导致引用错误。

Class 声明也不会被提升。class 是一种新的语法,用于创建构造函数和原型继承的语法糖。与箭头函数一样,如果你尝试在声明之前访问 class,你会得到一个引用错误。

let peter = new Person(); // ReferenceError: Cannot access 'Person' before initialization
class Person {
  constructor(name) {
    this.name = name;
  }
}

总结,在 ES6 中,函数声明提升的规则有所改变:

  • 传统函数声明:仍然会被提升到作用域的顶部,包括函数名和函数体。
  • 箭头函数:不会被提升,因为它们是表达式形式的函数。
  • Class 声明:不会被提升,因为 class 是一种新的语法,用于创建构造函数和原型继承的语法糖。

规避变量提升陷阱的策略 🔖🔖🔖

那么,了解了变量提升的机制和潜在问题后,我们可以采取以下措施规避陷阱:

1. 优先使用letconst

它们解决了 var 的变量提升问题,可以避免var带来的变量提升问题。

// 使用 var
for (var i = 0; i < 3; i++) {
    var bar = i;
    setTimeout(function() {
        console.log(bar); // 2, 2, 2
    }, 1000);
}
// 使用 let 和 const
for (let i = 0; i < 3; i++) {
    let bar = i;
    setTimeout(function() {
        console.log(bar); // 0, 1, 2
    }, 1000);
}

2. 将函数声明放在逻辑的顶部

如果你在函数内部使用函数声明,确保将这些声明放在函数体的顶部,这样就不会因为变量提升而导致意外的行为。

function example() {
    function foo() {
        // 函数声明被提升到 example 函数的顶部
    }
    // 其他逻辑
}

foo 函数声明被放在 example 函数体的顶部,这样可以避免因为函数声明提升而导致的混淆。

3. 使用立即执行函数表达式(IIFE)

立即执行函数表达式可以创建独立作用域,隔离变量。例如:

(function() {
    var foo = 'Hello World';
    // foo 在这个函数作用域内是局部的,不会影响到外部作用域
})();
// foo 在这里是不可访问的

IIFE 创建了一个新的作用域,foo 在这个作用域内是局部的,不会影响到外部作用域。

4. 使用函数表达式而不是函数声明

函数表达式不会被提升,因此你可以控制函数的创建和执行时机。例如:

const myFunction = function() {
    // 函数表达式不会被提升
};

myFunction 是一个函数表达式,它不会被提升到顶部,因此你可以控制它的创建和执行时机。

总结 🎉🎉🎉

在开发过程中,我们通常按照从上到下的顺序编写代码逻辑,而不去刻意考虑变量提升和函数声明提升。为了避免提升带来的潜在问题,我们可以考虑以下最佳措施:

  1. 优先使用letconst来声明变量。这样可以避免变量提升导致的意外行为,因为 letconst 声明的变量在赋值之前是不可访问的。
  2. 在需要的时候才声明函数和类。避免在作用域顶部之外的地方引用尚未声明的函数或类。

这样,我们可以在编写代码时最大程度地保持逻辑的清晰和正确性,减少由变量提升和函数声明提升引起的错误。

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

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

相关文章

HQChart使用教程98-右键菜单2.0使用介绍

HQChart使用教程98-右键菜单2.0使用介绍 内置右键菜单启用右键菜单定制右键菜单内容1. 注册内置右键菜单创建回调事件2. 修改内置菜单的显示内容回调函数格式菜单数据结构示例 3. 注册菜单项点击事件回调 右键事件完整示例HQChart代码地址 内置右键菜单 HQChart h5版本内置提供…

Python-VBA函数之旅-tuple函数

目录 一、tuple函数的常见应用场景 二、tuple函数使用注意事项 三、如何用好tuple函数&#xff1f; 1、tuple函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://myelsa1024.blog.csdn.net/ 一、tu…

U盘打不开提示格式化怎么办?(含数据恢复及U盘修复教程)

引言&#xff1a; 随着数字化时代的发展&#xff0c;U盘已成为我们日常生活和工作中不可或缺的数据存储工具。然而&#xff0c;有时我们可能会遇到U盘突然无法打开&#xff0c;并提示需要格式化的问题。这不仅会打乱我们的工作节奏&#xff0c;还可能会导致重要数据丢失。本文…

环保访谈|聚英环保:以创新科技引领工业环保

近期&#xff0c;中联环保圈希姐对浙江聚英环保科技有限公司负责人王江进行了专访&#xff0c;就公司的发展、核心产品以及合作客户等方面进行了深入交流。 浙江聚英环保科技有限公司成立于2012年&#xff0c;总占地面积超过3万平方米&#xff0c;拥有标准化的生产车间和先进的…

KNIME 报告扩展

文档对应的 KNIME AP 版本为 5.2 介绍 本指南介绍了 KNIME 报告扩展&#xff0c;并展示了如何创建简单和高级报告。 本指南更新于 2024/05/13&#xff0c;最新版请访问指北君网站 https://havef.fun/knime-cn/knime-doc/ KNIME 报告扩展允许您根据工作流程的结果创建静态报告。…

win10安装mysql8.0+汉化

一、官网安装 MySQL 1. 在mysql官网进行下载页面 2. 下滑页面&#xff0c;选择 MySQL community download 3.下载windows版本 4.选择第二个download 5.不用登陆&#xff0c;no thanks&#xff0c;just start my download. 6.下载 二、安装 1. 双击安装 2. 选 Full->next 3…

react Effect副作用 - 避免滥用Effect

react Effect副作用 - 避免滥用Effect react Effect副作用基础概率什么是纯函数? 什么是副作用函数?纯函数副作用函数 什么时候使用Effect如何使用Effect 避免滥用Effect根据 props 或 state 来更新 state当 props 变化时重置所有 state将数据传递给父组件获取异步数据 react…

​民兵档案管理系统-退伍军人档案管理全流程追踪

民兵档案管理系统-退伍军人档案管理全流程追踪 民兵档案管理系统&#xff08;智档案DW-S403&#xff09;是依托互3D技术、云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对RFID智能仓库进行统一管理、分析的信息化、智能化、规范化的系统。 RFID档案管理系统是以先进…

代码随想录——N叉树的层序遍历(Leetcode429)

题目链接 层序遍历 /* // Definition for a Node. class Node {public int val;public List<Node> children;public Node() {}public Node(int _val) {val _val;}public Node(int _val, List<Node> _children) {val _val;children _children;} }; */class Sol…

硬件FMEA与软件FMEA的区别——FMEA软件

​免费试用FMEA软件-免费版-SunFMEA 在产品开发和制造过程中&#xff0c;失效模式与影响分析&#xff08;FMEA&#xff09;作为一种预防性的质量工具&#xff0c;对于确保产品性能和质量至关重要。然而&#xff0c;硬件FMEA和软件FMEA在应用和实践方面存在显著的区别。本文旨在…

Excel 根据包含的关键词将指定列按关键词指定顺序排列

例题描述和简单分析 有 Excel 文件&#xff0c;数据如下所示&#xff1a; AB1Parent ColumnModifier (Column)2Jack lives in the villageRose3As mentioned by jackVillage4Rose already spoke to jack about last nightJack5Rose left the village6rose was their yesterda…

uboot 顶层 Makefile 逐行分析

文章目录 0001-00080009-00180019-00510052-00920093-01070108-01230124-01770178-23150178-01810182-01860187-02020203-02450246-02620263-02720273-03370338-03830384-03870388-04250426-04490450-04740475-04860487-04980499-05340535-05500551-05650566-221822192220-2332…

vue3延迟加载(异步组件​)defineAsyncComponent

最简单用法 Index.vue: <script setup> import { onMounted, defineAsyncComponent } from vue import ./index.cssconst Child defineAsyncComponent(() > import(./Child.vue))onMounted(() > {}) </script><template><div class"m-home-w…

HTML静态网页成品作业(HTML+CSS)——动漫哆啦A梦网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有3个页面。 二、作品演示 三、代…

等保2.0的全面解读与实施策略

《网络安全等级保护基本要求》&#xff08;等保2.0&#xff09;是中华人民共和国国家安全部于2019年6月发布的网络安全等级保护标准。该标准规定了我国关键信息基础设施的网络安全等级保护要求和评估标准&#xff0c;对于保障我国网络安全具有重要的意义。下面是对等保2.0的全面…

功能安全如何在公司顺利开展?-亚远景科技

亚远景功能安全主题线上会议报名开启&#xff01; 随着汽车技术的不断发展&#xff0c;汽车系统的复杂性和交互性大幅增加&#xff0c;功能安全成为确保驾驶员、乘客及行人安全的关键。 本场功能安全线上会议&#xff0c;亚远景为汽车行业的相关人员准备了以下内容&#xff1a…

保姆级AI绘画入门教程,从此找图不求人、风格还统一(入门篇)

安装流程 安装包使用的是b站大佬“秋葉aaaki”制作的整合程序。他还会讲授一些lora训练的方法&#xff08;对于大多数人来说可能一生都用不上的功能&#xff0c;跟着月仔学学基础操作就成了&#xff0c;哈哈。&#xff09; 首先要下载安装包&#xff0c;最新的安装包大小已经…

vant showNotify样式修改

main.ts直接引入根css会影响已经修改好的其他vant样式,直接添加一个新的my-vant.scss文件引入 //vant showNotify样式修改 .van-popup.van-popup--top.van-notify {width: 343px !important;height: 40px !important;border-radius: 12px !important;margin: 0 auto !importan…

黑马点评项目总结及个人优化

怎么根据前端代码实现自己的后端业务,实现不同接口 查阅文档:如果有完善的接口文档,可以直接查阅文档来了解后端所有接口的业务逻辑和功能。 阅读后端代码:通过阅读后端代码,特别是控制器(Controller)层和服务(Service)层的代码,可以了解后端所有接口的具体实现逻辑。…

FPGA verilog LVDS通信协议笔记

一幅图胜过千言万语 直接开始挫代码&#xff0c;先写top.v。 module top();reg clk; // 生成时钟的寄存器 reg rst; // 生成复位信号的寄存器initial clk 1; // 初始值取1 always #1 clk ~clk; //1ns取反一次initial begin // 复位信号&#xff0c;先0&#xff0c;过段时间赋…