Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JavaScript] 执行上下文 Execution Context #3

Open
andy2046 opened this issue Feb 10, 2018 · 0 comments
Open

[JavaScript] 执行上下文 Execution Context #3

andy2046 opened this issue Feb 10, 2018 · 0 comments

Comments

@andy2046
Copy link
Owner

andy2046 commented Feb 10, 2018

介绍

本文中,我们将分析ECMAScript的执行上下文以及相关的可执行代码的类型

先举个栗子🌰,为什么a和b表现大不相同,当引用一个函数或者变量时,解释器是如何以及从哪里找到它们的呢

b(); // Called b
console.log(a); // undefined

var a = "Hello World!";
function b() {
  console.log("Called b");
}

定义

每当控制器执行到ECMAScript可执行代码时,控制器就进入执行上下文

执行上下文(缩写:EC)是ECMA-262规范用于区分可执行代码的抽象概念

该标准并没有对EC的结构和种类做出准确的定义

逻辑上,一系列活动的执行上下文形成一个堆栈,这个堆栈的底部总是一个全局上下文,顶部是当前活动的执行上下文,在不同的执行上下文间切换时(进入和退出各种EC),堆栈被修改(通过压栈或者退栈)

可执行代码的类型

可执行代码类型与执行上下文是相关的,说到代码类型,就是指执行上下文

例如,我们将执行上下文的堆栈定义为一个数组:

ECStack = [ ];

每次控制器进入一个函数(即使该函数被递归调用或作为构造函数),都会发生压栈的操作,内置eval函数也不例外

全局代码

这种类型的代码在程序级中处理,即加载外部.js文件或本地内联代码(在<script></script>标签内),全局代码不包含函数体内的任何代码

在初始化(程序启动)时,ECStack如下:

ECStack = [
    globalContext
];

函数代码

控制器进入函数代码(各类函数)时,会有新的元素会被压栈到ECStack,要注意的是,实体函数代码并不包括内部函数的代码

例如,我们来看看递归调用一次的函数:

(function foo(flag) {
  if (flag) {
    return;
  }
  foo(true);
})(false);

之后,ECStack就被修改成:

// first activation of foo
ECStack = [
  <foo> functionContext
  globalContext
];

// recursive activation of foo
ECStack = [
  <foo> functionContext – recursively 
  <foo> functionContext
  globalContext
];

每次函数返回,退出当前活动的执行上下文时,ECStack就会被执行对应的退栈操作,先进后出,和传统的栈实现一致,这些代码执行完之后,ECStack中就只剩下一个执行上下文(globalContext),直到程序结束

当抛出未捕获的异常时也可能会退出一个或多个执行上下文:

(function foo() {
  (function bar() {
    throw 'Exit from bar and foo contexts';
  })();
})();

Eval代码

在这种情况下,存在一个调用上下文的概念,比如调用eval函数时的上下文

eval函数中所做的操作(如变量或函数定义)影响整个调用上下文:

// influence global context
eval('var x = 10');
(function foo() {
  // and here, variable "y" is
  // created in the local context
  // of "foo" function
  eval('var y = 20');
})();
alert(x); // 10
alert(y); // "y" is not defined

对于上面的示例,ECStack被修改为:

ECStack = [
  globalContext
];
  
// eval('var x = 10');
ECStack.push({
  context: evalContext,
  callingContext: globalContext
});
 
// eval exited context
ECStack.pop();
 
// foo funciton call
ECStack.push(<foo> functionContext);
 
// eval('var y = 20');
ECStack.push({
  context: evalContext,
  callingContext: <foo> functionContext
});
 
// return from eval 
ECStack.pop();
 
// return from foo
ECStack.pop();

在旧版SpiderMonkey(版本1.7及以前)实现中,可以将调用上下文作为eval函数的第二个参数传递,因此,如果上下文仍然存在,就有可能影响私有变量:

function foo() {
  var x = 1;
  return function () { alert(x); };
};
var bar = foo();
bar(); // 1
eval('x = 2', bar); // pass context, influence internal var "x"
bar(); // 2

引用

ECMA-262-3 in detail. Chapter 1. Execution Contexts.

Repository owner locked and limited conversation to collaborators Feb 10, 2018
@andy2046 andy2046 changed the title [JavaScript] 聊一聊 执行上下文 [JavaScript] 执行上下文 Execution Context Feb 11, 2018
Repository owner unlocked this conversation Feb 11, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant