天天看点

第三节:作用域链

  JavaScript采用的是静态作用域规则,也叫词法作用域,其解析过程是按照从上到下、从左到右的顺序加载,并分为两个阶段:预编译期(预处理)和执行期。预编译期对代码块中所有声明的变量和函数进行处理。注意关键字:代码块、声明、变量、函数。

1、代码块

代码块是指由<script>标签分割的代码段,JavaScript按照代码块来进行编译和执行,代码块间相互独立,但变量和函数共享。

<script type="text/javascript">

    var msg = "我在第一个代码块中定义";

    sayHello("张三");

    alert("我可以执行吗?");

</script>

    alert("第二个代码块中调用第一个代码块中的变量:\n"+msg);

执行以上代码,首先报错:

第三节:作用域链

点击是继续执行:

第三节:作用域链

第一个代码块中,因为函数sayHello没有定义自然报错,程序终止,所以语句alert("我可以执行吗?")没有执行,但是第二个代码块的代码仍然可以执行,说明代码块间是相互独立的。而且,第二个代码块可以调用第一个代码块的变量msg,说明代码快间的共享性。

因此,JavaScript的执行流程是:

第三节:作用域链

2、变量的预处理

预编译期,变量只是进行了声明但未进行初始化以及赋值。

    alert(msg);

    var msg = "预处理不会进行初始化";

执行结果为:

第三节:作用域链

这里显示msg没有赋值,说明变量msg已经声明了,但没有初始化赋值。如果msg没有声明,则应该报错:msg未定义。

3、函数的预处理

预编译期只是对声明式函数进行处理。

fn();

function fn(){

    alert("我是函数");

}

第三节:作用域链

上面的例子说明,在预编译期声明式函数已经被处理了,所以即使fn()调用函数放在声明函数前也能执行。

var fn =function(){

第三节:作用域链

而赋值式函数却不会被预处理,所以fn()调用函数放在赋值函数前执行就会报错:缺少对象。再看下面的例子:

    alert("我是函数一");

    alert("我是函数二");

第三节:作用域链

两个同名的声明式函数都会被预处理,后面的函数覆盖了前面的函数。

var fn = function(){

第三节:作用域链

虽然两个函数同名,但是,因为赋值式函数不会被预处理,所以执行的是第一个函数。如果fn()调用函数放在函数的定义之后,那么:

第三节:作用域链

在执行的时候,赋值式函数已经被处理了,后面的函数覆盖了前面的函数,所以执行了第二个函数。

4、作用域链

执行下面的代码:

var msg = "我是一个变量";

正常显示“我是一个变量”,说明内部环境可以通过作用域链访问外部环境。

    var msg = "我是一个变量";

alert(msg);

执行报错“msg未定义”,说明外部环境不能访问内部变量环境中的任何变量和函数。

    msg = "我是一个变量";

这段代码成功执行,正常显示“我是一个变量”,说明了什么呢?

两段代码的函数fn有一点细微的差别,第一个函数中代码var msg = "我是一个变量",有关键字var,说明在这里声明一个变量并初始化。而第二个函数中的代码msg = "我是一个变量",少了关键字var,说明这里给变量msg赋值。但是,在我们的代码中并没有声明变量msg的语句,那么,为什么赋值成功了呢?而且后面的语句还以调用这个变量?

在第二段代码中,我们执行函数fn()的时候,JavaScript引擎在变量表中找不到变量msg,就会沿着作用域链一直往上查找,一直到最外围的全局执行环境,在Web浏览器中,全局执行环境被认为是window对象。如果都没找到,那么,对于读操作,就会产生运行期错误;而对于写操作,就会等价为 window.msg = "我是一个变量" ,给window对象新增了一个属性。

所以,第一段代码中,在函数fn内部声明了一个变量msg,函数的外部环境无法访问这个变量。而第二段代码,执行函数fn,其实是给window全局对象设置了一个属性msg,并赋值初始化,所以我们可以访问它。其实,这段代码标准的写法应该是:

    window.msg = "我是一个变量";

alert(window.msg);