深入浅出JS—02 变量和函数提升

变量提升是JavaScript中的看起来很trick一个点,但是了解JS代码运行过程之后,一切都会迎刃而解

变量提升是什么

  • var声明的变量会自动被移到函数或者全局代码之上,但是变量赋值不会随之提升
  • 显示声明的函数作为一个整体也会提升到全局代码之上

如果你觉得这很难记住,记住也会忘(说明你是个正常人)。想彻底理解为什么会这样,JS代码是如何运行的,那么恭喜你发现宝藏。

JS代码执行过程

对于写好的代码假设有10行,JS引擎并不是以“行”为单位进行解析和运行。而是首先将10行代码从上到下整体解析一遍,构建出一个抽象语法树,之后转化为计算机可识别运行的机器语言,然后再从上到下逐行执行
在这里插入图片描述

为何变量声明提升了,赋值没有提升

结合上图,JS代码运行时,先进行词法解析,然后再执行代码。 变量提升发生在词法解析阶段,而赋值发生在执行代码阶段,执行到那一行才会给变量赋值。 这就是为什么变量声明提升了,赋值没有提升。

JS代码运行例子解析

现有JS代码如下:

var userName= "Jack";

console.log(userName); // "Jack"
console.log(age); // undefined

foo(); // 'Female' undefined
function foo() {
  var gender = 'Female';
  console.log(gender);
  console.log(age);
}

var age = 18;

阶段一:代码解析

全局代码解析阶段,JS引擎内部创建一个对象叫globalObject,简称GO,主要由三个部分组成:

// 上述代码对应的GO(伪代码)
globalObject = {
  // 一些JS的类,方法,函数
  String: "类",
  Math: "类",
  setTimeout: "函数",

  // 浏览器环境内置一个window属性,指向自身(node环境中没有
  // 它指向GO本身,运行console.log(window)或者console.log(window.window.window),指向的也是GO
  window: globalObject,

  // 全局作用域下变量声明
  userName: undefined, // 代码还没执行,没被赋值
  foo: 0xa00, 
  age: undefined, // 代码还没执行,没被赋值
};

创建的GO对象存储在内存的堆空间中;对于函数foo,V8引擎会在堆内存中开辟一块函数空间,来放置函数体和父级作用域,并将空间的地址0xa00存在全局变量函数名对应的值中。

阶段二:代码执行

在代码整体解析完成后,开始逐行运行代码,V8引擎会在内存中开辟一块栈空间,叫做执行上下文栈,也叫调用栈,用来存储执行上下文

执行上下文(Execution Context Stack,ECStack)可以理解为当前代码的运行环境,可以分为两类:

  • 全局执行上下文(Global Execution Context,GEC):全局环境,代码运行时创建
  • 函数执行上下文(Function Execution Context,FEC):函数环境,函数运行时创建

无论是全局执行上下文还是函数执行上下文,生命周期都包括三个阶段:第一个阶段为创建阶段,创建变量对象,之后作用域中涉及什么变量都到变量对象中来找,找不到就去父级变量对象中找。第二阶段为执行阶段,逐行执行代码,执行完之后,从调用栈中弹出,等待被JS引擎垃圾回收。
在这里插入图片描述
在 JavaScript 代码运行过程中,最先进入的是全局环境,所以先把全局执行上下文压入调用栈。

全局上下文创建阶段

创建变量对象VO,指向之前创建的全局对象GO;创建作用域链,记录父级作用域;确定this指向。
在这里插入图片描述

全局上下文执行阶段

逐行执行代码,代码中涉及到的变量都去变量对VO中寻找,找不到就去父级作用域中找。

var userName = "Jack" // 去VO中寻找username,之后赋值为"Jack" ,由于VO指向的是GO,所以改的是GO
console.log(userName) // 去VO中找username,并打印出来值,为'Jack'
console.log(age) // 去VO中找age,找到了age: undefined,打印出来undefined
foo()// 执行的过程中遇到函数,去VO中找`foo`,找到函数地址0xa00,开始创建函数上下文
函数上下文创建阶段

对函数先解析,再执行。解析时创建一个活跃对象Activation Object(AO),之后就会根据函数体创建一个函数执行上下文,并且加入到调用栈中。函数执行上下文中变量对象VO指向AO。之后确定作用域链:VO+parent scope,其中父级作用域是从0xa00中读取到的,为GO
在这里插入图片描述

函数上下文执行阶段
// 执行函数foo()内部代码
var gender = 'Female' //去函数对象VO中(其实是AO)找到gender,将其赋值为字符串'Female'
console.log(gender) //去函数对象VO中(其实是AO)找到gender,并打印
console.log(age) // 去函数对象VO中找到age,没有找到,去父级作用域查找也就是GO,找到了,值目前还是undefined,输出undefined
函数上下文销毁阶段

函数执行完,函数执行上下文出栈,等待被回收,就没有指针指向AO了,AO也会被回收。

  • 之后接着执行函数之后的代码,var age = 18,去VO中查找变量age,最后在GO中找到,赋值为18

全局执行上下文销毁阶段

此时代码全部执行完毕,全局执行上下文出栈,等待被垃圾回收

变量提升题目

步骤:

  • 写出GO,AO
  • 在赋值或者输出的语句处判断是对GO还是AO进行更改
  • 1
var n = 100;
function foo() {
	n = 200;
}
foo ();

console.log(n);

// 答案: 200

解析:函数中将n赋值为200,但是函数本身的变量对象中并没有n,所以要去父级变量对象中找并修改,所以将GO中的n赋值为了200。
在这里插入图片描述

  • 2
var n = 100;
function foo() {
	var n = 200;
}
foo ();

console.log(n);

// 答案: 100

解析:第二题可以和第一题对照理解,函数foo中含有变量n,所以n=200是将AO中的n赋值为200,并没有改变全局变量中n的值,所以依然为100
在这里插入图片描述

  • 3
function foo() {
	console.log(n);
	var n = 200;
	console.log(n);
}

var n = 100;
foo();

// 答案 undefined, 200

解析:console.log(n)是在函数调用栈执行,所以n要先从AO中找,赋值之前先输出的是undefined,赋值之后输出的是200
在这里插入图片描述

  • 4
var foo=1;  
 
function bar(){
   if(!foo){
     var foo=10; 
   }
   console.log(foo); 
  }
}

bar();  

// 答案 10 

解析:在函数bar中,无论条件语句是否满足条件,var foo = 10解析时,都会将foo作为函数内变量提升到AO中,且初始值为undefined,所以最后输出为10
在这里插入图片描述

  • 5
var name = "xs";
function foo1() {
  console.log(name);
}

function foo2() {
	var name = 'foo2';
	foo1();
}

// 答案 "xs"

解析:函数foo1执行时,对其foo1对应的VO中寻找name,没找到,之后去父级作用域中找,父级作用域在函数声明时已经确定,与函数在哪里调用无关,所以父级作用域为全局GO,name为“xs”.

总结

JS中的变量提升其实与代码执行过程有关。变量和函数提升发生在代码解析阶段,代码执行时,遇到变量赋值或者输出操作,会先在当前执行上下文的变量对象中搜索,如果找到该变量,就对其进行操作,否则就延着作用域链去父级作用域中寻找,若都没有找到,就会报错。

码字画图不易,如果对你有些许帮助,请别忘记点个赞哦👍;如果还有没看懂的地方,先收藏一下⭐,早晚会弄明白!
你的支持是我更新的动力(💖)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值