1、首先是作用域的一个概念 :简单地将作用域分为两部分 :
1)作用 作用即程序的读与写(虽然并不完整 但暂时这样理解)
2)域 域就是空间 范围 区域
那么在JS中最最常见的两个作用域就是<script></script>和 function(){} 这两者的区别可以看成是 :
1)<script></script>是一个全局的函数 由上到下进行解析(当有很多个<script>作用域时)
2)function(){} 由里向外
2、其次需要有一个浏览器解析的步骤认识(作用域都是先解析后执行的) 浏览器对JS代码的解析是从<script>标签开始的 分为两步:
1)JS预解析(可以看成是一个“仓库” 把解析出来的东西先放进这个“仓库”内) 解析的东西包括:
①var 变量 JS中因为没有明确定义变量时要具体定义成什么变量 所以在浏览器预解析到有变量的时候 先把变量名放入"仓库"
而不去管这个变量是什么类型的变量 全部赋以undefined值
②function函数 在正式运行代码之前 将整个函数块先放入“仓库‘中
③参数 性质和变量一致
那么这里又会出现一个新的问题 预解析的时候如果遇到重名问题 最常见的概括为以下两种情况:
①变量与函数重名时 留下函数(这与函数和变量在代码中出现的先后次序是无关的)这是因为此时的变量是个undefined值
而函数function也是一个类型 比起undefined更具有意义
②函数与函数或者是变量与变量重名时 就遵循后面的覆盖掉前面的函数或变量(先后次序原则)
2)逐行解读代码 在预解析后开始逐行解释代码 这边表达式就可以修改预解析的值(也就是修改"仓库"中的值)
具体表达式 可以是 = + - * / % ++ -- ! || && 参数等等等..........
3、作用域链
如果预解析时子级没有找到相应的声明 会往父级找 直到找到最顶级的作用域 此时子级中对父级变量的表达式影响的就是父级的变量
具体看例子4)
但是反过来父级却不会向子级中去找子级预解析时没有解析到的变量 所以想让父级能够得到子级中的变量的值
具体可以看下述内容“其他”
当然作用域链中经常使用到的是with语句 用来临时改变当前作用域
具体例子分析:
1)"仓库"中的重命名问题
<script> /*重名问题的分析 第一步预解析 发现有变量与函数重名问题那么保留函数 也有函数与函数重名问题 保留"后出现"的函数 即最后仓库中保留的是函数块function a(){alert(4);} 下面进行逐步解析*/ alert(a); // 发现仓库中有函数块a 所以输出function a(){alert(4);} var a = 1; //发现是个表达式 修改仓库中a的值 由函数块改为变量a = 1; alert(a); //发现仓库中有a = 1 输出1 function a() { //逐行解析时函数只是一个声明 只有表达式才可以改变"仓库"中变量值 alert(2); } alert(a); //输出1 var a = 3; // 仓库中a的值改为3 alert(a); //输出3 function a() { //同理 alert(4); } alert(a); //输出3 a(); //此时a是一个值 而不是函数 所以报错a is not a function </script>
2)作用域问题1 输出结果为 undefined 1
<script> /*现在函数fn1是<script>的子级,先进行预解析(注意此时的预解析只是针对全局<script>的) 预解析的结果是 a = undefined function fn1(){alert(a); var a = 2;}*/ var a = 1; //全局预解析中的a变量变为1 function fn1() { alert(a); //由下述预解析的结果可以知道输出undefined var a = 2; //函数作用域中的a变量变为2,函数执行结束后由GC回收 } fn1(); // 调用函数,进入函数的作用域,先进行预解析,预解析的结果是a = undefined alert(a); //输出全局预解析中的a 即现在的1 </script>
3)作用域问题2 输出结果为1 2 1
<script> /*<script>作用域预解析后结果为 a = undefined function fn1(a){...}*/ var a = 1; function fn1(a) { //此时a是一个参数 参数也是表达式 此时相当于是var a = 1; alert(a); //输出1 var a = 2; //表达式改变a的值 a = 2 alert(a); //输出2,函数结束GC回收 } fn1(a); /*函数fn1的预解析结果为 a = undefined*/ alert(a); //输出<script>域中的a = 1; </script>
3)* 多了一个arguments的使用 其实在function中参数a 变量a和arguments[0]是一样的
<script> /*预解析为 a = undefined function fn1(a){...}*/ var a = 1; // a = 1; function fn1(a) { // 相当于var a = 1; arguments[0] = 3; //相当于 a = 3; alert(a); //output number 3 var a = 2; // a = 2; alert(arguments[0]); //output number 2 } fn1(a); /*预解析为 a = undefined*/ alert(a); //output number 1 </script>
4)作用域链问题
<script> /*<script>作用域的预解析结果为a = undefined function fn1(){...}*/ var a = 1; //改变a的值为a = 1 function fn1() { alert(a); //在自己的预解析"仓库"中没有找到a 那么就往父级<script>作用域的预解析"仓库"中找,结果 //找到了a = 1那么就输出 a = 2; //改变父级<script>作用域中a的值 a = 2 注意现在fn1的"仓库"是空的 所以GC没有东西可以回收; } fn1(); /*预解析时候碰到没有var的变量声明 也没有函数声明 可以看成现在的函数的仓库为空*/ alert(a); //输出 a = 2; </script>
其他:所以由上一些内容可以知道如果想要获取函数内的值基本上就可以通过两种方式
1)利用作用域链 让父级的变量去获取
<script> var str; function fn1() { var tmp = ‘abc‘; str = tmp; } fn1(); alert(str); </script>
2)
<script> function fn1() { var tmp = ‘abc‘; fn2(tmp); } function fn2(a) { alert(a); } fn1(); </script>