一、浏览器
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 }