JavaScript整个执行过程,分为两个阶段,代码编译阶段和代码执行阶段
- 一、编译阶段
编译阶段由编译器执行,将代码翻译成可执行代码,这个阶段确定作用域规则。
变量的预编译只作声明,不作初始化,初始化在执行时
function语句定义的函数,不仅声明了函数名,而且函数体也进行了处理
二、执行阶段
执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。执行上下文也分为创建阶段和执行阶段。
1、执行上下文
执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。
2、三种类型
a、全局执行上下文:浏览器的全局对象是window对象,this指向这个全局对象
b、函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文,函数上下文的变量对象初始化只包括 Arguments 对象。
c、Eval函数执行上下文:指运行在eval函数的代码,很少用而且不建议使用。。
创建阶段:
确定this的值,也被成为 This Binding;
This Binding
a、全局执行上下文中,this 指向全局对象,在浏览器中this 指向window对象,在nodejs中指向这个文件的module 对象。
b、函数执行上下文中,this的值取决于函数的调用方式。具体有:默认绑定、隐式绑定、显式绑定、new绑定、箭头函数。
例一:变量提升
foo(); // 报错
var foo = function () {
console.log('foo1');
}
foo(); // foo1,foo重新赋值
var foo = function () {
console.log('foo2');
}
foo(); // foo2,foo重新赋值
复制代码
例二:函数提升
foo(); // foo2
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
复制代码
例三:声明优先级,函数 > 变量
foo(); // foo2
var foo = function() {
console.log('foo1');
}
foo(); // foo1,foo重新赋值
function foo() {
console.log('foo2');
}
foo(); // foo1
复制代码
需要注意的是同一作用域下存在多个同名函数声明,后面的会替换前面的函数声明。
三、执行上下文栈
因为JS引擎创建了很多的执行上下文,所以JS引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文。
当 JavaScript 初始化的时候会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,执行栈才会被清空,所以程序结束之前, 执行栈最底部永远有个 globalContext。
ECStack = [ // 使用数组模拟栈
globalContext
];复制代码
当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。
var scope = "global scope";
function checkscope(){
var scope = "local scope1";
function f(){
console.log(scope);
}
return f(); //执行
}
checkscope();
var scope = "global scope";
function checkscope(){
var scope = "local scope22";
function f(){
console.log(scope);
}
return f; //返回function
}
checkscope()();复制代码
第一段代码:
ECStack.push(<checkscope> functionContext);//执行checkscope
ECStack.push(<f> functionContext);//执行f
ECStack.pop();//弹出f
ECStack.pop();//弹窗checkscope复制代码
第二段代码:
ECStack.push(<checkscope> functionContext);//执行checkscope
ECStack.pop();//返回function
ECStack.push(<f> functionContext);//执行f
ECStack.pop();//返回f复制代码