悟透前端:加深Javascript变量函数声明提升理解

简介: Javascript变量函数声明提升(Hoisting)是在 Javascript 中执行上下文工作方式的一种认识(也可以说是一种预编译),从字面意义上看,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,在代码里的位置是不会动的,而是在编译阶段被放入内存中会和代码顺序不一样。

image.png

Javascript变量函数声明提升(Hoisting)是在 Javascript 中执行上下文工作方式的一种认识(也可以说是一种预编译),从字面意义上看,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,在代码里的位置是不会动的,而是在编译阶段被放入内存中会和代码顺序不一样。


变量函数声明提升虽然对于实际编码影响不大,特别是现在ES6的普及,但作为前端算是一个基础知识,必须掌握的,是很多大厂的前端面试必问的知识点之一

变量知道是ES5中的 var 和 function 中的产物,ES6中的 let 、 const 则不存在有变量提升。

变量提升

JavaScript引擎的工作方式是先解析代码,获取所有声明的变量和函数,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(Hoisting)。

这里说的变量声明,包括函数的声明,接下来看看代码:

function hoistingVariable() {
    if (!devpoint) {
        var devpoint = 1;
    }
    console.log(devpoint);
}
hoistingVariable();
// 下面是输出结果
// 1

变量所处的作用域为函数体内,解析的时候查找该作用域中的声明的变量,devpoint在if虽然未声明,根据变量提升规则,变量的声明提升到函数的第一行,但未赋值。实际的效果等同于下面的代码:

function hoistingVariable() {
    var devpoint;
    if (!devpoint) {
        devpoint = 1;
    }
    console.log(devpoint);
}
hoistingVariable();

接下再增加一些迷惑的代码,如下:

var devpoint = "out";
function hoistingVariable() {
    var devpoint;
    if (!devpoint) {
        devpoint = "in";
    }
    console.log(devpoint);
}
hoistingVariable();
console.log(devpoint);
// 下面是输出结果
// in
// out

对于同名变量声明,个人理解是先找作用域,就近原则,函数体内声明(前提是有声明 var ),就只找函数内查找,并不受函数外声明的影响。

把上面函数体内的声明语句去掉,输出情况也就不一样。

var devpoint = "out";
function hoistingVariable() {
    if (!devpoint) {
        devpoint = "in";
    }
    console.log(devpoint);
}
hoistingVariable();
console.log(devpoint);
// 下面是输出结果
// out
// out

函数体内声明语句去掉后,这是就需要去函数体外找声明,根据这一条,函数外声明并赋值了,函数体内的 if 语句就不会执行。

下面代码调整了赋值的顺序,代码如下:

var devpoint;
function hoistingVariable() {
    if (!devpoint) {
        devpoint = "in";
    }
    console.log(devpoint);
}
devpoint = "out";
hoistingVariable();
console.log(devpoint);
// 下面是输出结果
// out
// out

根据上面说的,函数体内的变量是外部声明的,但未赋值,函数是提升了,并为执行。在函数执行前赋值给devpoint,再执行就变成了 out

函数提升

上面介绍过,变量提升,同样包括函数的声明,不同方式的函数声明,执行也有所不同。这种问题就是直接上代码。

function hoistingFun() {
    hello();
    function hello() {
        console.log("hello");
    }
}
hoistingFun();
// 下面是输出结果
// hello

上面的代码能够正常运行是因为函数声明被提升,函数 hello 被提升到顶部,运行效果跟下面代码一致:

function hoistingFun() {
    function hello() {
        console.log("hello");
    }
    hello();
}
hoistingFun();
// 下面是输出结果
// hello

如果在同一个作用域中对同一个函数进行声明,后面的函数会覆盖前面的函数声明。

function hoistingFun() {
    hello();
    function hello() {
        console.log("hello");
    }
    function hello() {
        console.log("hello2");
    }
}
hoistingFun();
// 下面是输出结果
// hello2

两个函数声明都被提升了,按照声明的顺序,后面的声明覆盖前面的声明。

函数声明常见的方式有两种,还有一种是匿名函数表达式声明方式,这种方式可以视为是变量的声明来处理,当作用域中有函数声明和变量声明时,函数声明的优先级最高,将上面的代码更改后,结果就不一样了,如下:

function hoistingFun() {
    hello();
    function hello() {
        console.log("hello");
    }
    var hello = function () {
        console.log("hello2");
    };
}
hoistingFun();
// 下面是输出结果
// hello

上面的代码,编译逻辑如下:

function hoistingFun() {
    function hello() {
        console.log("hello");
    }
    hello();
    hello = function () {
        console.log("hello2");
    };
}
hoistingFun();
// 下面是输出结果
// hello

接下来再来看下,外部使用变量声明,函数体内使用函数声明的示例:

var hello = 520;
function hoistingFun() {
    console.log(hello);
    hello = 521;
    console.log(hello);
    function hello() {
        console.log("hello");
    }
}
hoistingFun();
console.log(hello);
// 下面是输出结果
// [Function: hello]
// 521
// 520

上面说过,在函数体内声明过的变量或者函数,只作用于函数体内,受限于函数体内,不受外部声明的影响,相当于函数体内作用域与外部隔离。上面代码的编译后的逻辑如下:

var hello = 520;
function hoistingFun() {
    function hello() {
        console.log("hello");
    }
    console.log(hello);
    hello = 521;
    console.log(hello);
}
hoistingFun();
console.log(hello);

在变量声明中,函数的优先权最高,永远提升到作用域最顶部,然后才是函数表达式和变量的执行顺序。

来看下面的代码:

var hello = 520;
function hello() {
    console.log("hello");
}
console.log(hello);
// 下面是输出结果
// 520

根据函数声明优先级最高的原则,上面代码的执行逻辑如下:

function hello() {
    console.log("hello");
}
hello = 520;
console.log(hello);

为什么要提升?

至于为什么要提升,这里不做详细介绍,提供一些参考文章,有兴趣的可以去查阅

最佳实践

现代Javascript中,已经有很多方式避免变量提升带来的问题,使用letconst替代var,使用eslint等工具避免变量重复定义,在一些前端开发团队中,可以针对团队做一些规范化的脚手架,如项目初始化强制项目的目录、eslint的最佳配置等,用程序规范的过程比人督促要靠谱。

下面的代码可以看到const 和 var 声明的变量的区别,const 声明的变量不会提升,具体的区别可以查阅《细说javascript中变量声明var、let、const的区别

console.log("1a", myTitle1);
if (1) {
    console.log("1b", myTitle1);
    var myTitle1 = "devpoint";
}
if (1) {  // 这里的代码是有错误无法执行
    console.log("3c", myTitle2);
    const myTitle2 = "devpoint";
}
// 下面是输出结果
// 1a undefined
// 1b undefined

总结

通过自我学习变量函数提升,加深了对其理解,对于前端面试所涉及的类似问题可以自信的给出答案,算是一种收获。

相关文章
|
14天前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
111 2
|
14天前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
30 0
|
10天前
|
前端开发 JavaScript 安全
JavaScript前端开发技术
JavaScript(简称JS)是一种广泛使用的脚本语言,特别在前端开发领域,它几乎成为了网页开发的标配。从简单的表单验证到复杂的单页应用(SPA),JavaScript都扮演着不可或缺的角色。
16 3
|
3天前
|
前端开发 JavaScript UED
"前端小技巧大揭秘:JS如何将后台时间戳秒变亲切小时前、分钟前,让用户秒懂,提升互动体验!"
【10月更文挑战第23天】在Web开发中,将后台返回的时间戳转换为“小时前”、“分钟前”、“刚刚”等友好的时间描述是常见需求。本文介绍如何用JavaScript实现这一功能,通过计算当前时间和时间戳的差值,返回相应的描述,提升用户体验。
8 0
|
9天前
|
JavaScript 前端开发
局部 JavaScript 变量
JavaScript 中,函数内部使用 `var` 声明的变量为局部变量,仅在函数内可见,函数执行完毕后被删除。全局变量则在函数外部声明,整个页面的脚本和函数均可访问,页面关闭后才被删除。未声明的变量赋值会自动成为 `window` 对象的属性,且在非严格模式下可被删除。
|
14天前
|
JavaScript 前端开发 应用服务中间件
vue前端开发中,通过vue.config.js配置和nginx配置,实现多个入口文件的实现方法
vue前端开发中,通过vue.config.js配置和nginx配置,实现多个入口文件的实现方法
85 0
|
14天前
|
前端开发 JavaScript 程序员
【从前端入门到全栈】Node.js 之核心概念
【从前端入门到全栈】Node.js 之核心概念
|
14天前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题
|
14天前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
|
14天前
|
人工智能 自然语言处理 运维
前端大模型应用笔记(一):两个指令反过来说大模型就理解不了啦?或许该让第三者插足啦 -通过引入中间LLM预处理用户输入以提高多任务处理能力
本文探讨了在多任务处理场景下,自然语言指令解析的困境及解决方案。通过增加一个LLM解析层,将复杂的指令拆解为多个明确的步骤,明确操作类型与对象识别,处理任务依赖关系,并将自然语言转化为具体的工具命令,从而提高指令解析的准确性和执行效率。