【JavaScript】变量提升 - 函数提升 - 执行上下文 - 执行上下文栈

本文详细解释了JavaScript中的变量提升和函数提升的概念,并通过多个示例深入探讨了这两个特性如何影响代码的行为。此外,还介绍了执行上下文的概念,包括全局执行上下文和函数执行上下文,以及它们如何帮助理解变量和函数提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


一、变量提升、函数提升

1. 变量(声明)提升

  • 通过var定义(声明)的变量,在定义语句之前就可以访问到
    不过值为:undefined

来看一个经典面试题

var a = 3
function fn() {
	console.log(a) 	//undefined
	var a = 4
}
fn()

通过打印结果我们能看出a的值为undefined不是3,这是为什么呢?因为这里存在两个知识点:作用域变量提升。由于log局部作用域(函数作用域)中,通过作用域链的“就近原则”我们知道,它会先从函数作用域里寻找变量a,再去寻找全局作用域,而又因为变量提升的缘故,导致最终结果为undefined。

实际上这块代码应该是这样的:

var a = 3
function fn() {
	var a;
	console.log(a) 	//undefined
	a = 4
}
fn()

这样就很好理解了吧!

2. 函数(声明)提升

通过function声明的函数,在之前就可以直接调用
值:函数定义(对象)

fn1()
function fn1() {
	console.log('fn1')  //fn1
}

相当于:

function fn1() {
	console.log('fn1')
}
fn1()

从以上代码结果我们可以看出,用function定义的函数,先调用后声明依然可以成功

我们通过打断点的方式来理解解析js代码的流程:

console.log(b)
fn1()
var b = 3
function fn1() {
	console.log('fn1')  //fn1
}

在这里插入图片描述
在执行js代码的开始,也就是在执行第15行时,我们就可以看到window对象已经包含了b变量与函数f1了,也就是说,在代码执行之前,用var声明变量b与用function定义的函数f1已经提前在window对象上创建了,所以说白了,在提前访问的时候,也就是在访问window对象上的变量b与函数f1,这就是为什么会出现变量/函数提升了。


我们以上说的无论是变量提升还是声明提升都是一个结果,是一个现象的结果,那接下来我们讨论一下:是什么导致了这样的结果?

二、执行上下文

我们都知道代码在位置上分为:

  1. 全局代码
  2. 函数(局部)代码
console.log(a1)  //相当于console.log(window.a1)
a2()	//相当于window.a2()

var a1 = 3
funciton a2() {
	console.log('a2')
}

我们都知道在执行代码之前 a1 和a2已经存在于window里了,说明在执行全局代码之前,js引擎肯定做了一些准备工作

1. 全局执行上下文

准备工作:

  1. 在执行全局代码前将window确定为全局执行上下文
  2. 对全局数据进行预处理(预编译)
    var定义的全局变量 ------> undefined, 添加为window的属性
    function声明的全局函数 --------> 赋值(fun),添加为window的方法
    this --------> 赋值(window)
  3. 开始执行全局代码

总结:也就是说,我们访问用var声明的变量或者用function定义函数,都会从全局上下文(window)找

2. 函数执行上下文

function fn(a1) {
    console.log(a1);    //2
    console.log(a2);    //undefined
    a3()    //a3
    console.log(this);  //window
    console.log(arguments) //伪数组(2,3)

    var a2 = 3
    function a3() {
        console.log('a3');
    }
}

fn(2,3)

准备工作:

  1. 调用函数后,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
  2. 对局部数据进行预处理(预编译)
    形参变量--------> 赋值(实参),添加为(该函数的)执行上下文的属性
    argument--------> 赋值(实参列表),添加为(该函数的)执行上下文的属性
    var定义的全局变量 ------> undefined, 添加为(该函数的)执行上下文的属性
    function声明的全局函数 --------> 赋值(fun),添加为(该函数的)执行上下文的方法
    this --------> 赋值(调用函数的对象)
  3. 开始执行全局代码

三、执行上下文栈

							//1.进入全局执行上下文
var a = 10
var bar = function(x) {
    var b = 5
    foo(x + b) //foo能够重新调用吗?		//3.进入foo函数执行上下文
}
var foo = function(y) {
    var c = 5
    console.log(a + c + y);
}
bar(10)						//2.进入bar函数执行上下文

可能有些人会有疑问foobar函数里,那么foo函数会执行吗? 会的。因为是先定义的foo函数,然后再调用bar,而在调用foo函数时,window上已经存在了。
相当于:

var a;
var bar;
var foo;
a = 10
bar = function(x) {
    var b = 5
    foo(x + b)
}
foo = function(y) {
    var c = 5
    console.log(a + c + y);
}
bar(10)

这样就很好理解了吧!


那么,以上代码块总共创建了几个执行上下文呢?
3个:window执行上下文、bar执行上下文、foo执行上下文

得出结论:n + 1 个
n:调用了几个函数
1:window

那么,有这么多的执行上下文,我们就需要用管理它们

在这里插入图片描述

1.在全局代码执行前,JS引擎就会创建一个来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中(压栈)
4.在当前函数执行完后,将栈顶的对象移除(出栈)
5.当所有的代码执行完后,栈中只剩下window


四、经典面试题

面试题1

  1. 依次输出什么?
  2. 整个过程中产生了几个执行上下文?
//1.依次输出什么?
//2.整个过程中产生了几个执行上下文?
console.log('global begin' + i);
var i = 1
foo(1)
function foo(i) {
    if(i === 4) {
        return
    }
    console.log('foo() begin:' + i);
    foo(i + 1)
    console.log('foo() end:' +  i);
}
console.log('global end:' + i);

依次输出:
global begin undefined
foo() begin: 1
foo() begin: 2
foo() begin: 3

foo() end: 3
foo() end: 2
foo() end: 1

执行上下文: 5个
foo 1~4个
window 1个

global end: 1


面试题2

var c = 1
function c() {
	console.log(c)
}
c(2)	// c is not a function

相当于:

function c() {
	console.log(c)
	var c = 3
}
var c
c = 1		//此处覆盖了1覆盖了c函数,因此调用1,肯定就会报错了
c(2)	// c is not a function

面试题3

console.log(bar);  // funciont bar() { console.log(123) }
console.log(bar()); // undefined
var bar = 456;
function bar() {
    console.log(123); // 123
}
console.log(bar); // 456
bar = 789;
console.log(bar); // 789
console.log(bar()) // bar is not a function

本题考察变量提升谁先执行,很显然是函数提升优先。
以上代码块相当于:

function bar() {
	console.log(123)
}
var bar    //注意:变量提升,变量提升不会覆盖(同名)函数提升,只有变量再次赋值时,才会被覆盖
console.log(bar)  // funciont bar() { console.log(123) }
console.log(bar())  //undefined  因为没有返回值
bar = 456
console.log(bar)	// 456
bar = 789
console.log(bar)  // 789
console.log(bar())  // bar is not a function
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值