要想了解 JS 作用域问题, 就要先了解浏览器的 JS 解析器的工作方式, 当浏览器读到 script 脚本代码时, JS 解析器便开始工作. 其工作步骤主要分为两部分:
JS 解析器:
1."找一些东西"(预解析):var function 参数
例:
- alert(a); //undefined
- var a=1;
- alert(a); //1
- function fn1(){
- alert(2);
- }
JS 解析器会先找到 var function 参数进行预解析.
找到 var 定义的变量 a 时, 解析为 a = 未定义, 不会读取具体值. 换句话说, 所有的变量, 在正式运行之前, 都提前赋了一个值 -- 未定义.
读到 funtion 时, 解析为: fn1=function fn1(){ alert(2);}. 所有的函数, 在正式运行代码之前, 都是整个函数块
2. 逐行解读代码:
JS 解析器在正式读取 JS 代码时, 会先读取预解析得出的结果, 所以本例会首先弹出 undefined, 当正式读到 a=1 时, 就会修改预解析时得到的值, 此时弹出 a 的值就为 1.
例:
- 1
- alert(a); // 报错
- a=1;
- 1
注: 因为 a 没有 var 声明, 所以预解析不到 a 的值, 正式读取代码时找不到 a, 浏览器会报错.
以下是一些具体例子, 会逐步加深对作用域的理解:
例:
- alert(a); //function a(){
- alert(4);
- }
- var a=1;
- alert(a); //1
- function a(){
- alert(2);
- }
- alert(a); //1
- var a=3;
- alert(a); //3
- function a(){
- alert(4);
- }
- alert(a); //3
- a(); // 报错, 最后解析器中保存的是 3
分析:
预解析过程:
- a=...,
- a=function a(){
- alert(2);
- }
- a=...,
- a=function a(){
- alert(4);
- }
注: 浏览器首先进行预解析, 预解析结果如上方所示. 浏览器在进行预解析时, 遇到重名的变量和函数时, 会留下函数; 遇到重名的多个函数时, 会留下后声明的函数. 当正式读到代码时, 就会修改预解析时保存的值, 要记住表达式会修改预解析的值, 但函数声明不会修改预解析的值.
例:
- var a=1;
- function fn1(){
- alert(a);
- var a=2;
- }
- fn1(); //undefined
- alert(a); //1
分析:
预解析: a = 未定义;
- fn1=function fn1(){
- alert(a);
- var a=2;
- }
逐行读代码: a=1;
预解析: a = 未定义;
逐行读代码: a=2;
本例会进行两次预解析, 如上所示, 函数内定义的变量只在函数内起作用, 所以最后弹出 a 的值为 1.
例:
- var a=1;
- function fn1(){
- alert(a);
- a=2;
- }
- fn1(); //1
- alert(a); //2
分析: 此处函数内的 a 未用 var 声明, 预解析函数时找不到任何 a 的信息, 所以会跳到作用域外去找 a, 这称作作用域链, 此时 a=1.
例:
- var a=1;
- function fn1(a){
- alert(a);
- a=2;
- }
- fn1(); //undefined
- alert(a); //1
分析: 函数参数 a 相当于 var a=..., 所以函数进行预解析时 a = 未定义
1. 预解析:
- a=...,
- function fn1(a){
- alert(a);
- a=2;
- }
2. 逐行读代码:
全局 a=1;
函数调用: 预解析 a=...; 逐行读代码 a=2, 但函数外部全局变量 a 值不变
例:
- var a=1;
- function fn1(a){
- alert(a);
- a=2;
- }
- fn1(a); //1: 这个参数来自于全局量 a=1;
- alert(a); //1
注: 在执行函数时, 会先从函数参数开始读取代码, 所以参数 a 被赋值为 1.
来源: http://www.bubuko.com/infodetail-3392912.html