js之预解析
在谈js的预解析之前,先看一段c++程序
#include <iostream>
using namespace std;
void useGreet(){
greet();
}
void greet(){
cout << "hello" <<endl;
}
int main() {
useGreet();
return 0;
}
复制代码
只要是有c++基础的都会知道以上程序是无法通过编译的,因为useGreet()函数中调用了一个未声明的函数,即便greet()函数在其声明。这是c++编译器编译顺序本身决定的。 再来看用js写的一段代码
function useGreet(){
greet();
}
function greet(){
console.log('hello');
}
useGreet();
复制代码
以上代码是可以正常运行的
以上两个例子虽然与js的预解析的关系不是很大,但是也反映了js中预解析的内容
再来看这段js代码
let a = 1;
let fun = function(){
console.log(a);
let a = 2;
console.log(a);
}
fun();
复制代码
代码的运行结果为
undefined
2
复制代码
再来看下面这段代码
let a = 1;
let fun = function(){
console.log(a);
a = 2;
console.log(a);
}
fun();
复制代码
代码的运行结果为
1
2
复制代码
两段看似差异十分微小的代码的运行结果是天差地别的。在解释运行结果之前,我们需要知道js中函数和变量在内存中的存储模型,js的预解析,js的作用域链。
函数和变量在内存中的存储模型
js的数据分为两类
- 基本类型
- 引用类型(对象类型) 其中基本类型是存储于栈内存中,而对象是存储于堆内存中。
var person = {
name: "Jack",
sex: "man"
}
复制代码
上例中先声明了一个变量person,然后对其进行赋值。其中person的值
{name: "Jack", sex: "man"}
存储于堆内存,而person这个字面量存储了一个地址,这个地址即为person的值在堆内存中的存储地址
预解析
js的预解析是指,在当前作用域中,JavaScript代码执行之前,浏览器首先会默认的把所有带var和function声明的变量进行提前的声明或者定义。即先执行变量的声明和函数的声明与赋值的语句,然后在按顺序执行其他语句。注意,变量和函数的预解析是不同的,变量的预解析只进行声明而不执行赋值,而函数的预解析声明与赋值都会执行。
例如
fun();
function fun(){
console.log("hello");
}
复制代码
以上代码可以正常输出hello
,其执行顺序为,先对 fun()
函数进行声明和赋值,再执行fun()
的调用
再看下面一段代码
fun(str);
function fun(str){
console.log(str);
}
let str = "hello";
复制代码
以上代码可以的输出结果为undefined
,其执行顺序为,先对 fun()
函数进行声明和赋值,在进行str
的声明,然后执行fun()
函数的调用,然后执行str的赋值,因为在赋值之前就使用了str,所以输出了str的初始值undefined
。
作用域链
作用域链为: 当在某个作用域要使用某个变量时,首先查找本作用域是否有该变量,若有停止查找并取得该变量,若没有再查找本作用域的上级作用域,重复以上过程,直到找到该变量为止。若到全局作用都没有找到则报错。
现在就可以对最开始的两段代码的运行结果做出解释
第一段
let a = 1;
let fun = function(){
console.log(a);
let a = 2;
console.log(a);
}
fun();
复制代码
这段代码有两个作用域,一个全局作用域,还有一个fun()匿名函数作用域
先执行全局作用域的代码
let a
=> let fun = function
=> fun()
=> a=1
复制代码
再执行函数作用域的代码,执行fun()函数调用的执行过程
let a // 预解析
=> console.log(a)
=> a=2
=> console.log(a)
复制代码
第二段
let a = 1;
let fun = function(){
console.log(a);
let a = 2;
console.log(a);
}
fun();
复制代码
这段代码也有两个作用域,一个全局作用域,还有一个fun()匿名函数作用域,唯一不同的是匿名函数作用域内a没有let或var声明
全局作用域的代码的执行过程与第一段是一样的
let a
=> let fun = function
=> fun()
=> a=1
复制代码
但fun()函数调用的执行过程就不同了
console.log(a)
=> a=2
=> console.log(a)
复制代码
fun()
的作用域内没有任何变量或者函数的声明,所以fun()
作用域内没有预解析过程,第一句执行console.log(a)
,由于a
在fun()
作用域内没有定义,所以到fun()
作用域的上一级作用域中找,上一级中a
已经声明并且赋值为1,所以先输出1;在执行a=2
,对a赋值为2,再执行console.log(a)
,此时a = 2,所以输出2
转载于:https://juejin.im/post/5cf9cb2df265da1bc8541a5c