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

相關文章
相關標籤/搜索