作用域和作用域链
js 作用域和作用域链
作用域指的就是 变量和函数(方法)能在哪些区域能调用和使用,全局变量和函数(方法)能在任何地方调用,当然前提是你在声明变量(注意这里指的变量)之后调用,如果在函数内或者函数外没有声明变量直接调用,js会抛出提示引用错误如下:
如果在函数内或函数外没有写var或 let 关键字赋值一个未声明的变量,那么js会直接在全局上添加这个为未声明的变量作为全局变量:
全局变量和函数 顾名思义在全局都可以使用,所以他的作用域是整个js,局部变量和函数说的就是,只能在这个限定区域使用,
上面这个图就是在函数内部申请的一个局部变量 ,在他函数外部是不能获取到的,但是他在函数内部就是可以访问到的,上图自执行函数内第一行代码,因为变量提升问题(后面讲作用域链会说明怎么提升的)所以他没有报错,只是返回了一个undefined.
前面讲解了什么是作用域,下面说说 作用域链,
作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。《出自javascript高级程序设计第四章》
上面这个图表达了,函数外部是不能访问函数内部的变量的,但是函数内容可以访问外部“父”级的函数,同级函数也是不能访问的,函数里面console.log(b),是怎么查找并输出的那?预编译warpb的时候创建activation object对象(也称为活动对象)简称ao对象,创建ao对象时候ao的第一个属性是arguments对象(全局是没有arguments对象的),arguments对象下包含所有该函数的形参和一些其他方法。
开始预编译函数内第一条代码var b时候查看本函数的ao对象上arguments属性有没有变量b如果没有就就在ao对象上创建属性名为b的变量(这里解释一下上面说的变量提升执行到这里的时候,查看自身ao对象上有没有,查看到有变量abc但是没有被赋值,所以输出undefined)
回到解释作用域链然后预编译第二行代码(注意预编译不是执行!)console.log(s)这里因为这里不是声明和赋值过程,不做任何操作。
(这里解释下上面所说的没有声明直接赋值的变量为什么会变成全局变量),编译到这里时检测到是赋值abc操作没有var,直接找到全局对象variable object(变量对象)简称vo对象,检查到vo上有abc属性不做任何操作,如果没有添加abc属性,在执行到这时候检查ao对象上有没有这个变量,一看没有 下面这段特别重要所以换行
这时候就会找到当前代码执行环境里的变量对象[[scope]](也就是作用域链),《javascript高级程序设计第四章》中解释到:作用域链的前端,始终就是当前执行的代码所在的环境里的ao(活动对象),我把[[scope]]对象比作成数组,数组的第一项就是自身ao对象,第二项就是父函数的ao对象,以此类推直到全局对象variable object(变量对象)简称vo(这里解释下vo(除了没有arguments)和ao一样只是叫法不同至少我这么理解的),如果自身函数活动对象ao没有要查找的变量,就向父级函数对象查找,直到vo对象,如果都没有就会抛出引用错误异常。(这种行为叫回溯)
没有检查到自身函数vo对象回溯查找,查到全局上有变量有abc,然后把“1111”赋值给了全局变量abc
回到解释作用域链由于wrapb只有一个var b 声明(没有声明直接赋值的操作才会在vo上添加该属性)。
下面说执行
1、执行第一条赋值a因为自身函数ao对象有a所以把“1”赋值给自身ao.a对象
2、执行console.log(s)自身ao对象找不到变量s,回溯找到全局上有输出s
3、执行console.log(a)自身ao对象找不到变量s,回溯找到父函数有输出a
4、执行console.log(b)自身ao对象有变量b,输出b(就算父函数或全局有变量a也不会进项回溯查找了,因为自身ao对象上有属性a。
5、上面说的回溯过程就成了链式操作,所以称为作用域链
忘了说函数,ao在预编译过程如果预编译到函数声明,会覆盖属性,如果遇到变量自己身有的话不做任何操作。下面例子:
注意函数表达式也是变量赋值的一个过程,和函数声明还是有区别的。
在来一边ao对象创建属性过程
1、创建函数对应ao对象,把arguments添加到ao对象上,
2、编译到console.log(a),直接找到vo对象发现vo对象有console属性存在不做任何操作
3、var a查看ao有a属性吗,检测到没有添加到ao上,
4、编译到function a(){console.log(1)},查看ao上有a因为是声明函数所以把function a(){console.log(1)}赋值给ao.a
5、编译到a=function (){alert(1)} 变量赋值查看到ao有a不做任何操作
6、编译到a();不是赋值和声明不做任何操作
7、开始执行console.log(a) 输出a的值 //ƒ a(){console.log(1)}
8、执行a=1,自身函数ao上有变量a 然后把“1”赋值
9、不对function a(){console.log(1)},执行因为这里是在预编译时执行的
10、继续执行,a被赋值function (){alert(1) }
11、创建a函数的活动对象ao,执行a函数,ao添加arguments,函数内没有声明和赋值不进行任何操作
12、执行函数a 弹出alert(1)
最后 如果有人觉得我有理解错,说错的地方 一定要在下面评论 别让我误人子弟。。。。。