由于比较复杂又很重要,所以感觉作用域链需要单独拿出来写一篇文章。感觉这篇文章真的讲的超级通俗易懂。
-
执行环境
每个函数都有自己的执行环境。当执行流进入一个函数时(即调用该函数),函数的环境就会被推入一个环境栈中。而在函数执行之后,将其环境弹出栈,把控制权返回给之前的执行环境。全局执行环境是最外围的一个执行环境。全局执行环境被认为是window对象,全局执行环境直到应用程序退出--例如关闭网页或浏览器---时才会被销毁。举个例子:var scope = "global"; function fn1(){ return scope; } function fn2(){ return scope; } fn1(); fn2();
上面代码执行情况演示:
-
this的意义:
全局函数中(函数声明提升),this等于window;函数作为某个对象的方法调用时,指代那个对象。(可以简单看成this就是指向所在函数作用域的上一级,全局函数的上一级是window,函数的上一级是对象,可以这么简化理解),以一道面试题举个例子:var a=1; function test(){ alert(a); //同第一题显示为undefined a=2; //这里其实还有var a,而且是全局变量,声明会被提升到最顶端但不会连同a=2一起提升,js在预编译阶段,会忽略重复的声明,然后在运行阶段赋值; alert(this.a); //因为this在全局函数指window所以这里的说的是window下的a所以结果为1 this.a=4; //改变window下的a值1的语句 var a=3; //局部变量a的值为3 覆盖前面局部变量 alert(a) //在局部域里输出当然只能是a=3 } test(); alert(a); //由于window下的a值变为4所以此输出语句为a=4
答案输出依次为:undefined 1 3 4。
完整代码等价于:
var a; var a=1; function test(){ alert(a); a=2; alert(this.a); this.a=4 var a=3 alert(a) } test(); alert(a);
-
作用域链
当某个函数第一次被调用时,就会创建一个执行环境(execution context)以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([scope])。然后使用this,arguments(arguments在全局环境中不存在)和其他命名参数的值来初始化函数的活动对象(activation object)。当前执行环境的变量对象始终在作用域链的第0位。每个执行环境都有一个与之关联的作用域链,当解析器进入执行环境时被定义,作用域链是一个对象列表,用来检索各个变量对象中的变量和函数,这样可以保证执行环境有权访问哪些变量和函数,举个栗子。
var a='123'; function testFn(b){ var c='abc'; function testFn2(){ var d='efg'; alert(a); } testFn2(); } testFn(10);
testFn2内未声明变量a,为什么testFn2能调用全局变量a?整个过程是怎么发生的呢?请看下图。
当解析器进入全局执行环境时,调用变量和函数时只在Global对象中查找。
当解析器进入testFn函数执行环境时,函数内部属性[[scope]]中首先填入Global对象,然后将testFn活动对象添加到Global对象之前,形成一个作用域链。
当解析器进入testFn2函数执行环境时,函数内部属性[[scope]]首先填入父级的作用域链,然后再将当前的testFn2活动对象添加到作用域链的前端,形成一个新的作用域链。
testFn2调用变量a时,首先在当前的testFn2活动对象中查找,如果没有找到就顺着作用域链向上,在testFn活动对象中查找变量a,如果没
有找到再顺着作用域链向上查找,直到在Global对象中找到为止,否则报所以函数内部可以调用外部环境的变量,外部环境不能调用函数内
部的变量,这就是作用域特性的原理。