预解析概念
在当前作用域下,js运行之前,会把带有var和function关键字的事先声明,并在内存中安排好。(这个过程也可以理解为变量提升)然后再从上到下执行js语句。预解析只会发生在通过var定义的变量和function上。
var声明的变量
使用var声明的变量预解析:告诉解析器知道有这个名字的存在并默认将该变量赋值undefined ,如下:
1 | console.log(x); //undefined |
变量x虽然是在console.log后面定义的,但是使用var申明的x会提前保存在内存中,并赋值undefined ,然后再从上往下执行js语句 。它的执行顺序类似于下面的结构:
1 | var x; |
先声明了x ,x没有定义赋值为undefined ,输出的结果自然为undefined 。然后再给x赋值为5。
需要注意的是,如果变量声明没有使用var ,不存在变量提升的。如下:
1 | console.log(x); //error: x is not defined |
x没有使用var声明,所以报错找不到x。
functin声明的函数
使用function声明函数的预解析:先告诉解析器这个函数名的存在,然后在告诉解析器这个函数名的函数体是什么 。如下:
1 | console.log(f); |
声明函数会把整个函数都提升到最前面 ,所以浏览器中结果会输出整个函数,结果如下:
1 | function f() { |
如果在一个函数作用域中声明一个变量 ,那么它也会提升到函数作用域的最上面,如下:
1 | var x = 5; |
以上虽然全局作用域声明了一个变量x ,但是函数里面也声明了一个变量x ,所以会先查找函数里面是否有变量x,如果有的话就不会再全局下查找了。函数里面的变量x会被提升到函数作用域的最前面 ,并且赋值为undefined,所以输出结果为undefined ,类似于如下结构:
1 | var x = 5; |
函数的参数也可以理解为函数作用域的变量 ,如下:
1 | var x = 5; |
为函数f传递一个形参x ,由于函数在调用时没有传递实参 (也就是说变量x没有赋值) ,所以为undefined 。而在全局下输出x自然在全局下查找变量x ,结果为5。
变量或函数覆盖
如果在同一个作用域下声明两个相同的变量或者函数,那么后一个会覆盖前一个。如下:
1 | var x = 5; |
1 | function f() { |
如果声明的函数与变量名字相同 ,那又会怎么覆盖呢?可以看如下例子:
1 | var f = 5; |
JavaScript中 ,函数的预解析优先级是要高于变量的预解析的。无论函数在什么位置声明 ,都优选把整个函数提升到最前面。所以上面的例子中 ,虽然函数f是在变量f下面定义的 ,但是在预解析时先解析函数f ,然后再解析变量f ,后面的变量f会把前面的函数f覆盖,最后f为5 为数值类型 ,所以调用f时报错 ,f不是一个函数。
需要注意的是 ,如果变量m定义后没有赋值 ,那么函数就不会被覆盖了,如下:
1 | var f; |
掌握以上知识,我们看下面的例子 :
1 | console.log(x); //function x() {console.log(5);} |
以上例子,两个函数x优先提升 ,所以第二个函数x覆盖第一个函数x。然后两个变量x提升,由于变量x提升后为undefined,所以第二个函数没有被覆盖 ,第一个输出x结果为第二个函数function x(){console.log(5);}。随后x被赋值为2 ,所以第二个输出x结果为2。因为第一个函数x已经被提升到前面去了,所以第三个输出x结果还是2。随后为x赋值为3,所以第四,第五输出x结果为3。最后调用x,x因为是数值类型,所以会报错x不是一个函数。