JS精进之Hoisting(提升)

前言

组会分享,初略的讲讲JS的Hoisting机制

Hoisting提升🏗️前置知识

啥是提升?

简单的说 Hoisting 是JS引擎在代码执行前将所有声明提升到其所在作用域的顶部
这句话你可以在看完这篇文章后再回头看看这句话,真的是一言蔽之。
接下来我会简单讲解一下。

所有声明

JS中拢共有

  • 变常量var let const
  • 函数function
  • class
  • 模块import export
    是的这些都会提升,但是JS引擎在处理这些声明时会有不同的细节处理。

所在作用域

JS中拢共 global function block三个常规的作用域,这不会区别自己学去。
[[JS作用域]]
还有模块作用域 (Module Scope)、词法作用域 (Lexical Scope)、动态作用域 (运行时上下文)、私有作用域 (Private Scope),嘿🫠我也不会,下次学到的时候补上😁😁😁,初略的看了眼,除了module外和hoisting没多大关系(应该吧)
记住Hoisting提升只会提升到其所在作用域的顶部,不会发生将function scope内的提升到global scope的事情。

详细讲解

var 的提升

var 声明的变量会被提升到作用域顶部,但仅提升声明部分,初始化(赋值)不会提升
提一句var会全局污染,即会绑定到window上,不了解的可以自己去查资料看var、let、const的区别.
例子:

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

底层执行过程:

  1. JavaScript 引擎将 var a 提升到作用域顶部,等同于:
    var a;
    console.log(a); // undefined
    a = 5;
    console.log(a); // 5
    
  2. 因此,在变量赋值之前,a 的值是 undefined

[[let 和 const 的提升]]

letconst 也会被提升,但它们的变量在提升时会被放入一个称为 暂时性死区(Temporal Dead Zone, TDZ) 的区域,只有在声明之后才能访问。

例子:

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;

底层执行过程:

  1. let b 被提升到作用域顶部,但在声明之前无法访问。
  2. 由于在 let b 之前尝试访问变量 b,引擎抛出 ReferenceError
啥是[[暂时性死区(Temporal Dead Zone, TDZ)]]

暂时性死区是 JavaScript 中一种行为:
在作用域内,虽然变量已经“被提升”(即解析时已经被声明),但在实际声明和初始化完成之前,访问该变量会抛出 ReferenceError 错误。
这是为了避免变量在声明之前被意外访问或使用,增加代码的可预测性和安全性。
有死区和没死区粗暴的看就是,一个爆ReferenceError,一个爆undefined


函数的 Hoisting

函数又和前面的机制不同,函数声明的提升会将整个函数提升🫡🫡🫡,而函数表达式则只是会将变量提升,不提升函数。

1. 函数声明的提升

⭐函数声明会被完整地提升到作用域顶部,因此在声明之前也可以调用函数。
例子:

greet(); // Hello!
function greet() {console.log("Hello!");
}

底层执行过程:

  1. 函数声明 function greet() { ... }完整提升到作用域顶部,等同于:
    function greet() {console.log("Hello!");
    }
    greet(); // Hello!
    

2. 函数表达式的提升

函数表达式(包括箭头函数)不会提升其赋值部分,仅变量名会被提升
例子:

console.log(foo); // undefined
var foo = function () {console.log("Hello!");
};
foo(); // Hello!

底层执行过程:

  1. 只有 var foo 被提升,赋值部分不会提升,等同于:
    var foo;
    console.log(foo); // undefined
    foo = function () {console.log("Hello!");
    };
    foo(); // Hello!
    

使用 letconst 声明函数表达式时,会触发暂时性死区:

console.log(bar); // ReferenceError
const bar = () => {console.log("Hi!");
};

class (类声明)

类声明也会被提升,但同样会存在暂时性死区,在声明之前无法访问。
示例

const instance = new MyClass(); // ReferenceError报错,MyClass 在 TDZ 中
class MyClass {constructor(name) {this.name = name;}
}

import (模块声明)

import 总是在模块顶部执行,不能在代码块中使用。
示例

import { myFunc } from './module.js';
myFunc();

export (模块声明)

特点

  • 用于导出变量、函数、类等,使其可以在其他模块中使用。
  • 支持命名导出(export {})和默认导出(export default)。
    示例
export const myVar = 42;
export default function myFunc() {console.log("Hello!");
}

对比总结:

特性varletconstfunctionclassimport/export
作用域三作用域三作用域三作用域函数作用域块作用域模块作用域
变量提升是(值为 undefined是(TDZ限制)是(TDZ限制)是(TDZ限制)
重复声明可以不可以不适用不适用不适用
可重新赋值不适用不适用

[[Hoisting提升的先后顺序]]

看到这不知道你有没有疑惑,所有声明都会发生提升,那究竟谁比谁更能提升🫡🫡🫡,总不能左脚🦶踩右脚🦶升天不是。
在 JavaScript 中,不同类型的声明(如 varletconstfunctionclassimport/export)它们的执行优先级和提升行为有所不同。以下是提升的先后顺序及具体规则:

提升顺序的原则
  1. 模块系统优先importexport 的静态绑定首先被处理。
  2. 函数声明优先:在代码执行前,函数声明会被提升到所在作用域的顶部。
  3. 变量声明依次提升var 早于 letconst,但值初始化按代码顺序进行。
  4. class 的提升特殊:类声明会被提升,但无法在声明前使用(存在 TDZ)。
提升优先级
类型提升顺序初始化可用性是否受 TDZ 限制特性解释
import最高在代码执行前绑定模块加载的静态绑定,解析阶段完成,但无法访问
function第二在声明前可调用函数声明整体提升,初始化为可调用函数
var第三在声明前可用(undefined声明提升但初始化在原始位置
letconst第四声明前不可用存在 TDZ,初始化需等到执行到声明语句才有效
class最后声明前不可用类声明会提升,但使用前需显式定义
  • 最高优先级import/export 静态绑定最先解析。
  • 中间优先级function 声明高于 var
  • 最低优先级letconstclass,它们都受 TDZ 限制。
综合示例:优先级的对比

以下代码展示了多种声明的提升顺序和执行规则:

console.log(func()); // "I am a function!"
console.log(x);      // undefined
// console.log(y);   // 报错:Cannot access 'y' before initialization
// console.log(z);   // 报错:Cannot access 'z' before initialization
// console.log(MyClass); // 报错:Cannot access 'MyClass' before initializationfunction func() {return "I am a function!";
}var x = 10;
let y = 20;
const z = 30;class MyClass {constructor() {this.name = "Class Example";}
}

执行顺序

  1. 函数 func 提升并初始化。
  2. var x 声明提升但值为 undefined
  3. let yconst z 提升但进入 TDZ,未初始化。
  4. class MyClass 提升但进入 TDZ,未初始化。

为什么要提升?

这也是我当初了解到提升这一逆天特性时的最大疑惑,吃饱了撑着么要这么干?
我自己也解释不清,欧克,下面让我们看看GPT是怎么说的:

提升(Hoisting)是 JavaScript 的设计特性之一,它从一开始就融入了语言的实现中。设计提升的原因和历史背景与 JavaScript 的初衷、执行模型、开发环境的需求,以及语言设计的权衡密切相关。

  1. 简化解析和作用域解析,提升性能。
  2. 支持灵活的代码结构,适合快速开发和调试。
  3. 向下兼容,保留早期代码的行为一致性。
  4. 模仿 C 语言的声明模型,降低学习成本。
  5. 宽容开发者错误,降低入门门槛。

我们来挑重点看,主要就是俩点,一是能提高编译器效率,二是设计者就是想要易用🫡(毕竟是10天就完成了第一个版本,原则上就是降低入门成本,减少语法报错的可能性,不管结果死活了,能跑就是赢)

那么为啥Hoisting能提高编译效率呢?

在 JavaScript 执行的早期阶段,解析器会对整个代码进行作用域解析。将变量和函数的声明“提升”到作用域的顶部,可以使解析器在代码运行之前快速定位所有变量和函数,从而优化代码执行过程。如果没有提升,解析器必须动态检查代码块中是否存在变量,这会显著增加解析器的复杂性和性能开销。

那么为什么不把这逆天机制改了呢?

还是有请GPT:

1. 向下兼容性
JavaScript 已经被广泛应用,去掉提升会破坏大量现有代码的运行。因此,ECMAScript 保留了提升特性,同时通过引入 letconst 来改进。
2. 保留历史遗留行为
JavaScript 的早期设计中考虑了宽松的语法规则以适应快速开发环境,提升就是这些规则的遗留产物。删除提升会导致历史代码中的行为发生改变,难以维护。
3. 提升的替代方案更复杂
没有提升的语言通常需要更严格的声明规则(如 Python),这与 JavaScript 的宽松设计理念相悖。虽然提升容易被滥用,但它为开发提供了一种低门槛的动态方式。

行,我起码是信了,但我觉得历史原因是更多的。Git也是如此,即使现在SHA-1已经不安全了,但还是因为历史原因未用SHA-256去替换。

最佳实践

如何避免提升的陷阱?

  1. 使用 letconst
    它们也会被提升,但由于存在 暂时性死区(TDZ),未初始化前访问会抛出 ReferenceError,从而避免了意外行为。
    console.log(x); // ReferenceError
    let x = 10;
    
  2. 严格模式
    使用 strict mode,避免变量未声明时被使用:
    "use strict";
    console.log(a); // ReferenceError
    a = 5;
    
  3. 遵循“先声明,后使用”规则
    避免依赖提升,让代码更加直观。

后记

呼,结束了,但感觉有开了个坑,作用域解析机制[[JS作用域]] 或许有的一讲🫡,下次在说吧。

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

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

相关文章

golang实现TCP服务器与客户端的断线自动重连功能

1.服务端 2.客户端 生成服务端口程序: 生成客户端程序: 测试断线重连: 初始连接成功

【Spring Boot】# 使用@Scheduled注解无法执行定时任务

1. 前言 在 Spring Boot中,使用Scheduled注解来定义定时任务时,定时任务不执行;或未在规定时间执行。 import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;Component public c…

java 老矣,尚能饭否?

随笔 从千万粉丝“何同学”抄袭开源项目说起,为何纯技术死路一条? 数据源的统一与拆分 监控报警系统的指标、规则与执行闭环 java 老矣,尚能饭否? 一骑红尘妃子笑,无人知是荔枝来! java 老吗? 去年看…

[译]Elasticsearch Sequence ID实现思路及用途

原文地址:https://www.elastic.co/blog/elasticsearch-sequence-ids-6-0 如果 几年前,在Elastic,我们问自己一个"如果"问题,我们知道这将带来有趣的见解: "如果我们在Elasticsearch中对索引操作进行全面排序会怎样…

解锁PPTist的全新体验:Windows系统环境下本地部署与远程访问

文章目录 前言1. 本地安装PPTist2. PPTist 使用介绍3. 安装Cpolar内网穿透4. 配置公网地址5. 配置固定公网地址 前言 在Windows系统环境中,如何本地部署开源在线演示文稿应用PPTist,并实现远程访问?本文将为您提供详细的部署和配置指南。 P…

一文学会Golang里拼接字符串的6种方式(性能对比)

g o l a n g golang golang的 s t r i n g string string类型是不可修改的,对于拼接字符串来说,本质上还是创建一个新的对象将数据放进去。主要有以下几种拼接方式 拼接方式介绍 1.使用 s t r i n g string string自带的运算符 ans ans s2. 使用…

IEC61850读服务器目录命令——GetServerDirectory介绍

IEC61850标准中的GetServerDirectory命令是变电站自动化系统中非常重要的一个功能,它主要用于读取服务器的目录信息,特别是服务器的逻辑设备节点(LDevice)信息。以下是对GetServerDirectory命令的详细介绍。 目录 一、命令功能 …

Flink学习连载第二篇-使用flink编写WordCount(多种情况演示)

使用Flink编写代码,步骤非常固定,大概分为以下几步,只要牢牢抓住步骤,基本轻松拿下: 1. env-准备环境 2. source-加载数据 3. transformation-数据处理转换 4. sink-数据输出 5. execute-执行 DataStream API开发 //n…

数据集-目标检测系列- 花卉 玫瑰 检测数据集 rose >> DataBall

数据集-目标检测系列- 花卉 玫瑰 检测数据集 rose >> DataBall DataBall 助力快速掌握数据集的信息和使用方式,会员享有 百种数据集,持续增加中。 贵在坚持! 数据样例项目地址: * 相关项目 1)数据集可视化项…

Windows系统运行库软件游戏修复工具

本页面下载的资源包包括PC电脑常用的运行库和电脑必备组件,如您的电脑出现应用打不开,缺少dll链接库、闪退等现象可以尝试用下面软件修复。 本资源永久有效。 软件安装基本常识科普: 为什么要安装运行库?运行库默认安装到C盘&…

wireshark使用lua解析自定义协议

wireshark解析自定义协议 1.自定义的lua放入路径2.修改init.lua2.1 开启lua2.2 init.lua文件最后加入自己的lua文件位置,这里需要确保与自己的文件名相同 3.编写lua4.编写c抓包5.wireshark添加自定义协议如何加调试信息 1.自定义的lua放入路径 一般是自己软件的安装…

ISAAC Gym 7. 使用箭头进行数据可视化

在这里发布一个ISAAC GYM可以使用的箭头绘制类。 gymutil默认有WireframeBoxGeometry,WireframeBBoxGeometry, WireframeSphereGeometry三个线段集生成函数,可以绘制盒子和球体。绘制函数分别有draw_lines和draw_line。 同理,使…

【计算机网络】网段划分

一、为什么有网段划分 IP地址 网络号(目标网络) 主机号(目标主机) 网络号: 保证相互连接的两个网段具有不同的标识 主机号: 同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号 互联网中的每一台主机,都要隶属于某一个子网 -&…

机器学习周志华学习笔记-第5章<神经网络>

机器学习周志华学习笔记-第5章<神经网络> 卷王&#xff0c;请看目录 5模型的评估与选择5.1 神经元模型5.2 感知机与多层网络5.3 BP(误逆差)神经网络算法 5.4常见的神经网络5.4.1 RBF网络&#xff08;Radial Basis Function Network&#xff0c;径向基函数网络&#xff0…

MySQL数据库设计

数据库设计 数据库是用来存在数据的&#xff0c;需要设计合理的数据表来存放数据–能够完成数据的存储&#xff0c;同时能够方便的提取应该系统所需的数据 1. 数据库的设计流程 数据库是为应用系统服务的&#xff0c;数据库的数据存储也是由应用系统决定的 当我们进行应用系统开…

Spring Boot 3.x + OAuth 2.0:构建认证授权服务与资源服务器

Spring Boot 3.x OAuth 2.0&#xff1a;构建认证授权服务与资源服务器 前言 随着Spring Boot 3的发布&#xff0c;我们迎来了许多新特性和改进&#xff0c;其中包括对Spring Security和OAuth 2.0的更好支持。本文将详细介绍如何在Spring Boot 3.x版本中集成OAuth 2.0&#xf…

数据可视化复习2-绘制折线图+条形图(叠加条形图,并列条形图,水平条形图)+ 饼状图 + 直方图

目录 目录 一、绘制折线图 1.使用pyplot 2.使用numpy ​编辑 3.使用DataFrame ​编辑 二、绘制条形图&#xff08;柱状图&#xff09; 1.简单条形图 2.绘制叠加条形图 3.绘制并列条形图 4.水平条形图 ​编辑 三、绘制饼状图 四、绘制散点图和直方图 1.散点图 2…

logback 初探学习

logback 三大模块 记录器&#xff08;Logger&#xff09;、追加器&#xff08;Appender&#xff09;和布局&#xff08;Layout&#xff09; 配置文件外层最基本的标签如图示 xml中定义的就是这个三个东西下面进入学习 包引入参考springboot 官方文档 Logging :: Spring Boo…

Linux:自定义Shell

本文旨在通过自己完成一个简单的Shell来帮助理解命令行Shell这个程序。 目录 一、输出“提示” 二、获取输入 三、切割字符串 四、执行指令 1.子进程替换 2.内建指令 一、输出“提示” 这个项目基于虚拟机Ubuntu22.04.5实现。 打开终端界面如图所示。 其中。 之前&#x…

《图像梯度与常见算子全解析:原理、用法及效果展示》

简介:本文深入探讨图像梯度相关知识&#xff0c;详细介绍图像梯度是像素灰度值在不同方向的变化速度&#xff0c;并以 “pig.JPG” 图像为例&#xff0c;通过代码展示如何选取图像部分区域并分析其像素值以论证图像梯度与边缘信息的关联。接着全面阐述了 Sobel 算子&#xff0c…