Javascript 执行上下文 context&scope
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Javascript 执行上下文 context&scope相关的知识,希望对你有一定的参考价值。
执行上下文(Execution context)
执行上下文可以认为是 代码的执行环境。
1 当代码被载入的时候,js解释器 创建一个 全局的执行上下文。
2 当执行函数时,会创建一个 函数的执行上下文。
3 当执行 eval()的时候,创建 一个 eval 执行上下文。
# if,for,while 等块不会创建 execution context,从而不会创建 scope。
当js解释器开始工作的时候:
1 首先创建一个 执行上下文栈(后进先出)
2 接着创建一个 全局的执行上下文,并放入执行上下文栈中。
3 当在全局上下文中调用一个函数的时候.
为函数创建一个 执行上下文,
压入执行上下文栈中。
4 当函数执行结束的时候,位于栈顶的 执行上下文 被弹出。继续执行 新的位于栈顶的执行上下文。
这种方式保证了 任意时刻都只有位于栈顶的执行上下文才会被执行,也就是实现了单线程。
执行上下文创建分成两个阶段:
创建阶段: 将所有的变量,函数声明以及函数参数 组成一个活动对象(可变对象)
执行阶段: 解释代码.
创建阶段具体做的事情:
1 创建 活动对象VO(variableObject)
2 创建 arguments对象
检测参数,将参数名作为 key值(arguments对象的所有属性都同时是 VO的属性)
3 检测当前执行上下文中的 函数声明 (function fn(){} 这种叫函数声明)
每检测到一个函数声明都会在 VO 上添加一个属性,属性名就是函数名。属性值就是函数在内存中的引用
(
检测到同名依旧覆盖掉。
)
4 检测变量声明
每检测到一个变量声明,就在VO上面添加一个属性,属性名就是变量名,属性值就是 undefined.
(
检测到同名就丢弃掉当前。
)
#34的执行顺序没有关系的,调换顺序也是同样的结果。只要变量声明遇见函数声明,最后留下来的肯定是函数声明。
5 确定当前的this的指向。
执行阶段:一行一行解释代码.
但是在执行阶段还有一个规律:对于同一个变量的赋值,后面的会覆盖前面的。
假设有函数:
function foo(i) {
var a = ‘hello‘;
var b = function privateB() {
};
function c() {
}
}
VO大概长这个样子:
ExecutionContext = {
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
scopeChain: { ... },
this: { ... }
}
scope chain
js解释器在创建 执行上下文栈 的时候,会同时创建一个 scope chain。一个单向链表.
当 某个执行上下文的创建阶段的最后:创建的活动对象,会被放到 scope chain的 第一个位置。
在执行上下文的 执行阶段的时候,当需要访问某个变量时,
会首先在 当前的 执行上下文的 VO中查找。如果找不到,就往链表的下一个位置查找,一直到最后一个位置。
# 因为链表是单向的,不能反向查找,这也是为什么,全局的执行上下文不能访问 函数上下文的原因,
就是 函数外部访问不了函数内部的原因.
1 触发 scope chain 查找的原因只有一个:执行上下文的 执行阶段,遇见了当前 VO中不存在的属性。
所以:
var a = ‘outer‘;
function test(a){
console.log(a);//(1)
var a = ‘inner‘;
console.log(a);//(2)
}
test();
// output: undefined,‘inner‘
因为参数是 a>VO 中有 属性 a,在 (1)的时候,a 的值是 undefined,因为存在a,所以不会触发 scope chain 查找.
如果想要触发,把参数 a 改成其他名字.
# 这个东西,帮助理解一下原理就好,不要在代码中这么干,出了问题都不知道哪里有问题。
2 这个用来证明scope chain的查找方向。
var x = 10;
function foo() {
alert(x);
}
(function () {
var x = 20;
foo();
})();
3 // 重复的函数声明,后面的会覆盖前面的(函数声明比较霸道一点)
function f(){
console.log(1);
}
f();
function f(){
console.log(2);
}
f();
4 执行阶段的时候的规律,后面的赋值会覆盖前面的同名。
var x = 0;
var f = function(){
x = 1;
}
f();
alert(x);
function f(){
x = 10;
}
f();
alert(x);
// output: 1,1
var b = 10;
(function b(){
b = 20;
console.log(b);
})();
// output: function b(){...}
原因:
1 首先进入 全局执行上下文,注册了 b
2 全局执行上下文执行 b = 10
3 进入()();创建的执行上下文,注册了 函数声明 b
4 执行-进入 函数执行上下文
5 注册了b是全局的。
6 输出:
在函数中找不到,在表达式中找到了,就是 function b(){}
如果是:
var b = 10;
(function b(){
var b = 20;
console.log(b);
})();
// output:20
如果是:
var b = 10;
(function(){
b = 20;
console.log(b);
})();
// output:20
IIFE和 全局的执行上下文之间还有一层执行上下文,这样才能解释上面的的问题。
函数表达式&函数声明
var fn = function(){} // 函数表达式
function fn(){} // 函数声明
记忆:
vb:使用 var 的 都是 biao 达式
区别:
函数声明必须要有函数名,只有函数声明才有 hoisting.
javascript hositing
变量和函数声明总是会被提升到当前作用域的顶部。
原因:
执行上下文的 创建阶段,创建VO对象的时候就已经检测过所有的 变量声明和函数声明了。
所以这个时候 它们已经存在了。
只不过 变量声明的值始终是undefined.而函数声明的值是一个函数在内存地址中的引用.
以上是关于Javascript 执行上下文 context&scope的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript学习--Item19 执行上下文(execution context)
深入理解Javascript之执行上下文(Execution Context)
JavaScript内部原理系列-执行上下文(Execution Context)