基础:JavaScript的怪癖之一:提升(Hoisting)

2023-11-08 16:28

本文主要是介绍基础:JavaScript的怪癖之一:提升(Hoisting),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JavaScript,通常被称为“Web 语言”,是一种多功能且广泛使用的编程语言。它以其怪癖而闻名,其中之一就是 hoisting(提升)。无论你是经验丰富的开发人员还是刚刚开始你的编码之旅,理解提升对于编写干净和高效的 JavaScript 代码至关重要。

在本文中,我们将带您了解 JavaScript 中的提升概念,揭示变量和函数是如何被提升的。到最后,你不仅能掌握提升背后隐藏的机制,还能学会如何利用它为你带来优势。话不多说,让我们一起进入 JavaScript 提升的迷人世界!

什么是提升?

在我们深入研究细节之前,让我们揭开 JavaScript 中提升的神秘面纱。提升是一个后台进程,它在编译阶段将变量和函数声明移动到它们所包含作用域的顶部。这允许你在正式声明它们之前就使用它们。

想象一下,它就像一个魔术师从帽子里拿出一只兔子,兔子就是你的变量或函数,帽子就是 JavaScript 引擎,提升确保魔术师(JavaScript)总是能找到它需要的兔子(变量或函数),无论它被放在代码的什么位置。

变量提升

var 的魔力

在 JavaScript 中,用 var 声明的变量会表现出一种奇怪的提升行为,当你用 var 声明一个变量时,它会被提升到它所属的函数或全局作用域的顶部。考虑以下例子:

function hoistExample() {console.log(myVar); // Outputs: undefinedvar myVar = 42;console.log(myVar); // Outputs: 42
}
hoistExample();

hoistExample 函数中,我们尝试在声明 myVar 之前记录它的值。令人惊讶的是,第一个 console.log 语句没有抛出错误。相反,它输出了 undefined 。这是由于提升 —— myVar 的声明被移动到函数的顶部,使其在整个作用域中可访问。

let 和 const 的混合

var 的行为似乎有悖直觉,而且它经常导致意外的 bug,为了解决这个问题,JavaScript 引入了 letconst ,它们具有不同的提升机制。

function hoistExample() {console.log(myVar); // Throws a ReferenceErrorlet myVar = 42;console.log(myVar);
}
hoistExample();

使用 letconst 时,仍然会发生提升,但是变量在代码中实际声明之前不会初始化。这意味着在声明 myVar 之前尝试访问它会导致 ReferenceError 。这种行为促进了代码的更清晰和更可预测性。

函数声明 vs. 表达式

函数声明提升

就像变量一样,JavaScript 中的函数也会被提升,让我们来探讨一下在提升时函数声明和函数表达式之间的区别。

hoistMe(); // Outputs: "I'm hoisted!"
function hoistMe() {console.log("I'm hoisted!");
}

在这个例子中,我们在声明 hoistMe 函数之前调用它。由于提升,没有错误,函数按预期执行。函数声明被整体提升,使它们在作用域内的任何地方可用。

函数表达式

另一方面,函数表达式有不同的提升行为。

hoistMe(); // Throws a TypeError
var hoistMe = function () {console.log("I'm not hoisted!");
};

在这种情况下,当我们试图在 hoistMe 的声明之前调用它时,我们遇到了一个 TypeError 。函数表达式不会以与函数声明相同的方式被提升。变量 hoistMe 被提升了,但它对函数的赋值没有,这就是为什么在赋值之前调用它会导致错误。

作用域和提升

要完全理解提升,必须掌握 JavaScript 中的作用域的概念,作用域决定了变量和函数在代码中的访问位置。

全局作用域

在任何函数或代码块之外声明的变量具有全局作用域,可以在 JavaScript 代码的任何位置访问它们。

var globalVar = "I'm global!";
function accessGlobalVar() {console.log(globalVar); // Outputs: "I'm global!"
}
accessGlobalVar();

在上面的例子中, globalVaraccessGlobalVar 函数中可用,因为它具有全局作用域。

局部作用域

在函数或代码块中声明的变量具有局部作用域,只能在声明它们的作用域中访问它们。

function localScopeExample() {var localVar = "I'm local!";console.log(localVar); // Outputs: "I'm local!"
}
localScopeExample();
console.log(localVar); // Throws a ReferenceError

localScopeExample 函数中, localVar 具有局部作用域,因此在函数之外无法访问它。尝试全局访问它会导致 ReferenceError

常见的提升陷进

理解提升对于编写干净、无 bug 的代码至关重要。以下是处理提升时需要注意的一些常见陷阱:

重定义变量

当你在同一个作用域中使用 var 多次声明同一个变量时,它不会抛出错误,只是简单地重新赋给变量一个新值。

var myVar = "First declaration";
var myVar = "Second declaration";
console.log(myVar); // Outputs: "Second declaration"

这种行为会导致意想不到的后果,因为重定义变量会使代码更难理解和维护。使用 letconst 来防止意外的变量重声明。

函数重写

在 JavaScript 中,如果你多次声明同一个函数,最后一次声明将覆盖之前的任何一次声明,这可能会导致意想不到的行为和错误。

function myFunction() {console.log("First definition");
}
function myFunction() {console.log("Second definition");
}
myFunction(); // Outputs: "Second definition"

为了避免函数重写,请始终使用唯一的函数名,并保持清晰和有组织的代码结构。

代码整洁的最佳实践

既然我们已经探讨了提升的细微差别和潜在的陷阱,那么让我们深入研究一些最佳实践,以编写干净和可维护的 JavaScript 代码。

正确声明变量
为了防止与提升相关的问题,在变量的作用域顶部声明变量,如果你使用 var ,考虑切换到 letconst 来利用块作用域,这更可预测,更安全。

function cleanCodeExample() {var localVar = "I'm declared at the top";//此处显示您的其余代码
}

通过在开头声明变量,可以使代码更具可读性,并减少遇到意外情况的可能性。

组织函数
当使用函数时,请确保定义一致。在代码库中使用函数声明或函数表达式来维护统一的结构。

// 良好做法
function calculateSum(a, b) {return a + b;
}// 避免混淆函数声明和表达式
var multiply = function (a, b) {return a * b;
};

代码风格的一致性不仅可以提高代码的清晰度,还可以最大限度地减少与提升相关的问题。

总结:提升的力量

JavaScript 提升是一种隐藏的魔法,它通过将变量和函数声明移动到其作用域的顶部来优化代码,从而增强语言的行为。了解各种变量声明和函数声明的提升,可以让代码更简洁,让专家更熟练。接受并掌握这种方法可以提高代码编写的效率和优雅程度,使其成为开发人员的重要工具。总之,在编码过程中,JavaScript 的怪异之处(包括提升)应作为资产加以利用,以提高工作效率和编码技能。


原文:https://vishwasacharya.hashnode.dev/hoisting-in-javascript

这篇关于基础:JavaScript的怪癖之一:提升(Hoisting)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/371065

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2