執行上下文就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。html
執行上下文的生命週期包括三個階段:建立階段→執行階段→回收階段,咱們重點介紹建立階段。瀏覽器
建立階段(當函數被調用,但未執行任何其內部代碼以前)會作如下三件事:安全
建立變量對象:首先初始化函數的參數arguments,提高函數聲明和變量聲明。閉包
建立做用域鏈函數
肯定this指向this
function test(arg){ // 1. 形參 arg 是 "hi" // 2. 由於函數聲明比變量聲明優先級高,因此此時 arg 是 function console.log(arg); var arg = 'hello'; // 3.var arg 變量聲明被忽略, arg = 'hello'被執行 function arg(){ console.log('hello world') } console.log(arg); } test('hi'); /* 輸出: function arg() { console.log('hello world'); } hello */
這是由於當函數執行的時候,首先會造成一個新的私有的做用域,而後依次按照以下的步驟執行:spa
若是有形參,先給形參賦值線程
進行私有做用域中的預解釋,函數聲明優先級比變量聲明高,最後後者會被前者所覆蓋,可是能夠從新賦值code
私有做用域中的代碼從上到下執行htm
函數多了,就有多個函數執行上下文,每次調用函數建立一個新的執行上下文,那如何管理建立的那麼多執行上下文呢?
JavaScript 引擎建立了執行棧來管理執行上下文。能夠把執行棧認爲是一個存儲函數調用的棧結構,遵循先進後出的原則。
//引用 慕課手記 的圖示來示例一下:
console.log(1); function pFn() { console.log(2); (function cFn() { console.log(3); }()); console.log(4); } pFn(); console.log(5); //輸出:1 2 3 4 5
從上面的流程圖,咱們須要記住幾個關鍵點:
JavaScript執行在單線程上,全部的代碼都是排隊執行。
一開始瀏覽器執行全局的代碼時,首先建立全局的執行上下文,壓入執行棧的頂部。
每當進入一個函數的執行就會建立函數的執行上下文,而且把它壓入執行棧的頂部。當前函數執行完成後,當前函數的執行上下文出棧,並等待垃圾回收。
瀏覽器的JS執行引擎老是訪問棧頂的執行上下文。
全局上下文只有惟一的一個,它在瀏覽器關閉時出棧。
ES6 到來JavaScript 有全局做用域、函數做用域和塊級做用域(ES6新增)。
咱們能夠這樣理解:做用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。也就是說做用域最大的用處就是隔離變量,不一樣做用域下同名變量不會有衝突。
函數做用域:顧名思義就是在這個函數體裏邊才能訪問的變量;固然能夠利用閉包來實現跨區域訪問局部做用域的變量;查看
塊級做用域:ES6新增,用let命令新增了塊級做用域,外層做用域沒法獲取到內層做用域,很是安全明瞭。即便外層和內層都使用相同變量名,也都互不干擾;
接下來咱們再來了解下自由變量(也就是全局變量);
以下代碼中,console.log(a) 要獲得a變量,可是在當前的做用域中沒有定義a(可對比一下b)。當前做用域沒有定義的變量,這成爲 自由變量。
var a = 100 function fn() { var b = 200 console.log(a) // 這裏的a在這裏就是一個自由變量 100 console.log(b) // 200 } fn()
接下來再看一個示例:
function F1() { var a = 100 return function () { console.log(a) } } function F2(f1) { var a = 200 console.log(f1()) } var f1 = F1() F2(f1) // 100
上述代碼中,自由變量a的值,從函數F1中查找而不是F2,這是由於當自由變量從做用域鏈中去尋找,依據的是函數定義時的做用域鏈,而不是函數執行時。
那麼自由變量的值如何獲得 ? —— 向父級做用域 (建立該函數的那個父級做用域)尋找。
若是父級也沒呢?再一層一層向上尋找,直到找到全局做用域仍是沒找到,就宣佈放棄。這種一層一層的關係,就是做用域鏈 。