在开始今天的内容之前,先看一段代码,下面的代码在同一个 js 文件中定义。代码中的打印结果是什么?
function log() {
console.log(name);
}
function welcome() {
var name = "前端小课";
log();
}
var name = "素燕";
welcome();
我们一起来分析下:
当 JavaScript 代码被执行的时候,首先会创建一个「全局执行上下文」,你可以把执行上下文理解为一段代码要执行时需要准备的环境,它主要包含变量环境、词法环境、this 等。
代码执行时需要经历两个阶段:编译、执行。编译完成后需要创建全局执行上下文。var定义的变量和函数声明会被保存到变量环境中,let、const 定义的变量会保存到词法环境中。
在 var 很傻、let 很亲切 、const 更坚定 这节课程中我们有提到过变量提升,其实变量提升的原理就是把变量的声明提前注入到执行上下文中,上面的代码在执行之前,全局执行上下文如下图所示:
1. name 通过 var 声明,发生变量提升,保存到变量环境中;
2.函数 welcome 和 log 被添加到变量环境中,指向函数的实现,函数的实现会被保存到堆中,此时 welcome 和 log 其实是一个指向函数实现的指针;
可执行的代码,其实是:
name = "素燕";
welcome();
执行 name = “素燕”,给属性 name 赋值。全局执行上下文变为:
然后执行函数 welcome。执行函数的时候会创建被执行函数的上下文,并压入调用栈中。JavaScript 引擎通过调用栈来管理函数的执行,函数发生调用时把函数执行环境入栈,当函数返回的时候(执行结束)进行出栈操作。
执行代码为:
name = "前端小课";
log();
给 name 属性赋值,welcome 函数执行上下文为:
此时的调用栈如下图:
执行 log 函数,创建 log 函数的执行上下文,并压入堆栈,调用栈为:
JavaScript 代码使用的是词法作用域,它是一种静态作用域,和代码的书写有关系,与代码执行无关。也就是说作用域是一成不变的。
function log() {
console.log(name);
}
function welcome() {
var name = "前端小课";
log();
}
var name = "素燕";
welcome();
上面这段代码会存在一个全局作用域,log 函数作用域和 welcome 函数作用域,JavaScript 代码执行的时候,会从当前作用域查找变量,如果未找到会到它的外层作用域中查找。log 函数的外层作用域是全局作用域,故 log 函数的打印值为全局作用域定义的变量。打印结果为“素燕”。
当 log 和 welcome 函数执行完后,它们的执行上下文会依次出栈,并释放它使用的内存空间。全局执行上下文的内存空间会随着页面的生命周期一直保留着。
推荐阅读: