javascript之做用域与预解析

js之预解析

在谈js的预解析以前,先看一段c++程序ios

#include <iostream>
using namespace std;

void useGreet(){
    greet();
}

void greet(){
    cout << "hello" <<endl;
}

int main() {
    useGreet();
    return 0;
}
复制代码

只要是有c++基础的都会知道以上程序是没法经过编译的,由于useGreet()函数中调用了一个未声明的函数,即使greet()函数在其声明。这是c++编译器编译顺序自己决定的。 再来看用js写的一段代码c++

function useGreet(){
	greet();
}

function greet(){
	console.log('hello');
}

useGreet();
复制代码

以上代码是能够正常运行的浏览器

以上两个例子虽然与js的预解析的关系不是很大,可是也反映了js中预解析的内容函数

再来看这段js代码ui

let a = 1;
let fun = function(){
	console.log(a);
	let a = 2;
	console.log(a);
}
fun();
复制代码

代码的运行结果为spa

undefined
2
复制代码

再来看下面这段代码code

let a = 1;
let fun = function(){
	console.log(a);
	a = 2;
	console.log(a);
}
fun();
复制代码

代码的运行结果为对象

1
2
复制代码

两段看似差别十分微小的代码的运行结果是天差地别的。在解释运行结果以前,咱们须要知道js中函数和变量在内存中的存储模型,js的预解析,js的做用域链。ip

函数和变量在内存中的存储模型

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),因为afun()做用域内没有定义,因此到fun()做用域的上一级做用域中找,上一级中a已经声明而且赋值为1,因此先输出1;在执行a=2,对a赋值为2,再执行console.log(a),此时a = 2,因此输出2

相关文章
相关标签/搜索