上一篇咱们提到的变量会登记在一个叫Lexical Environments(词法环境)上面。咱们说了Lexical Environments(词法环境)的组成部分,咱们也知道GlobalEnvironment是一个Lexical Environments(词法环境),它的outer为null,它的EnvironmentRecord和global相关,复习一下:javascript
//Lexical Environment
function LexicalEnvironment() {
this.EnvironmentRecord = undefined;
this.outer = undefined; //outer Environment Reference
}
function EnvironmentRecord(obj) {
if(isObject(obj)) {
this.bindings = object;
this.type = 'Object';
}
this.bindings = new Map();
this.type = 'Declarative';
}
EnvironmentRecord.prototype.register = function(name) {
if (this.type === 'Declarative')
this.bindings.set(name,undefined)
this.bindings[name] = undefined;
}
EnvironmentRecord.prototype.initialize = function(name,value) {
if (this.type === 'Declarative')
this.bindings.set(name,value);
this.bindings[name] = value;
}
EnvironmentRecord.prototype.getValue = function(name) {
if (this.type === 'Declarative')
return this.bindings.get(name);
return this.bindings[name];
}
//全局环境GlobalEnvironment
function creatGlobalEnvironment(globalobject) {
var globalEnvironment = new LexicalEnvironment();
globalEnvironment.outer = null
globalEnvironment.EnvironmentRecord = new EnvironmentRecord(globalobject)
return globalEnvironment;
}
GlobalEnvironment = creatGlobalEnvironment(globalobject)//能够看做是浏览器环境下的window
复制代码
咱们上一篇最后提出了一个问题:java
词法环境(Lexical Environments),是用来登记变量和相关函数名字的,也知道这个名字是登记在 词法环境的 >EnvironmentRecord上的。那时候登记,怎么登记?是直接找老师(LexicalEnvironments)登记,仍是设置一个 办公厅,办公厅设置登记窗口提供登记服务?浏览器
答案是JS引擎咱们学校同样,设置了一个办公厅,老师(Lexical Environments)坐在办公厅了面,手里拿着登记簿(EnvironmentRecord上的),等别人来办理注册。bash
JS的这个办公厅叫运行上下文(Execution Contexts),并且还有两个办事窗口,这个两个窗口还分别有个名字:LexicalEnvironment,VariableEnvironment。看起来有点像:函数
运行上下文(Execution Contexts)有三个组成部分ui
用伪代码表示看起来像这样的:this
function ExecutionContext() {
this.LexicalEnvironment = undefined;
this.VariableEnvironment = undefined;
this.ThisBinding = undefined;
}
复制代码
这边须要说明一下,运行上下文(Execution Contexts)中的LexicalEnvironment是名称,它的值是一个Lexical Environment(词法环境),不要搞混。 小伙伴们可能会疑惑,LexicalEnvironment 和VariableEnvironment 通常会初始化为同一个词法环境,那要两个干嘛呢?LexicalEnvironment使用过程有时候会被替换,而VariableEnvironment不会,后面会提到使用场景。spa
这个“办公厅”(Execution Contexts)就是javascript代码的"办公环境"的组成部分。和学校同样,并非全部班级都设置一个办公室,javascript也不是运行任意的代码是都要建立一个Execution Contexts。在javascript中有三种状况下,会建立Execution Contexts。prototype
ECMAScript 5 规范,定义了三类可运行代码(Executable Code) ,运行这些代码时候会建立运行上下文(Execution Contexts):code
那么问题又来了,除了global code,每次运行function code,eval code ,若是咱们写的函数多,那运行上下文也就多了,如何管理建立的那么多执行上下文呢?
其实呀,运行上下文(Execution Contexts)建立出来会被放在一个叫运行栈结构里,也叫调用栈。每当代码执行进入global code、function code、eval code,就会建立一个运行上下文(Execution Contexts),并把它放到调用栈的顶部(PUSH,入栈),调用栈顶部的运行上下文(Execution Contexts)称为运行时运行上下(runing Execution Contexts),或叫作当前运行上下文(current Execution Contexts),当运行时运行上下(runing Execution Contexts)对应代码运行完毕后,它就会被从调用栈顶拿掉(POP,出栈)。
以前,你们可能都知道js的调用栈,可是调用栈存的是什么东西可能不清楚,其实就是运行上下文(Execution Contexts)。
到这里,咱们就能够构造一个JS运行的基本环境,js代码运行基于运行栈,它变量的存取都是从运行栈上的Execution Contexts来登记、获取的。咱们用一个伪代码来模拟一个JS的运行环境:
Runtime = {
executionContextStack: [];
};
//获取当前的运行上下文,也就是运行栈,栈顶的运行上下文
Runtime.getRunningExecutionContext = function() {
return this.executionContextStack[this.executionContextStack.length - 1];
}
//把运行栈站定的运行上下文弹出
Runtime.pop = function() {
this.executionContextStack.pop();
}
//把一个运行上下文放到运行栈的栈顶
Runtime.push = function(newContext) {
this.executionContextStack.push(newContext);
}
//在当前运行上下文登记一个变量信息
Runtime.register = function(name) {
var env = this.getRunningExecutionContext().VariableEnvironment;
env.EnvironmentRecord.register(name);
}
//在当前运行上下文初始化一个变量信息
Runtime.initialize = function(name,value) {
var env = this.getRunningExecutionContext().VariableEnvironment;
env.EnvironmentRecord.initialize(name,value);
}
//在当前运行上下文上,解析一个标识符
Runtime.getIdentifierVaule = function (name) {
var env = this.getRunningExecutionContext().LexicalEnvironment;
while(env){
var envRec = env.EnvironmentRecord;
var exists = envRec.isExist(name);
if(exists) return envRec.getValue(name);
env = env.outer;
}
}
复制代码
以一段代码为例,来讲明当JS引擎开始执行你的代码时,会干哪些事情。
console.log(a);
var a = 4;
print();
function print(){
console.log(a)
}
复制代码
当JS引擎开始要进行global code代码运行以前,会先建立一个全局运行上下文(global execution context),并放入运行栈中:
//建立一个空的运行上下文
var globalExecutionContext = new ExecutionContext();
//建立全局词法环境
GlobalEnvironment = creatGlobalEnvironment(globalobject)//能够看做是浏览器环境下的window
//设置运行上下文
globalExecutionContext.LexicalEnvironment = GlobalEnvironment;
globalExecutionContext.VariableEnvironment = GlobalEnvironment;
globalExecutionContext.ThisBinding = globalobject;
Runtime.push(globalExecutionContext);
//这时的Runtime是这样的:
Runtime = {
executionContextStack: [globalExecutionContext];
};
复制代码
此时的当前运行上下文为globalExecutionContext。这个时候看起来像这样:
基本运行环境初始化完,而后开始解析代码,找出var声明和函数声明,并登记到globalExecutionContext.VariableEnvironment:
找到var a ,登记并初始化为undefined:
执行Runtime.register('a') ;等于下面的操做:
//获取当前运行上下文
var runningExecutionContext = Runtime.getRunningExecutionContext();//runningExecutionContext是globalExecutionContext
//获取当前运行上下文的词法环境,//variableEnvironment是globalEnviroment
var variableEnvironment = runningExecutionContext.VariableEnvironment
//在词法环境上登记'a'
variableEnvironment.EnvironmentRecord.register('a');
复制代码
找到函数声明,建立函数对象fo,并登记到globalExecutionContext.VariableEnvironment: function print()...:
fo = FunctionCreate(...)//函数建立下篇讲
Runtime.initialize('print',fo) ;
等于:
variableEnvironment.EnvironmentRecord.initialize('print',fo);
复制代码
有没发现,如今代码还没执行,可是环境中已经有a和print的记录了并且函数print记录的值是一个实实在在的函数对象不是undefined,这就是javascript的变量提高时,变量的值是undefined,而函数却不是undefined,而是可运行的。这时整个环境看起来是这样的:
开始代码执行
执行console.log(a):发现有对a的引用,就是要a进行解析.:
//其实就是variableEnvironment.EnvironmentRecord.getValue('a') ;
var aValue = Runtime.getIdentifierVaule('a'):
//这时,aValue为undefined
打印出undefined
复制代码
执行var a = 4:
Runtime.initialize('a',4);//variableEnvironment.EnvironmentRecord.initialize('a',4);
复制代码
执行print(),发现print引用,就是要print进行解析:
var fun = Runtime.getIdentifierVaule('print') //variableEnvironment.EnvironmentRecord.getValue('print') ;
//获得一个函数对象,运行该函数
复制代码
函数运行的细节留在下一篇说明。
执行完毕,退出当前运行上下文,把globalExecutionContext从调用栈上移除:
Runtime.pop();
//这时的Runtime,为空
Runtime = {
executionContextStack: [];
};
复制代码
到目前为止,咱们还没详细涉及函数,只知道函数是词法环境上登记的时候是立刻初始化为具体函数对象的,但没谈及函数是如何被建立以及如何运行function里的代码。
下一篇咱们将开始讨论这些内容。
总结上述,过程咱们构建一个JS的运行模型,进入可执行代码,都会走这个运行模型:
可运行代码(Executable Code)
ECMAScript 5 规范,定义了三类可运行代码(Executable Code) ,运行这些代码时候会建立运行上下文(Execution Contexts):
- global code:就是js整个“程序”,就是源代码文件中全部不在function体中的代码。
- function code:就是函数体中的代码,除了内嵌函数体中的代码之外
- eval code : 就是传给内置eval函数的代码字符串
运行代码 = 运行上下文初始化 + var声明和函数声明扫描scan + 执行语句;
复制代码
可运行代码的运行 = 运行上下文初始化 + var声明和函数声明扫描scan + 执行语句;
复制代码
Runtime = {
executionContextStack: [];
};
//获取当前的运行上下文,也就是运行栈,栈顶的运行上下文
Runtime.getRunningExecutionContext = function() {
return this.executionContextStack[this.executionContextStack.length - 1];
}
//把运行栈站定的运行上下文弹出
Runtime.pop = function() {
this.executionContextStack.pop();
}
//把一个运行上下文放到运行栈的栈顶
Runtime.push = function(newContext) {
this.executionContextStack.push(newContext);
}
//在当前运行上下文登记一个变量信息
Runtime.register = function(name) {
var env = this.getRunningExecutionContext().VariableEnvironment;
env.EnvironmentRecord.register(name);
}
//在当前运行上下文初始化一个变量信息
Runtime.initialize = function(name,value) {
var env = this.getRunningExecutionContext().VariableEnvironment;
env.EnvironmentRecord.initialize(name,value);
}
//在当前运行上下文上,解析一个标识符
Runtime.getIdentifierVaule = function (name) {
var env = this.getRunningExecutionContext().LexicalEnvironment;
while(env){
var envRec = env.EnvironmentRecord;
var exists = envRec.isExist(name);
if(exists) return envRec.getValue(name);
env = env.outer;
}
}
function ExecutionContext() {
this.LexicalEnvironment = undefined;
this.VariableEnvironment = undefined;
this.ThisBinding = undefined;
}
function LexicalEnvironment() {
this.EnvironmentRecord = undefined;
this.outerEnvironmentReference = undefined;
}
function EnvironmentRecord(obj) {
if(isObject(obj)) {
this.bindings = object;
this.type = 'Object';
}
this.bindings = new Map();
this.type = 'Declarative';
}
EnvironmentRecord.prototype.register = function(name) {
if (this.type === 'Declarative')
this.bindings.set(name,undefined)
this.bindings[name] = undefined;
}
EnvironmentRecord.prototype.initialize = function(name,value) {
if (this.type === 'Declarative')
this.bindings.set(name,value);
this.bindings[name] = value;
}
EnvironmentRecord.prototype.getValue = function(name) {
if (this.type === 'Declarative')
return this.bindings.get(name);
return this.bindings[name];
}
function creatGlobalEnvironment(globalobject) {
var globalEnvironment = new LexicalEnvironment();
globalEnvironment.outer = null
globalEnvironment.EnvironmentRecord = new EnvironmentRecord(globalobject)
return globalEnvironment;
}
GlobalEnvironment = creatGlobalEnvironment(globalobject)//能够看做是浏览器环境下的window
复制代码