执行上下文(EC)
当js代码执行的时候,都会创建一个执行上下文,明确执行上下文之前,先明确什么是可执行代码?
可执行代码
可执行的代码分为 全局代码 函数代码 eval代码
执行环境栈(ECS)
js代码是一个单线程代码,其他的行为或者事情,只能放在执行栈里面去排队,当浏览器首次载入脚本的时候,会默认代码进入全局的执行上下文,例如你在全局代码里面,调用了一个函数,这时候就会为这个函数创建一个上下文,并把这个上下文对象,压入到栈的最上层,如果你执行的函数内部,又出现了调用其他的函数的时候,上面的事情会继续重复,当函数执行完之后,这个执行上下文,会从栈里弹出,并把上下文交给当前的栈顶的上下文,直到回到全局的上下文。
归纳一下
- 单线程
- 唯一一个全局上下文
- 上下文没有个数限制
- 同步执行
- 每次执行一个函数,都会创建一个新的上下文
- 每次执行的代码 都指向栈顶的上下文
执行上下文建立的细节
创建阶段
-
创建作用域链
-
创建变量 函数和参数
-
a.创建arguments对象,检查上下文,初始化参数名称和值并创建引用的复制。
-
b.扫描上下文的函数声明(而非函数表达式): 1、为发现的每一个函数,在变量对象上创建一个属性,确切的说是函数的名字,其有一个指向函数在内存中的引用。 2、如果函数的名字已经存在,引用指针将被重写。
-
c.扫描上下文的变量声明: 1、为发现的每个变量声明,在变量对象上创建一个属性,就是变量的名字,并且将变量的值初始化为undefined 2、如果变量的名字已经在变量对象里存在,将不会进行任何操作并继续扫描。
-
求this的值
-
函数的形参(当进入函数执行上下文时) —— 变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值;对于没有传递的参数,其值为undefined
-
函数声明(FunctionDeclaration, FD) —— 变量对象的一个属性,其属性名和值都是函数对象创建出来的;如果变量对象已经包含了相同名字的属性,则替换它的值
-
变量声明(var,VariableDeclaration) —— 变量对象的一个属性,其属性名即为变量名,其值为undefined;如果变量名和已经声明的函数名或者函数的参数名相同,则不会影响已经存在的属性。
具体的举例说明:
function foo2(a){
console.log(a)
var a = 10
}
foo2(20) //'20'
//情景二:与函数名相同
function foo2(){
console.log(a)
var a = 10
function a(){}
}
foo2() //'function a(){}'
在创建阶段,只是对形参的属性进行命名,而不为他们赋值。
console.log(typeof foo); // 函数指针
console.log(typeof bar); // undefined
var foo = 'hello',
bar = function() {
return 'world';
};
function foo() {
return 'hello';
}
}());
为什么我们可以在变量声明之前使用了
就是因为在创建上下文的时候,就已经做好了变量的定义命名了
同样一个函数被声明了两次为什么是函数而不是变量
在创建阶段,函数声明是优先于变量被创建的。而且在变量的创建过程中,如果发现VO中已经存在相同名称的属性,则不会影响已经存在的属性。因此,对foo()函数的引用首先被创建在活动对象里,并且当我们解释到var foo时,我们看见foo属性名已经存在,所以代码什么都不做并继续执行
为什么bar是undefined
bar采用的是函数表达式的方式来定义的,所以bar实际上是一个变量,但变量的值是函数,并且我们知道变量在创建阶段被创建但他们被初始化为undefined,这也是为什么函数表达式不会被提升的原因。
执行阶段:
- 初始化变量的值跟函数的引用
- 执行代码
js闭包是什么?
闭包就是在一个函数里,返回了一个函数,这个函数里使用了外层声明的变量。
闭包的应用
- 声明api 而不暴露内部的参数
- 节流 防抖