1>什么是执行上下文javascript
Javascript中代码的运行环境分为如下三种:
全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最早进入的就是这个环境。
函数级别的代码 - 当执行一个函数时,运行函数体中的代码。
Eval的代码 - 在Eval函数内运行的代码。
javascript是一个单线程语言,这意味着在浏览器中同时只能作一件事情。当javascript解释器初始执行代码,它首先默认进入全局上下文。每次调用一个函数将会建立一个新的执行上下文。
每次新建立的一个执行上下文会被添加到做用域链的顶部,有时也称为执行或调用栈。浏览器老是运行位于做用域链顶部的当前执行上下文。一旦完成,当前执行上下文将从栈顶被移除而且将控制权归还给以前的执行上下文。java
不一样执行上下文之间的变量命名冲突经过攀爬做用域链解决,从局部直到全局。这意味着具备相同名称的局部变量在做用域链中有更高的优先级。
简单的说,每次你试图访问函数执行上下文中的变量时,查找进程老是从本身的变量对象开始。若是在本身的变量对象中没发现要查找的变量,继续搜索做用域链。它将攀爬做用域链检查每个执行上下文的变量对象,寻找和变量名称匹配的值。浏览器
2>执行上下文的创建过程函数
咱们如今已经知道,每当调用一个函数时,一个新的执行上下文就会被建立出来。然而,在javascript引擎内部,这个上下文的建立过程具体分为两个阶段:
创建阶段(发生在当调用一个函数时,可是在执行函数体内的具体代码之前)
创建变量,函数,arguments对象,参数
创建做用域链
肯定this的值
代码执行阶段:
变量赋值,函数引用,执行其它代码
实际上,能够把执行上下文看作一个对象,其下包含了以上3个属性:this
executionContextObj = { variableObject: { /* 函数中的arguments对象, 参数,
内部的变量以及函数声明 / }, scopeChain: { / variableObject
以及全部父执行上下文中的variableObject */ }, this: {} }线程
3>创建阶段以及代码执行阶段的详细分析
确 切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,可是在函数体被真正执行之前所建立的。函数被调用时,就是我 上述所描述的两个阶段中的第一个阶段 - 创建阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,而后基于这些信息创建执行上下文对象 (executionContextObj)。在这个阶段,variableObject对象,做用域链,以及this所指向的对象都会被肯定。
上述第一个阶段的具体过程以下:code
1.找到当前上下文中的调用函数的代码 2.在执行被调用的函数体中的代码之前,开始建立执行上下文 3.进入第一个阶段-创建阶段: 创建variableObject对象: 创建arguments对象,检查当前上下文中的参数,创建该对象下的属性以及属性值 检查当前上下文中的函数声明: 每找到一个函数声明,就在variableObject下面用函数名创建一个属性,属性值就是指向该函数在内存中的地址的一个引用 若是上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。 检查当前上下文中的变量声明: 每找到一个变量的声明,就在variableObject下,用变量名创建一个属性,属性值为undefined。 若是该变量名已经存在于variableObject属性中,直接跳过(防止指向函数的属性的值被变量属性覆盖为undefined),原属性值不会被修改。 初始化做用域链 肯定上下文中this的指向对象 4.代码执行阶段: 执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。 下面来看个具体的代码示例: function foo(i) { var a = 'hello'; var b = function privateB() { }; function c() { } } foo(22); 在调用foo(22)的时候,创建阶段以下: fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, scopeChain: { ... }, this: { ... } } 因而可知,在创建阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。一旦上述创建阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象以下: fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: 'hello', b: pointer to function privateB() }, scopeChain: { ... }, this: { ... } } 咱们看到,只有在代码执行阶段,变量属性才会被赋予具体的值。
4>局部变量做用域提高的原因
在网上一直看到这样的总结: 在函数中声明的变量以及函数,其做用域提高到函数顶部,换句话说,就是一进入函数体,就能够访问到其中声明的变量以及函数。这是对的,可是知道其中的原因吗?相信你经过上述的解释应该也有所明白了。不过在这边再分析一下。看下面一段代码:对象
function() { console.log(typeof foo); // function pointer console.log(typeof bar); // undefined var foo = 'hello', bar = function() { return 'world'; }; function foo() { return 'hello'; } }()); 上述代码定义了一个匿名函数,而且经过()运算符强制理解执行。那么咱们知道这个时候就会有个执行上下文被建立,咱们看到例子中立刻能够访问foo以及bar变量,而且经过typeof输出foo为一个函数引用,bar为undefined。 为何咱们能够在声明foo变量之前就能够访问到foo呢?
因 为在上下文的创建阶段,先是处理arguments, 参数,接着是函数的声明,最后是变量的声明。那么,发现foo函数的声明后,就会在variableObject下面创建一个foo属性,其值是一个指向 函数的引用。当处理变量声明的时候,发现有var foo的声明,可是variableObject已经具备了foo属性,因此直接跳过。当进入代码执行阶段的时候,就能够经过访问到foo属性了,由于它 已经就存在,而且是一个函数引用。进程
为何bar是undefined呢?
由于bar是变量的声明,在创建阶段的时候,被赋予的默认的值为undefined。因为它只要在代码执行阶段才会被赋予具体的值,因此,当调用typeof(bar)的时候输出的值为undefined。ip