在JavaScript的运行过程中,经常会遇到一些"奇怪"的行为,不理解为什么JavaScript会这么工作。
这时候可能就需要了解一下JavaScript执行过程中的相关内容了。
在JavaScript中有三种代码运行环境:
Global Code
Function Code
Eval Code
为了表示不同的运行环境,JavaScript中有一个执行上下文(Execution context,EC)的概念。也就是说,当JavaScript代码执行的时候,会进入不同的执行上下文,这些执行上下文就构成了一个执行上下文栈(Execution context stack,ECS)。
个人对于执行上下文的理解:
某个函数或全局代码的执行环境,该环境中包含执行代码需要的所有信息。
可以简单地理解为执行上下文是一个对象,对象中包含了执行代码的全局信息。
JS原理
原型链
执行上下文
事件循环
1、栈(Stack)是一种线性存储结构,它具有如下特点:
(1)栈中的数据元素遵守“先进后出"(First In Last Out)的原则,简称FILO结构。
(2)限定只能在栈顶进行插入和删除操作。
2、栈的相关概念:
(1)栈顶与栈底:允许元素插入与删除的一端称为栈顶,另一端称为栈底。
(2)压栈:栈的插入操作,叫做进栈,也称压栈、入栈。
(3)弹栈:栈的删除操作,也叫做出栈。
在此对栈不做过多解释,可以自行查阅资料学习。
变量对象是与执行上下文相关的数据作用域。它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。也就是说,一般VO中会包含以下信息:
当JavaScript代码运行中,如果试图寻找一个变量的时候,就会首先查找VO。对于前面例子中的代码,Global Execution Context中的VO就可以表示如下:
对象引用:如果变量是一个对象,实际上变量中存放的是对象的地址。
对于这张图里面的对象引用可以有这两种说法:
user1持有用户对象的引用
user1指向用户对象
上面说到了执行上下文栈,在这里说明一下执行上下文堆栈
Call Stack(执行上下文堆栈(执行栈)):组织管理程序运行过程中的执行上下文
var a=2;
//函数说明
function bar() {
var i=3;
function foo(){
var b=4;
method();
}
foo();
console.log("123");
}
function method(){
console.log("abc");
}
//函数调用
bar();
上述代码比较简单,这里说明一下函数运行的过程(自底向上看):
全局执行上下文 结束
Bar函数的执行上下文出栈
销毁Console.log函数的执行上下文
Console.log函数的执行上下文
Foo函数的执行上下文出栈
Method函数的执行上下文出栈
销毁Console.log函数的执行上下文
Console.log函数的执行上下文
Method函数的执行上下文
Foo函数的执行上下文
Bar函数的执行上下文
栈底:全局执行上下文
执行上下文的内容
{
vo:...,
scope:...,
this:....
}
VO是一个对象,调用函数或执行全局代码时创建,创建一个VO,需要经过三步
function foo(a, b) {
}
foo(3, 4);
foo函数的执行上下文建立过程:
function foo(a,b){
vo:{
a:3,
b:4,
arguments:{.......}
}
}
2.确定函数中所有的函数字面量声明
function foo(a, b) {
a();
function a() {
console.log("hello");
}
var i = 11;
var j = 22;
var bar = 100;
}
function foo(a,b){
vo:{
a:3,
b:4,
arguments:{.......},
a:指向一个函数,
i:undefined,
j:undefined,
bar:undefined
}
}
一个简单的小demo示例:
function test(a){
console.log(a); //fn
function a(){
console.log("hello");
};
var a = function(){
console.log("haha");
}
a(); //haha
var a = 34;
console.log(a); //34
}
test(5);
当上面的理解之后,对于为什么输出是这个结果,应该没有什么疑问。
下面的文字解释都是以栈的角度入手,阅读时自底向上
在函数中寻找数据时,会先从vo中查找,
vo:{
foo:undefined --> 10
}
输出结果:undefined 10
b的执行上下文入栈:
vo:{
a:指向函数 --> 10 函数结束
}
全局上下文:
vo:{
b:指向函数,
a:undefined --> 1
输出:undefined 1
全局上下文:
vo{
foo:指向C函数 --> “A” -->指向函数B
--> B
}
最终输出:fn(C) A fn(B) B fn(B) B
a的上下文
vo{
}
bar的上下文
vo{
a:3 --> 指向函数 ,
a1: undefined --> 指向函数a,
a:1(全局里面找的1)
}
全局上下文:
vo:{
bar:指向函数bar,
foo:undefined --> 1
}
最终输出结果:1