前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

JavaScript进阶_执行上下文栈和变量对象|博客系列

# 变量提升

在使用javascript编写代码的时候, 我们知道, 声明一个变量用var, 定义一个函数用function.那你知道程序在运行它的时候, 都经历了什么吗?

# 变量声明提升

首先是用var定义一个变量的时候, 例如:

var a = 10;
@前端进阶之旅: 代码已经复制到剪贴板

大部分的编程语言都是先声明变量再使用, 但是javascript有所不同, 上面的代码, 实际相当于这样执行:

var a;
a = 10;
@前端进阶之旅: 代码已经复制到剪贴板

因此有了下面这段代码的执行结果:

console.log(a); // 声明,先给一个默认值undefined;
var a = 10; // 赋值,对变量a赋值了10
console.log(a); // 10
@前端进阶之旅: 代码已经复制到剪贴板

上面的代码👆在第一行中并不会报错Uncaught ReferenceError: a is not defined, 是因为声明提升, 给了a一个默认值.

这就是最简单的变量声明提升.

# 函数声明提升

定义函数也有两种方法:

  • 函数声明: function foo () {};
  • 函数表达式: var foo = function () {}.

第二种函数表达式的声明方式更像是给一个变量foo赋值一个匿名函数.

那这两种在函数声明的时候有什么区别吗?

案例一🌰:

console.log(f1) // function f1(){}
function f1() {} // 函数声明
console.log(f2) // undefined
var f2 = function() {} // 函数表达式
@前端进阶之旅: 代码已经复制到剪贴板

可以看到, 使用函数声明的函数会将整个函数都提升到作用域(后面会介绍到)的最顶部, 因此打印出来的是整个函数;

而使用函数表达式声明则类似于变量声明提升, 将var f2提升到了顶部并赋值undefined.


我们将案例一的代码添加一点东西:

案例二🌰:

console.log(f1) // function f1(){...}
f1(); // 1
function f1() { // 函数声明
	console.log('1')
}
console.log(f2) // undefined
f2(); // 报错: Uncaught TypeError: f2 is not a function
var f2 = function() { // 函数表达式
	console.log('2')
}
@前端进阶之旅: 代码已经复制到剪贴板

虽然f1()在function f1 () {...}之前,但是却可以正常执行;

而f2()却会报错, 原因在案例一中也介绍了是因为在调用f2()时, f2还只是undifined并没有被赋值为一个函数, 因此会报错.

# 声明优先级: 函数大于变量

通过上面的介绍我们已经知道了两种声明提升, 但是当遇到函数和变量同名且都会被提升的情况时, 函数声明的优先级是要大于变量声明的.

  • 变量声明会被函数声明覆盖
  • 可以重新赋值

案例一🌰:

console.log(f1); // f f1() {...}
var f1 = "10";
function f1() {
	console.log('我是函数')
}
// 或者将 var f1 = "10"; 放到后面
@前端进阶之旅: 代码已经复制到剪贴板

案例一说明了变量声明会被函数声明所覆盖.

案例二🌰:

console.log(f1); // f f1() { console.log('我是新的函数') }
var f1 = "10";

function f1() {
		console.log('我是函数')
}

function f1() {
		console.log('我是新的函数')
}
@前端进阶之旅: 代码已经复制到剪贴板

案例二说明了前面声明的函数会被后面声明的同名函数给覆盖.

如果你搞懂了, 来做个小练习?

练习✍️

function test(arg) {
		console.log(arg);
		var arg = 10;
		function arg() {
				console.log('函数')
		}
		console.log(arg)
}
test('LinDaiDai');
@前端进阶之旅: 代码已经复制到剪贴板

答案📖

function test(arg) {
		console.log(arg); // f arg() { console.log('函数') }
		var arg = 10;
		function arg() {
				console.log('函数')
		}
		console.log(arg); // 10
}
test('LinDaiDai');
@前端进阶之旅: 代码已经复制到剪贴板
  1. 函数里的形参arg被后面函数声明的arg给覆盖了, 所以第一个打印出的是函数;
  2. 当执行到var arg = 10的时候, arg又被赋值了10, 所以第二个打印出10.

# 执行上下文栈的变化

先来看看下面两段代码, 在执行结果上是一样的, 那么它们在执行的过程中有什么不同吗?

var scope = "global";
function checkScope () {
	var scope = "local";
	function fn () {
		return scope;
	}
	return fn();
}
checkScope();
@前端进阶之旅: 代码已经复制到剪贴板
var scope = "global"
function checkScope () {
	var scope = "local"
	function fn () {
		return scope
	}
	return fn;
}
checkScope()();
@前端进阶之旅: 代码已经复制到剪贴板

答案是 执行上下文栈的变化不一样。

在第一段代码中, 栈的变化是这样的:

ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
@前端进阶之旅: 代码已经复制到剪贴板

可以看到fn后被推入栈中, 但是先执行了, 所以先被推出栈;


而在第二段中, 栈的变化为:

ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
@前端进阶之旅: 代码已经复制到剪贴板

由于checkscope是先推入栈中且先执行的, 所以在fn被执行前就被推出了.

# VO/AO

接下来要介绍两个概念:

  • VO(变量对象), 也就是variable object, 创建执行上下文时与之关联的会有一个变量对象,该上下文中的所有变量和函数全都保存在这个对象中。

  • AO(活动对象), 也就是``activation object`,进入到一个执行上下文时,此执行上下文中的变量和函数都可以被访问到,可以理解为被激活了。

活动对象和变量对象的区别在于:

  • 变量对象(VO)是规范上或者是JS引擎上实现的,并不能在JS环境中直接访问。
  • 当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。

上面似乎说的比较难理解😢, 没关系, 我们慢慢来看.

# 执行过程

首先来看看一个**执行上下文(EC)**被创建和执行的过程:

  1. 创建阶段:
  • 创建变量、参数、函数arguments对象;

  • 建立作用域链;

  • 确定this的值.

  1. 执行阶段:

变量赋值, 函数引用, 执行代码.

# 进入执行上下文

在创建阶段, 也就是还没有执行代码之前

此时的变量对象包括(如下顺序初始化):

  1. 函数的所有形参(仅在函数上下文): 没有实参, 属性值为undefined;
  2. 函数声明:如果变量对象已经存在相同名称的属性,则完全替换这个属性;
  3. 变量声明:如果变量名称跟已经声明的形参或函数相同,则变量声明不会干扰已经存在的这类属性

一起来看下面的例子🌰:

function fn (a) {
	var b = 2;
	function c () {};
	var d = function {};
	b = 20
}
fn(1)
@前端进阶之旅: 代码已经复制到剪贴板

对于上面的例子, 此时的AO是:

AO = {
	arguments: {
		0: 1,
		length: 1
	},
	a: 1,
	b: undefined,
	c: reference to function c() {},
	d: undefined
}
@前端进阶之旅: 代码已经复制到剪贴板

可以看到, 形参arguments此时已经有赋值了, 但是变量还是undefined.

# 代码执行

到了代码执行时, 会修改变量对象的值, 执行完后AO如下:

AO = {
	arguments: {
		0: 1,
		length: 1
	},
	a: 1,
	b: 20,
	c: reference to function c() {},
	d: reference to function d() {}
}
@前端进阶之旅: 代码已经复制到剪贴板

在此阶段, 前面的变量对象中的值就会被赋值了, 此时变量对象处于激活状态.

# 总结

  • 全局上下文的变量对象初始化是全局对象, 而函数上下文的变量对象初始化只有Arguments对象;

  • EC创建阶段分为创建阶段和代码执行阶段;

  • 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值;

  • 在代码执行阶段,会再次修改变量对象的属性值.

← JS中按位取反运算符及其它运算符你真的能完全运用好防抖节流吗 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合