预编译
- 首先下面这段代码的执行是一个怎样的结果呢?
showName();
console.log(MyName);
var MyName = '小陈同学'
function showName() {
console.log('函数showName被执行');
}
在这段代码中我们声明了一个变量MyName和一个函数showName,调用函数,打印MyName
因为在函数编译的过程中会存在变量的声明提示,默认赋值为undefined,方法的整体提升
所以这段代码又可以看做成以下代码
var MyName = undefined
function showName() {
console.log('函数showName被执行');
showName();
console.log(MyName);
MyName = '小陈同学'
}
所以执行结果显而易见

接下来我们再细致的分析一下
在编译过程中,首先会创建一个上下文对象

当执行showName时,又会创建一个showName的执行上下文,这里就形成了一个调用栈
调用栈
调用栈是js引擎用来维护函数调用关系的一个数据结构
这段代码会出现什么结果?
function fn(){
fn()
}
fn()
答案是

超出了最大的栈内存调用大小,发生了栈溢出,这里我们可以知道,每当一个函数调用的时候就会发生栈帧的创建,以及压栈操作
这里我们明白了这个道理后,我们继续分析一下代码
var a = 2
function add(b, c) {
return b + c
}
function addAll(b, c) {
var d = 10
var result = add(b, c)
return a + result + d
}
addAll(3, 6)
首先会创建全局上下文

第二步会创建addAll的执行上下文

第三步会创建add的执行上下文

这便是这段代码在引擎里面维护的一个执行上下文调用栈
前面一直没有用到词法变量,现在我们要引入词法变量了,来分析一下下面的代码
function foo() {
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a);
console.log(b);
}
console.log(b);
console.log(c);
console.log(d);
}
foo()
首先会创建一个全局的上下文,这里包括了fanction foo,比较简单
接下会创建foo的执行全局上下文,这里我们重点分析一下
首先会进行一个预编译

预编译执行后开始执行代码
- 在函数内,定义了变量
a为 1(使用var),b为 2(使用let)。 - 然后在一个代码块中,又重新定义了
let b = 3,此时在这个代码块内的b是 3 ,而不是外面的 2 ;同时定义了var c = 4和let d = 5。 - 在代码块内打印
a为 1 ,打印b为 3 。 - 之后在函数内再次打印
b为 2 (因为外面的b没有被修改)。 - 由于
c是在代码块内用var定义的,所以在函数内可以访问到,打印c为 4 。 - 但是
d是在代码块内用let定义的,在代码块外无法访问,会报错说d未定义。
那么这里我提一个问题
代码块中的console.log(b);为什么打印的是3而不是2?
你肯定会说,因为你使用了{}啊,这只是表层
其实是因为,在查找变量的时候,首先会去词法环境查找数据,然后再去变量环境查找,并且在词法环境里面维护了一个栈的结构,从上往下找

这里就出来了另一个问题,既然是首先会去词法环境查找数据,然后再去变量环境查找,那为什么

这个打印的b不是3呢?
这里面就是作用域链在起作用了
- 作用域链
这里举个代码例子,分析一下这段代码执行的结果
function bar(params) {
console.log(myName);
}
function foo(params) {
var myName = 'Tom'
bar();
}
var myName = 'Jerry';
foo();
结果为

为什么结果是Jerry呢?
不是在foo里面var myName = 'Tom’吗?
首先我们分析一下这段代码的调用栈

-
首先,程序开始执行,遇到
foo函数的调用,进入foo函数,此时调用栈中添加了foo -
在
foo函数内部,定义了变量myName为'Tom',然后调用bar函数,此时调用栈中添加了bar在foo之上 -
进入
bar函数后,它尝试打印myName,由于bar函数内部没有定义myName,它会沿着作用域链向上查找。首先在bar自身的作用域内未找到,然后到foo函数的作用域内也未找到(尽管foo内部有定义,但不是同一个作用域),最后到全局作用域中找到了定义的myName为'Jerry',所以打印出'Jerry' -
当
bar函数执行完毕后,从调用栈中弹出bar,回到foo函数继续执行,直到foo函数执行完毕,再从调用栈中弹出foo
其实在每个上下文中都有一个outer属性,他通过去关联上一个作用域,来形成作用域链

那么它是一局什么规则呢?
词法作用域在哪里,outer就指向哪里,词法作用域的意思是在函数定义时所在的作用域
这也就是为什么打印的是Jerry,因为bar定义在全局,所以会去全局找myName
那么接下来这段代码呢?
function foo(params) {
var myName = 'Tom'
function bar(params) {
console.log(myName);
}
bar();
}
var myName = 'Jerry';
foo();
- 首先,执行
foo函数。在foo函数内部定义了变量myName为'Tom',然后定义了内部函数bar。 - 当调用
bar函数时,它要打印myName。由于bar函数内部没有定义myName,它会沿着作用域链查找。 - 首先在
bar自身的局部作用域中找不到,然后会在其直接外层,也就是foo函数的作用域中找到myName为'Tom',所以最终会打印出'Tom'。而全局定义的myName为'Jerry',在这里并不会被bar函数访问到
总结
本文深入探讨JavaScript的执行机制,从预编译,引擎的调用栈,作用域链等方面分析,相信看到这里你一定对JavaScript的执行机制有了更加深刻的理解



















