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: 在 JavaScript 中,变量提升指的是在代码执行前,变量声明就已经被提升(即复制)到了当前作用域的最顶端。这意味着,无论变量在代码中定义在哪里,它都在代码执行前被提升到所在作用域的最顶端。 例如: ``` console.log(a); // undefined var a = 1; ``` 在这个例子中,虽然变量 `a` 实际上定义在了第二行,但是在代码执行前,它已经被提升到了最顶端。所以在第一行中,当我们尝试访问 `a` 时,它已经被声明过了,所以输出 `undefined`。 注意,变量提升仅仅是声明(复制)被提升,而不是赋值。所以在上面的例子中,变量 `a` 的值在第二行被赋值为 1,而不是在第一行被赋值为 `undefined`。 ### 回答2: JavaScript变量提升JavaScript中的一种特性。在JavaScript中,变量的声明在代码执行之前进行处理,也就是说它们被“提升”到其作用域的顶部。这意味着你可以在声明之前使用变量,而不引发错误。 例如,假设我们有以下的JavaScript代码: ``` console.log(x); // undefined var x = 5; console.log(x); // 5 ``` 在这个例子中,变量x被提升到了代码的顶部,所以在第一个console.log语句中,x被声明了但尚未被赋值,所以它的值是undefined。然后,变量x被赋值为5,并在第二个console.log语句中打印出来。 需要注意的是,只有变量的声明提升,而不是它们的赋值。例如: ``` console.log(y); // ReferenceError: y is not defined y = 10; console.log(y); // 10 var y; ``` 在这个例子中,变量y在它的声明之前被使用引发错误。只有在变量y的声明后,它的赋值才生效。 变量提升在理解JavaScript的作用域和执行顺序时非常重要。它使得我们可以在变量声明之前使用变量,但也可能导致一些意外的结果,所以在编写代码时要小心使用。 ### 回答3: JavaScript变量提升是指在代码执行之前,所有变量的声明提升到代码的顶部。这意味着我们可以在变量声明之前使用这些变量。但是要记住的是,只有变量的声明提升,而不是初始化。 例如,我们可以在变量声明之前输出变量的值: console.log(x); // undefined var x = 5; 这段代码中,变量x在声明之前被赋值为undefined,因此在打印x时,它的值是undefined。 变量提升还可以应用于函数声明。我们可以在函数声明之前调用函数: myFunction(); // "Hello, World!" function myFunction() { console.log("Hello, World!"); } 在这个例子中,函数myFunction在声明之前被调用,所以我们可以在调用函数之前定义它。 需要注意的是,变量提升仅适用于使用var关键字声明的变量,而不适用于使用let和const关键字声明的变量。使用let和const声明的变量是块级作用域的,不提升。 综上所述,JavaScript变量提升是一种将变量的声明提升到代码顶部的机制,使我们可以在变量声明之前使用这些变量。这在代码书写和阅读上提供了一定的便利,但也需要注意一些细节,以避免出现意料之外的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值