第三节:作用域链

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第三节:作用域链相关的知识,希望对你有一定的参考价值。

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


1、代码块


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


<script type="text/javascript">

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

    sayHello("张三");

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

</script>

<script type="text/javascript">

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

</script>


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


技术分享

点击是继续执行:

技术分享

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


因此,JavaScript的执行流程是:


技术分享


2、变量的预处理


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

    alert(msg);

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

执行结果为:

技术分享

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


3、函数的预处理


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


fn();

function fn(){

    alert("我是函数");

}

技术分享

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


fn();

var fn =function(){

    alert("我是函数");

}

技术分享

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


fn();

function fn(){

    alert("我是函数一");

}

function fn(){

    alert("我是函数二");

}

技术分享


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


fn();

function fn(){

    alert("我是函数一");

}

var fn = function(){

    alert("我是函数二");

}

技术分享


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


function fn(){

    alert("我是函数一");

}

var fn = function(){

    alert("我是函数二");

}

fn();

技术分享


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


4、作用域链


执行下面的代码:


function fn(){

    alert(msg);

}

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

fn();


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


function fn(){

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

}

alert(msg);


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


function fn(){

    msg = "我是一个变量";

}

fn();

alert(msg);


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


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


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


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


function fn(){

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

}

fn();

alert(window.msg);


本文出自 “老惠” 博客,转载请与作者联系!

以上是关于第三节:作用域链的主要内容,如果未能解决你的问题,请参考以下文章

关于Javascript作用域及作用域链的总结

关于JS的原型链和作用域链

JS的作用域链

理解作用域链

前端:如何理解 JS 的作用域和作用域链?说说闭包的两个应用场景

作用域链