前面的文章说到, 执行上下文的建立阶段,主要有三个内容:javascript
一、建立变量对象;二、初始化做用域链;三、肯定this的指向。java
在这里,要说一下做用域和做用域链了,先来一个例子:git
//全局环境 var a = 10; function inner(){ console.log(a); } inner();
在inner函数的执行上下文的执行阶段中,它的VO(变量对象)都没有var a这样的变量声明,因此console的时候,怎样得到a的值呢,就是经过全局环境中的AO(活动对象),由于里面就有a的值(不知道什么是VO和AO,必定要先看一下我前面的文章:关于javascript中的变量对象和活动对象)。github
其实,做用域这个东西,能够理解为自身执行上下文中的活动对象(AO)能够被访问的区域,说的有点拗口,其实看一下我前面的文章(关于javascript中的变量对象和活动对象),就能够知道,其实咱们执行函数的时候,用到的变量值,都是从AO上面取到的,若是本身的执行上下文中的AO没有对应要用的值(例如上面例子中的a),那就要往上一层的执行上下文中的AO中找这个值,若是上一层尚未,就要再往上一层的执行上下文中的AO去找,而这个一层一层的连接关系,就是所谓的做用域链。(这里说到的上一层,其实就是执行上下文栈中压着的下一层执行上下文,不理解能够先看我前面的文章:关于javascript中的从堆栈内存到执行上下文)函数
说到做用域这个东西,我以为很多人都被它坑过,举个例子:this
//先声明变量jj并赋值为10 var jj = 10; //再声明一个函数what function what(){ console.log(jj); } //执行what函数 what();
相信你们都很是清楚打印结果了,就和上面例子同样,就是10。那若是这样呢:code
//先声明变量jj并赋值为10 var jj = 10; //再声明一个函数what function what(){ console.log(jj); var jj = 20; console.log(jj); } //执行what函数 what();
是否是会说打印结果是10和20呢?那就错了,实际打印结果是undefined和20。为何呢?不是一开始打印时候前面没有变量jj,而后向上找到等于10,后面就改变它的值,而后输入20吗?对象
这样就没有真正理解javascript的词法做用域的概念。做用域的类别能够影响到变量的取值,分为词法做用域(静态做用域)和动态做用域。ip
它们的区别是:对于词法做用域,函数的做用域在函数定义的时候就已经肯定了,而动态做用域不一样,在函数调用的时候才肯定的。内存
而javascript,采用的就是词法做用域,或者叫静态做用域。
因此在what函数中声明了一个var jj = 20,就将里面有jj这个变量名的取值,框住了在这个函数里面了,或者能够说,调用what函数的时候,你用var这样的字眼声明了jj这个变量,就会在执行上下文建立时候的变量对象VO中挂上了属性jj=undefined,因此一开始就将jj打印出来,因为尚未赋值,因此打印出undefined了,而后后面赋值了,就打印出了20了。
若是你想按照你一开始想的那样打印出10和20,能够将what函数里面的var jj = 20改成jj = 20,去掉var,这样就至关于what函数里面没有声明变量jj,而是向上找到jj,并将它打印,而后更改jj的值,再打印,实际上,这种作法会污染全局变量,由于你在what函数里面将jj这个全局变量的值改成20了。
好了,若是你明白由于用var声明了变量,致使在自身的执行上下文中寻找jj的值而不是向上寻找,可是你不明白为何var jj 明明在console以后才声明的,为何会受到它影响呢?这里,就要再说一个概念,叫作变量提高。
变量提高,就是解释器会将函数声明和变量声明提高到方法体的最顶部,函数声明比变量声明提得更高。
其实很容易理解变量提高,仍是回去看一下我前面的文章(关于javascript中的变量对象和活动对象)就知道了,执行上下文在建立的时候就会建立变量对象,而变量对象的建立顺序为:形参、函数声明、变量声明(用var 声明的),因此在你的代码执行阶段(执行上下文的执行阶段)以前,它已经建立了变量对象了,因此相对其余的执行代码来讲,这就是所谓的变量提高。
说回去最初的执行what函数的地方,其实我这样写也是能够的:
//先声明变量jj并赋值为10 var jj = 10; //执行what函数 what(); //如今才声明一个函数what function what(){ console.log(jj); }
为何呢?由于变量提高,解释器会将声明的what这个函数提到顶部,因此你上面执行what这个函数,实际解释器已经将what函数提高上去了。
除了函数声明,变量声明也同样。
回到前面的例子,我在what函数内声明并初始化var jj = 20 能够当作两个步骤,第一个步骤,声明变量var jj ,第二个步骤,初始化变量,jj = 20,因此上面的函数能够写成这样:
function what(){ console.log(jj); var jj ; jj = 20; console.log(jj); }
固然了,这里面声明的jj变量,也会变量提高,因此会变成这样:
function what(){ var jj ; console.log(jj); jj = 20; console.log(jj); }
再结合回到前面一块儿:
//先声明变量jj并赋值为10 var jj = 10; //再声明一个函数what function what(){ var jj ; console.log(jj); jj = 20; console.log(jj); } //执行what函数 what();
是否是很好地理解了打印结果就是undefined 和 20了,这里要注意的是,初始化变量是不会提高的,因此jj = 20仍是留在了原位。
换个方式说一下变量提高,下面两个函数写法有什么不一样的地方:
//写法一 var claim = function(){ console.log('i am first'); }; //写法二 function claim(){ console.log('i am first'); }
举一个例子就很清楚了:
//写法一 var claim = function(){ console.log('i am first'); }; claim();//打印结果为i am first var claim = function(){ console.log('i am second'); }; claim();//打印结果为i am second
//写法二 function claim (){ console.log('i am first'); }; claim();//打印结果为i am second function claim(){ console.log('i am second'); }; claim();//打印结果为i am second
好了,理解了上面两种打印结果就知道了变量提高了。
其实做用域前面已经说得很清楚了,就是执行上下文的AO(活动对象)可被访问的范围,而做用域链能够类比原型链,本身若是没有,就一级一级往上找,这个一级一级,就是执行上下文栈中压着的下一个执行上下文(再回顾前面文章:关于javascript中的从堆栈内存到执行上下文),那就很容易理解明白了。