JavaScript变量提升原理详解和实例

变量提升

//示例1

showName();
console.log(myname);
var myname = '张三';
function showName() {
    console.log('执⾏函数showName');
}

上面这段代码的输出结果为:
执⾏函数showName
undefined

问题1:执行showName();的时候,函数是在这行代码之后被定义的,为什么能正确执行
问题2:执行console.log(myname);的时候,变量是在这行代码之后被定义的,输:出为undefined,那么这个undefined值存储在哪里
问题3:为什么函数能正确执行,而打印变量却得到的是undefined,难道函数和变量有什么不同吗
问题4:提升的变量存储在哪里

要解决这几个问题,先来看下什么是JavaScript中的声明和赋值

变量的声明和赋值
var myname = '张三';

上面那段代码可以看成两行代码组成:

var myname;          //变量声明
myname = '张三';    //变量赋值
函数的声明和赋值
//函数声明
function showName() {
    console.log('执⾏函数showName');
}

//声明一个变量,再将匿名函数赋值给该变量
var show = function () {
    console.log('执⾏函数showName');
}
变量提升

变量提升是指JavaScript在执行过程中,JavaScript引擎将变量和函数的声明提升到代码开头的行为,并且,变量提升后,该变量的默认值为undefined,函数提升后,会将函数在堆中的首地址赋值给函数变量。
此外,变量提升并不是说真正的把var myname = '张三';这行代码的变量声明var myname在物理层面上提升到代码的开头处,而是在编译阶段被JavaScript引擎放入内存中。

提升的变量存储在哪里

上面说过变量声明是在JavaScript代码编译阶段放入内存中的,这具体是怎么回事呢?
JavaScript代码执行大致分为两个阶段:编译阶段和执行阶段
对于示例1的代码,变量提升部分和执行部分为:

//变量提升部分

var myname = undefined;
function showName() {
    console.log('执⾏函数showName');
}

//执行部分

showName();
console.log(myname);
myname = '张三';

编译阶段

生成执行上下文和可执行代码。执行上下文是JavaScript执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,然后确定该函数在执行期间用到的变量:this、变量、函数等。
在执行上下文中存在一个变量环境的对象( VariableEnvironment),提升的变量就是保存在该对象中的。
VariableEnvironment:
myname: undefined
showName:存储了函数在堆中的地址

因此,通过编译阶段,示例1的代码生成了如上所示的变量环境对象,该对象包含一个提升的变量myname,值为undefined,一个提升的函数,值为函数在堆中的地址。

执行阶段

按照顺序执行代码的可执行部分

  • 执行showName();时,JavaScript引擎开始在变量环境对象中查找该函数,由于VariableEnvironment中存在showName,值为函数定义的地址,因此可以执行该函数
  • 执行console.log(myname);,JavaScript引擎继续在变量环境对象中查找变量,找到myname,值为undefined,然后输出
  • 执行myname = ‘张三’;将 '张三’赋值给环境变量对象中的myname属性。

小结

在JavaScript代码的编译阶段,先做变量提升,将执行上下文所用到的变量、函数的声明放到环境变量对象中,执行的时候,到自己的执行上下文的环境变量对象中取相应的变量。

实例

实例1:

function showName() {
    console.log('张三');
}

showName();

function showName() {
    console.log('李四');
} 
showName();

结果:
李四
李四

分析
在编译阶段,变量提升的部分为:

function showName() {
    console.log('张三');
}

function showName() {
    console.log('李四');
} 

当遇到第一个showName函数时,将该函数的声明放入变量环境对象中,当遇到第二个showName函数时,将该函数替换掉已存在的showName。因此,执行showName();的时候,取环境变量对象中查找该函数,只存在第二个showName函数。通过这个实例,反应了JavaScript执行的一个重要概念:先编译,编译生成执行上下文(包括环境变量对象)和可执行代码,再执行。

实例2:

showName();
var showName = function() {
    console.log(2);
} 
function showName() {
    console.log(1);
}
showName();

结果:
1
2

分析
实例2代码在编译阶段的变量提升部分为:

var showName=undefined;
function showName() {
    console.log(1);
}

当存在相同的变量名时,后声明的变量会覆盖先声明的变量,因此环境变量对象中showName的值是函数的地址。

执行部分为:

showName();
var showName = function() { //赋值
    console.log(2);
}
showName();
  • 执行showName();时,在环境变量对象中找到该函数,打印值为1
  • 执行赋值语句时,将showName的值改为另一个函数的地址
  • 再次执行showName();时,环境变量对象中的showName存储的值已经改变了,打印出2

通过这个实例,当我们在分析一段JavaScript代码在执行阶段取值时,可以先将整体代码分为声明提升部分和执行部分。

实例3:
这段代码能正确输出Hi",是因为这里是函数声明提升,因此能找到该函数的具体定义方法。

sayHi();    //正常输出"Hi"
function sayHi(){
	alert("Hi!");
}

这段代码报错,因为sayHi是一个变量,提升阶段默认值是undefined。

sayHi();    //报错
var sayHi=function(){
	alert("Hi!");
}

实例4:

var scope="gloval";
function f() {
    console.log(scope);     //undefined
    var scope="local";
    console.log(scope);     //"local"
}
f();

console.log(scope); //undefined这里可能会使人有点疑惑,按理说在var scope="gloval";执行后,scope变量值应该是"gloval"。但是,上面提到的环境变量对象是存在一个执行上下文中的,f函数有一个自己的执行上下文,因此console.log(scope); //undefined打印数据的时候,在f函数的上下文的环境变量对象中的scope是var scope="local";的变量提升,默认值为undefined。

实例5:

function foo() {
    for(var i = 0; i < 10; i++) {
        
    }
    console.log(i);     //10
}

这段代码能很好地体现JavaScript中函数作用域和
其他语言中块级作用域的区别(ES6及其之后也存在
块级作用域),在块级作用域中,console.log(i);肯定是访问不到i变量的,因为i变量输入for循环块。但是,在JavaScript中,i变量存在于foo函数的上下文中,在这段代码的编译阶段,i变量进行变量提升,执行阶段,for循环执行完后,i的值改为10,因此最后打印出10。

4 从数据存储的角度看闭包原理

第3节只是从原理上讲解了下闭包的作用和概念,这里再从数据存储的角度看闭包原理。

我在《JavaScript数据类型及其存储方式》一文中讲解了JavaScript中,简单类型的变量就存储在栈空间中,引用类型的变量存储在堆空间中。
第3节中说foo函数的闭包存储在内存中,即使foo函数执行结束,通过bar.setName或者bar.getName也能访问到foo函数中的变量,那么这个闭包具体是如何存储的呢?

这里从数据存储的角度分析:
当JavaScript引擎遇到内部函数的时候,会对内部函数做一次词法分析,发现内部函数使用了外部函数中的myname变量和test1变量,于是,在堆空间中创建foo函数的闭包,保存myname变量和test1变量,而test2变量没有被内部函数用到,因此继续保留在栈中。

当foo函数执行到return innerBar;的时候,调用栈和堆空间如下图:
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值