很多人在使用JavaScript的时候都会遇到一些奇葩的问题,而其中不少问题是因为大家忽视掉了JavaScript中作用域与作用域链相关知识。推荐个一学习交流群:1072209430
一、 JavaScript作用域
在 JavaScript中,只有局部作用域和全局作用域。而只有函数可以创建局部作用域,像 if,for 或者 while 这种块语句是没办法创建作用域的。 (当然 ES6 提供了 let 关键字可以创建块作用域。)
JavaScript的这种特性导致 for 循环里面创建闭包时会产生让人意想不到的结果。比如下面这个例子:
上面的输出结果,大致原因就是 for 循环里面的变量的作用域是整个函数的,循环内部创建的一系列闭包引用的是同一个变量 i,而在 for 循环结束后,这个 i 的值变成了 10。所以当我们调用这些内部函数的时候,就会输出 10 了。
现在这样讲可能还是不够清楚,在我们了解作用域链和 JavaScript的执行原理后,就更容易理解了。
二、 JavaScript作用域链
当JS 里面 声明 一个函数的时候,会给该函数对象创建一个 scope 属性,该属性指向当前作用域链对象。
当JS里面 调用 一个函数的时候,会创建一个执行上下文,这个执行上下文定义了函数解释执行时的环境信息。每个执行上下文都有自己的作用域链,主要用于变量标识符的解析。
在JS引擎运行一个函数的时候,它首先会把该函数的 scope 属性添加到执行上下文的作用域链上面,然后再创建一个 活动对象 添加到此作用域顶端共同组成了新的作用域链。活动对象包含了该函数的所有的形参,arguments 对象,所有的局变变量等信息。
当解释执行函数的每一条语句的时,会依据这个执行上下文的作用域链来查找标识符,如果在一个作用域对象上面没有找到标识符,则会沿着作用链一直向上查找,这一点类似于 JS 的原型继承的属性查找机制。
来看几个具体的例子:
三、 变量提升
调用 echo 函数的第一行 name = "hello"时并不是对全局变量 name 进行重新赋值,而是对函数内部声明的变量 name 进行赋值。所以,在 echo 函数声明之后,调用 console.log(name)输出的还是 mowan。
echo 函数内部的 name 变量“使用在前,而声明在后”,这就是所谓的变量提升。
正因为函数内部的变量声明会发生“提升”副作用,所以,最好的做法就是把函数需要用到的局部变量都放在函数开头进行声明,避免产生不必要的混淆。
四、 小结
JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。理解作用域和作用域链对于理解闭包和变量提升这种奇葩特性非常有帮助。
本文可能有些地方讲的还不是非常清楚,读者可以读一读后面的参考链接,相信会有助于理解。
http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html
理解 JavaScript作用域链原理
http://code.tutsplus.com/tutorials/javascript-hoisting-explained--net-15092