Demos: https://github.com/jiangheyan/JavaScriptBase
一、浏览器
1、“JS解析器”(至少分为两步骤)
1.1 JS预解析(代码正式运行之前的准备工作) “找一些东西并形成一个仓库”:var、function、参数
1.1.1 var a = 1; 找到var a = undefined (所有的变量在正式运行代码之前,都提前赋值:未定义;--》undefined)
1.1.2 function fn(){ alert(......); } 找到fn = function fn(){ alert(......); } (所有的函数在正式运行之前都是整个函数块)
1.1.3 预解析规则:
1.1.3.1 遇到重名的:只留一个------变量和函数重名,选择留下函数;同等级的留下后面那个(两个变量重名留后者;两个函数重名留后者)
1.1.3.2 表达式可以修改仓库中的值
1.1.3.2.1 表达式:+-*/ Number()...... 注意:函数只是一个声明,不是表达式
1.2 逐行解读代码
1 alert(a); // function a(){alert(4);}
2 var a = 1; // 表达式能改变仓库中(预解析仓库)的值---> a = 1
3 alert(a); // 1
4
5 function a() { //函数声明不是表达式,不能改变a的值
6 alert(2);
7 }
8 alert(a); // 1
9 var a = 3; // 表达式能改变仓库中(预解析仓库)的值---> a = 3
10 alert(a); // 3
11
12 function a() { //函数声明不是表达式,不能改变a的值
13 alert(4);
14 }
15 alert(a); // 3
16
17 a(); //a is not a function 报错,因为此时仓库中已经没有函数a了
18
19
20
21 模拟浏览器解析:
22 预解析:
23 a = undefined
24 a = function a(){alert(2);}
25 a = undefined
26 a = function a(){alert(4);}
27 综上,最后 a = function a(){alert(4);}
28 所以 ......
二、作用域 只要是一个域,就会发生预解析。其中script标签是一个域
1、每个script标签代表一个域块,从上到下的顺序,执行完一个script块中 js(预解析+逐行读代码) 才执行下一个script(如果有的话)
2、script是全局变量、全局函数
3、函数也是一个域,所以遇到函数执行时也会发生至少两步骤:预解析+逐行读代码 读代码:由里而外,作用域链
4、{} 也是一个域,所以遇到 {} 执行时也会发生至少两步骤:预解析+逐行读代码
1 var a = 1; // a = 1
2 function fn(){ //fn不变
3 alert(a); // // undefined
4 var a = 2; // // a = 2
5 }
6 fn(); // // 开始fn的预解析+逐行读代码
7 alert(a); //完成fn代码域的js解析,这是全局下的a ---> // 1
8
9 模拟浏览器解析:
10 预解析:
11 a = undefined
12 fn = function fn(){alert(a); var a = 2;}
13 综上:读代码 //
14
15 fn 中的预解析:
16 a = undefined
17 综上:读代码 // //
1 var a = 1; // a = 1
2 function fn() { //fn不变
3 alert(a); // // 预解析的仓库中没有,此时作用域链发生作用,由里而外,局部没有,找全局中a = 1
4 a = 2; // // a = 2
5 }
6 fn(); // // 开始fn的预解析+逐行读代码
7 alert(a); //完成fn代码域的js解析,这是全局下的a,并且被局部函数中的表达式改变了值 ---> // 2
模拟浏览器解析:
预解析:
a = undefined
fn = function fn() { alert(a); var a = 2; }
综上: 读代码 //
fn 中的预解析:
仓库为空
综上: 读代码 // //
1 var a = 1; // a = 1
2 function fn(a) { //fn不变
3 alert(a); // // 预解析仓库中有,就优先预解析仓库的值---> undefined
4 a = 2; // // a = 2 修改的是仓库中的a,没有修改全局的 a
5 }
6 fn(); // // 开始fn的预解析+逐行读代码
7 alert(a); //全局中的a 1
8
9
10
11 模拟浏览器解析:
12 预解析:
13 a = undefined
14 fn = function fn(a) { alert(a); var a = 2; }
15 综上: 读代码 //
16
17 fn 中的预解析:
18 a = undefined //这是参数a,也是被预解析的对象
19 综上: 读代码 // //
1 var a = 1; // a = 1
2 function fn(a) { //fn不变
3 alert(a); // // 预解析仓库中有,并且传值a = 1(fn(a) == fn(var a = 实参a<如果有传参的话>))就优先预解析仓库的值---> 1
4 a = 2; // // a = 2 修改的是仓库中的a,没有修改全局的 a
5 }
6 fn(a); // // 开始fn的预解析+逐行读代码
7 alert(a); //全局中的a 1
8
9 模拟浏览器解析:
10 预解析:
11 a = undefined
12 fn = function fn(a) {alert(a);var a = 2;}
13 综上: 读代码 //
14
15 fn 中的预解析:
16 a = undefined //这是参数a,也是被预解析的对象
17 综上: 读代码 // //
为了防止预解析浏览器兼容:尽量不要在if、for中定义函数、定义变量等等
1 alert(fn); //预解析中if、for不是一个作用域,所以里面的变量相当于全局变量,所以弹出function fn(){alert(2);}
2 //但是,在FF浏览器中有兼容性,会报错,fn未定义!!!
3 if(true){
4 function fn(){
5 alert(2);
6 }
7 }
注意:for循环内部的函数与i
1 for (var i = 0; i < 3; i++) {
2 oBtn.onclick = function() {
3 alert(i); //undefined 原因是函数内部是一个域,内部找到var i---> i预加载是undefined
4 for (var i = 0; i < 3; i++) {
5 ......
6 }
7 }
8 }
9
10 for (var i = 0; i < 3; i++) {
11 oBtn.onclick = function() {
12 alert(i); //3 原因是函数内部是一个域,预加载时没有变量,于是第二部在逐行读代码的时候找不到i,到作用域链上(外部)找到i = 3,所以3
13 // for (var i = 0; i < 3; i++) {
14 // ......
15 // }
16 }
17 }
18
19 for (var i = 0; i < 3; i++) {
20 oBtn.onclick = function() {
21 alert(i); //3 原因是函数内部是一个域,预加载时没有变量,于是第二部在逐行读代码的时候找不到i,到作用域链上(外部)找到i = 3,所以3
22 for (i = 0; i < 3; i++) {
23 ......
24 }
25 }
26 }