提醒:执行上下文和作用域是完完全全不同的两个概念,请勿混淆视听,请勿混淆视听,请勿混淆视听。请看下面?
JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段(编译阶段)作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段(执行阶段)创建。不过,作用域链是在上下文生命周期的创建阶段被确定。唔。。。。
何为上下文?简单地说,就是代码在其内部中执行的对象,不是作用域!请看上面?。JavaScript编码中我们通常会使用函数创建一个上下文,函数执行的时候,上下文被激活为执行上下文。
上下文特点
- 类似于栈的读取方式,具有“先进后出,后进先出”的特点。在A上下文执行中激活B上下文,执行上下文变成B进入栈顶,B上下文执行完毕后跳出栈,继续A上下文执行。以此类推。
- 同步执行,栈顶的上下文执行中,其他上下文等待。(理解特点1)
- 上下文执行完毕跳出栈。
- 底层永远是全局上下文,窗口关闭全局上下文跳出栈(这里需要指出全局上下文的变量对象,以浏览器中为例,全局对象为window。它的变量对象,就是window对象。而这个特殊,在this指向上也同样适用,this也是指向window。)
- (按照我现在的理解)在浏览器中,全局上下文的生命周期等同于程序的生命周期,只要程序不结束(关掉窗口),那么全局上下文永远存在,在其他的所有上下文环境中,都可以访问全局上下文的属性。
上下文生命周期
一、创建周期及其流程
- 创建arguments对象,检查上下文参数,并将上下文参数建立为arguments对象下的属性和属性值
- 检查上下文中的函数声明(function关键字申明),如果上下文中不存在该函数的函数名这样一个属性,则在上下文中以函数名建立一个属性,属性值指向该函数所在内存的引用地址,如果上下文中已经存在该函数名这样一个属性,那么该属性的属性值则会被覆盖为新的引用地址(即该函数所在内存的引用地址)。
- 检查上下文中的变量申明,检查到之后,就会在上下文中创建一个属性,属性名就是变量申明的名字,属性值则是 undifinded ,注意:检查到以后,会先判断该属性值是否存在,如果存在,为了以防同名函数被修改为undefined,那么则会被跳过
二、执行阶段
执行阶段就很简单啦,就是变量赋值、函数引用和执行代码咯。
栗子一
function test (){ var a = 'string' function foo (){ return 'this is a foo' }, a; foo()}test() 复制代码
- 创建阶段(创建arguments => 检查函数声明 => 检查变量声明)
variableObject = { arguments:[], foo:,//函数引用地址 a:undefinded}复制代码
- 执行阶段(变量赋值、函数引用、执行代码)
variableObject = { arguments:[], foo:'this is a foo',//执行函数引用 a:'string',//变量赋值}复制代码
从上面的例子我们可以看出,变量赋值其实是在执行阶段,而不是在创建阶段,理解好这一点,在创建过程,若非是函数声明,可以成功的变量声明全部是undefined。这样,下面的例子也就可以十分轻松地理解透彻
栗子二
function foo() { console.log('function foo') }var foo = 20;console.log(foo); // 20复制代码
下面我们来对这个栗子进行分解
- 创建阶段
variableObject = { arguments:[], foo:}复制代码
- 执行阶段
variableObject = { argumrnts:[], foo:20,//变量赋值}复制代码
上面例子,上下文创建阶段,在检查函数声明阶段的时候检测到函数声明foo,所以创建foo属性,并且foo的属性值指向函数foo所在内存的引用地址,然后检查变量声明的时候检测到变量声明foo,不过这个时候JS解析器检查到上下文已经存在属性undefined,所以跳过,在上下文创建阶段周期内,foo的值始终指向函数foo所在内存的引用地址。接下来就是上下文的执行阶段,这个时候执行变量赋值,所以foo被赋值为20。
从上面这个栗子我们也从侧面发现,声明具有提升性,并且函数声明的优先级大于变量声明。但是并不是浅显的理解为foo先被函数声明为函数foo所在内存引用地址,再次被覆盖为20,整个上下文创建周期,foo值,未被覆盖。