再探javascript作用域

对于javascript作用域的理解,之前整理过两篇文章:《javascript作用域和作用域链》《javascript的词法作用域》。最近读了周爱民老师的《javascript语言精髓》,对javascript作用域有了新的认识,特地整理下思路。

javascript的作用域广义来分:语法作用域变量作用域

javascript语法作用域

javascript语法作用域是讨论代码的组织结构上抽象,可以理解为讨论的是“圈地”问题,也有书籍中对纯粹的语法作用域称为“静态作用域”。

语法作用域分四级,下标依次增高

级别 作用域
级别一 表达式
级别二 语句/批语句
级别三 函数
级别四 全局(宿主)
  1. 同级别可以平行也可以相互嵌套(包含)
  2. 高级别可以嵌套(包含)低级别
  3. 低级别不可以嵌套(包含)高级别作用域;假如低级别嵌套高u别,可以理解成相互平行,例如下面的代码是完全等效的
if(true){
    //
    function fn(){
        //
    }
}

if(true){
    //
}
function fn(){
    //
}

javascript作用域和作用域链

javascript的作用域是一个重要的知识点,javascript作用域(scope)是通过javascript的作用域链(scope chain)来实现的。

javascript作用域

javascript作用域(scope):简单的说,就是创建一个函数时在什么环境下创建的,它控制了javascript代码运行时变量和函数的访问范围。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。

全局作用域(Global Scope)

在代码中任何地方都能访问到的对象拥有全局作用域,注意:全局变量是魔鬼!因为它效率低(后面讲到),污染全局环境!一般有一下三种方式获取全局作用域。

代码最外层定义的函数和变量拥有全局作用域

在代码的最外层,定义的函数、变量,都是拥有全局作用域的。

var a = 1;
function b(){
    var a = 2;
}
b();
alert(a);//1

函数内部不使用var定义的拥有全局作用域

在函数内部,不使用var定义的变量拥有全局作用域,这是个坑!要注意,很多前端开发工程师不习惯写var,其实这时候你已经污染了全局作用域!

var a = 1;
function b(){
    a = 2;
    c = 3;
}
b();
alert(a);//2
alert(c);//3

javascript的词法作用域

大家应该写过下面类似的代码吧,其实这里我想要表达的是有时候一个方法定义的地方和使用的地方会相隔十万八千里,那方法执行时,它能访问哪些变量,不能访问哪些变量,这个怎么判断呢?这个就是我们这次需要分析的问题——词法作用域

var classA = function(){
    this.prop1 = 1;
}
classA.prototype.func1 = function(){
    var that = this,
        var1 = 2;

    function a(){
        return function(){
            alert(var1);
            alert(this.prop1);
        }.apply(that);
    };
    a();
}
var objA = new ClassA();
objA.func1();

词法作用域:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。 with和eval除外,所以只能说JS的作用域机制非常接近词法作用域(Lexical scope)。

下面通过几个小小的案例,开始深入的了解对理解词法作用域和闭包必不可少的,JS执行时底层的一些概念和理论知识。

经典案列重现

1、经典案例一

/*全局(window)域下的一段代码*/
function a(i) {
    var i;
    alert(i);
};
a(10);

疑问:上面的代码会输出什么呢?
答案:没错,就是弹出10。具体执行过程应该是这样的

a 函数有一个形参 i,调用 a 函数时传入实参 10,形参 i=10
接着定义一个同名的局部变量 i,未赋值
alert 输出 10

思考:局部变量 i 和形参 i 是同一个存储空间吗?

2、经典案例二

/*全局(window)域下的一段代码*/
function a(i) {
    alert(i);
    alert(arguments[0]); //arguments[0]应该就是形参 i
    var i = 2;
    alert(i);
    alert(arguments[0]);
};
a(10);

疑问:上面的代码又会输出什么呢?(( 10,10,2,10 || 10,10,2,2 ))
答案:在FireBug中的运行结果是第二个10,10,2,2,猜对了… ,下面简单说一下具体执行过程