在说作用域链之前首先搞清楚几个事情:
1.JavaScript中没有块级作用域(除了es6中的let)
2.JavaScript中只有全局作用域和函数作用域(也就是局部作用域)
3.在JavaScript中每个函数作为一个作用域,在外部无法访问内部作用域中的变量。
4.由于JavaScript中的每个函数作为一个作用域,如果出现函数嵌套函数,则就会出现作用域链,那么就会出现嵌套函数里面的变量到底属于哪一个作用域的声明的问题
5.JavaScript中作用域执行前已创建
6.作用域就寻找变量真正出处的地方
7.小心变量提升
8.变量当前的作用域就是变量所属函数声明位置的时候的作用域
说那么多废话不如直接上代码
其实作用域链就是对应数据结构中的树,那么如果代码里出现函数的嵌套,也就是作用域的嵌套,就形成了像链子一样的作用域链。如果将每个作用域看成一个节点,就形成了父节点(父作用域)下若干子节点(子作用域),子节点下面可能又有子节点...这样就形成了类似树的形状。如上图所示,可以大致的画出树形图,只标记每个作用域下的变量或者函数的声明,调用不管。
如果插上一个a = 3
那么这个a在哪个作用域里面呢?是在函数f1作用域里面,注意不是全局的,因为之前已经声明了var a = 2
后面的a = 3
只是赋值操作,所以在当前作用域里面找变量a的值就会优先找它。
如果没有var a = 2
那么这里的a = 3
是哪个作用域下的a呢?
一定在网上听过很多说在函数里声明变量不加 var,只写a = 3
就是在声明全局变量??不是的,不是这样的。a = 3
会优先认为这是一个赋值的操作,不会认为是一个声明的,没有声明,哪有声明,但是它会沿着当前的作用域树去找,首先会沿着当前的作用域f1去找,有没有声明a,如果没有就去它的父作用域去找,发现全局作用域里面有一个a的声明,于是就是给全局作用域的a进行赋值的操作。
那么什么情况下才是声明全局呢?那就是如果如果沿着这个作用域树一直往上找一直找不到这个变量的声明。先从自己所在的函数f1的作用域里找没有,然后去父作用域(这里是全局作用域)找也没有,那退而求其次只能声明并赋值
那么实际中我们如何画这个树呢?其实只要找函数就行了,一个函数就是一个作用域,然后给它标记出来
那么我们在使用一个变量时候怎么知道它的声明是谁?就找它当前作用域里面的声明,如果找不到就找它外面的作用域(也就是父作用域),如果还是找不到就再往外找一个作用域,也就是**"就近原则"**,也就说哪个作用域“离我”最近,我就用哪个作用域下声明的变量
代码的变形,此时执行f1的结果是什么?答案是undefined
我们要从浏览器的思维去看待问题,首先它会先看到变量的声明然后才会看到变量的赋值,做好的办法就是凡是看到变量的声明就要把它放到当前作用域的最前面,那样就可以保证自己不会犯错
代码再变形,此时函数f4里面的a输出什么?答案是1,只需要知道变量当前的作用域就是变量所属函数声明位置的时候的作用域而不是变量所在函数调用时候所在作用域的变量的值
还有一个问题就是下面的代码有没有可能?????是遮住的代码使得h函数f4里面的变量a结果不是1而是2,如果有遮住的代码会是什么,作用域到底是在说什么?作用域说的是这个a是哪个a但是并没有说这个a是哪个a的值,答案是a = 2
下面还有一个最最最有名的题,很多人第一次应该都会错
liTags[i]里面的i是2,那怎么能代表console.log(i)里面的i还是2呢? 就相当于 name 和 ‘name’ 是一样的吗?name 是一个变化的量
如果我们写完了这段代码,一个小时之后再去点击列表3,那么console.log(i)里面的i就是一个小时后的i
其实道理就像下面的这样,这个时候输出的i是几?我们知道作用域的i就是顶部的i但是i的值却是最后的i的值,也就是或要注意i是那个i,但是i的值可能会变
再回过头看那个for循环的题,其实for循环里面的console.log(i)里面的i就相当于for循环结束后的i
其实真正的代码是这样的,全局作用域下只有一个i,那么有没有可能用户在for循环之前就点击了“选项3”呢,答案是不可能的,for循环是在一瞬间就完成的
题外话引入闭包的概念:什么是闭包?
如果一个函数使用了它范围(或者说作用域)之外的变量,那么这个函数和这个变量组成的整体就叫闭包