写点什么

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

用户头像
devpoint
关注
发布于: 2021 年 05 月 26 日
悟透前端:加深Javascript变量函数声明提升理解

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
复制代码

总结

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

发布于: 2021 年 05 月 26 日阅读数: 11
用户头像

devpoint

关注

细节的追求者 2011.11.12 加入

专注前端开发,用技术创造价值!

评论

发布
暂无评论
悟透前端:加深Javascript变量函数声明提升理解