JS预解析是什么?
浏览器在解析JS代码时候,首先会进行一个预解析的操作。即寻找作用域中var 和 function 关键字及其声明的变量和函数,将其进行事先声明存储。此时变量的值为undefined 函数也未调用。然后再从上到下逐行解析代码。见到操作,就修改前面存储的值,见到读取,就取得前面存储的值。遇到函数执行,就又开启一个新的域,执行预解析和逐行解读。
常见JS预解析情况类型
情况一:
console.log(a); // undefined
var a = 10;
console.log(a); // 10
原因:在JS预解析时候,首先把var=a;提前解析,最先声明。然后逐行解析JS代码。所以首先log输出的变量a为undefined,然后执行a=10;进行赋值。所以后面的log输出为10;
情况二:
console.log(fun1);//函数fun1
console.log(fun2);//undefined
function fun1() {
console.log('函数fun1');
}
var fun2 = function () {
console.log('函数fun2');
}
console.log(fun1);//函数fun1
console.log(fun2);//函数fun2
原因:同理,在JS预解析时候,首先把var=fun2;和函数function fun1(){}提前解析,最先声明。然后逐行解析JS代码。所以首先log输出的变量fun1时候为整个fun1函数, 而fun2输出打印时候,此时仅仅var fun2=undefined;还没有赋值,所以输出为undefined;然后再继续解析,var fun2=function(){};此时才给变量fun2赋值了函数,即是函数申明中的表达式申明方式。然后打印fun1和fun2.打印函数fun1和fun2。
此时可以从控制台查看具体结构和分析一致。
情况三:
var a = 10;
console.log(a); // 10
function fun2() {
alert(a); // undefined
var a = 20;
alert(a); // 20
}
fun2();
分析:同理,首先先解析var a; function fun2(){...} ;然后逐行解析,a=10; log输出10,调用fun2()函数,然后在执行函数时,重新开辟一个域空间,在fun2()整个中,var a;首先被解析,然后执行alert(a); 此时弹出undefined,然后给a赋值为20;在弹出a时,值为20;
情况四:
var a = 1;
function fn1() {
alert(a);//undefined
var a = 2;
}
fn1();
alert(a);//1
分析:同理可知。注意一下,函数内部是局部变量,作用域仅仅为函数的代码块即{}所包含的区域,而这个局部对象会在函数执行完毕后立即被销毁。所以fn1函数内自己定义的局部变量a的值不会影响外面a的值得变化。 所以最后a输出的值是1。
情况五:
var a = 1;
function fn1() {
alert(a);//1
a = 2;
}
fn1();
alert(a);//2
分析:同理可知。a和函数fn1(){} 首先被解析。然后逐行解析: a=1;给a赋值 fn1();函数调用。
注意:函数外的a是一个全局变量,在任何地方都可以访问和对值进行修改。在调用函数fn1时候,首先进行函数内部的预解析,此函数没有声明的变量。在alert(a);时候, 首先寻找自己函数内部有没有定义变量a,如果用,则使用自己申明的变量a,如果没有,则向上一级逐级寻找有无声明此变量。如果有,则使用它,没有则报错,此变量未定义。在这里,fn1函数会找到外部定义的a=1的值,然后弹出1;修改a的值,将其改为2;然后结束函数。最后弹出alert(a)时,此时a的值为2。
情况六:
var a = 1;
function fn1(a) {
alert(a);//undefined
a = 2;
}
fn1();
alert(a);//1
注意点:函数的形参,相当于var a; 所以在调用函数时候,函数在预解析时候,会先将形参a 声明;函数的形参作用范围也是函数内部。所以结果如上图;
情况七:
var a = 1;
function fn1(a) {
alert(a);//1
a = 2;
}
fn1(a);
alert(a);//1
注意点:当调用函数有实参传递给函数时候,首先先将函数的实参赋值给形参再进行函数内部的预解析。所以在调用函数时候,a=1传递了过去。会弹出1 然后将形参a修改为2;结束fn1函数;
情况八:
alert(a);//a();
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
alert(typeof a)//number
a();//报错 a is not a function
分析原因:如果var同名,后面的覆盖前面的,如果函数同名,后面覆盖前面的,函数和var同名,则函数覆盖var。所以再次个案例中我们可以将代码等价于如下所示:
var a = function() { alert(4); }
alert(a); //a();
a = 1;
alert(a); //1
alert(a); //1
a = 3;
alert(a); //3
alert(a); //3
alert(typeof a)//number
a(); //报错 a is not a function
相当于变量a最先预解析之后赋值为成了一个函数,然后重名的函数后者会覆盖前者定义的函数,var申明的a变量也不会再预解析了,因为同名函数优先级最高,所以我们可以显示等价于上面代码。然后逐行在逐行解析代码。首先弹出a是一个函数,然后执行a=1;的赋值操作,将a的值修改为1;然后修改为3,此时a的类型为number数值型。最后调用函数a时候,a已经被数值3的值覆盖了,不在是一个函数类型。所以最后一行会报错。a不是一个函数,不能调用。
情况九:
var a = 2;
function test() {
var a = 3;
var b = 4;
c = 5;
alert(a); // 3
}
test();
alert(c); // 5
alert(b); // 报错 b is not defined
注意:在JS中,没有var关键字申明的变量赋值了之后,相当于是一个全局变量。所以test()函数内部定义的c=5;相当于是一个全局变量,在任何地方都可以被访问到。而函数内部定义的b变量,在函数执行完毕之后就被销毁了,在外部弹出变量b时候,不能找到。所以会报错。
情况十:
function fn(a) {
console.log(a);
var a = 2;
function a() { }
console.log(a)//2
}
fn(1);
分析:调用函数fn时候,首先现将实参1赋值给函数的形参a;然后再执行函数fn内部的预解析,var a 和 function函数a同名,函数优先级更高,而形参同var等价,所以最后a为一个函数类型,输入打印a时,是一个函数,然后解析a=2;修改a的值为数值型2, 调用a函数,然后在函数a中打印的a为2。
情况十一:
console.log(a); //是函数a
function a(a) {
console.log(a); //函数a
var a = 10;
console.log(a); //10
function a() { };
console.log(a); //10
}
a(1);
a = 0;
var a;
console.log(a); //0
同理可得;
情况十二:
var a = 1;
var x = function () {
console.log(a); //1
};
function f() {
var a = 2;
x();
}
f();
注意点:此处调用函数f()时,内部嵌套了一个函数,但是并不存在父子关系,所以函数x中的a不会去函数f()中获取,而是去找a=1;所以打印1
情况十三:
var x = 1,
y = z = 0;
function add(n) {
n = n + 1;
};
y = add(x);
function add(n) {
n = n + 3;
};
z = add(x);
console.log(x, y, z); //1 undefined undefined
原因:因为函数没有返回最后的值,即没有return语句,所以y和n都获取不到值,所以都为undefined;
总结:
- 找到var,就给它一个undefined,提到最前面;
- 找到function声明的函数,就把函数整体提到最前面
- 如果var同名,后面的覆盖前面的,如果函数同名,后面覆盖前面的,函数和var同名,则函数覆盖var。
- 函数调用时候,重新开启一个新的域函数内部的预解析,如果调用函数时候有实参,先进行实参赋值,在进行预解析;函数的形参预解析作用同var;其他同上;
- JS执行预解析之后,就会逐行解析代码;
- 注意全局变量和局部变量的作用范围;
- 函数没有返回值时候,接收的结果为undefined;
谢谢观看!!!