天天看點

javascript變量對象 函數調用棧 作用域 閉包等細解!

說明

下面代碼示範基于window系統chrome浏覽器環境,版本号為63.0.3239.132,32位!相關結果可能會有一點出入,請也實際為準!

相關代碼調試的過程中檢視結果的步驟:

  • 打開浏覽器控制台,切換到sources闆塊,并選擇相應的源檔案;
  • 在對應的源檔案代碼左邊的行号上打上斷點;
  • 然後重新整理浏覽器,浏覽器會在對應打斷點的代碼出停止執行,此時我們根據需要按f11鍵一步一步的運作代碼,并同時檢視代碼的調用棧,作用域等情況,主要檢視source闆塊下最右邊子闆塊的Call Stack和Scope項。

https://juejin.im/entry/5b2ba0a8f265da59a36e3ae7#%E7%9B%B8%E5%85%B3%E6%A6%82%E5%BF%B5%E6%A2%B3%E7%90%86 相關概念梳理

其實從我自身出發,我覺的如果需要更好地了解變量對象的話,那麼需要對以下概念有一個比較基本的了解會更友善些!

  • 函數調用棧

為了了解函數調用棧,我們先寫一段代碼:

function fn1(){
    console.log('fn1');
    fn2();
  };
  function fn2(){
    console.log('fn2');
  };
  fn1();           

我們在fn2函數調用的地方打上斷點,然後重新整理浏覽器,檢視Call Stack選項,會看到下面的結果:

fn1
  (anonymous)           

此時再按一次f11鍵,此時Call Stack顯示結果如下:

fn2
  fn1
  (anonymous)           

這裡說明一下,anonymous指代全局匿名調用函數環境,而fn1和fn2分别指代fn1函數作用域和fn2作用域環境。

我們梳理一下浏覽器的調用過程:1 js進入全局匿名函數環境,把這個所謂的匿名函數推入調用棧;2 發現此時又調用了fn1,于是把fn1函數推入調用棧,此時fn1在anonymous的上面; 3 緊接着,發現調用了fn2,于是把fn2推入調用棧,于是得到了上面的結果。

如果後續我們繼續按f11鍵調試,會發現Call Stack會依次出現先面的結果:

fn2
  fn1
  (anonymous)           
fn1
  (anonymous)           
(anonymous)           

也就是說最後隻剩下了全局匿名函數環境,這裡強調一下,anonymous環境将會伴随着程式運作一直存在,除非你關閉了浏覽器。

于是我們總結得到這樣的結果:存在這樣一個調用棧,預設推入一個全局匿名函數在棧底,當此時再調用其它全局函數的時候,會把該函數推入棧,并在anonymous上面,如果該函數内部繼續調用了其它函數,那麼同樣道理,會把其它函數推入棧,放在該函數上面,最後當函數在調用完成的過程中,會依次退出該調用棧,退出的過程中會把權限交給上一層函數,最後又回歸了隻剩下全局匿名函數環境。于是我們說了這麼多,其實這就是函數調用棧!

函數調用棧你可以了解為函數調用前後包含關系:先進後出,後進先出!它描述了代碼執行的先後順序以及目前代碼執行控制權限的擁有者關系等。

  • 函數作用域

我們都知道javascript是沒有塊級作用域的,隻有函數作用域,怎麼了解?我們看下面的代碼:

代碼1

for(var k = 0;k<10;k++){
  //...
};
console.log(k);//輸出10           

我們發現for循環代碼塊執行完了之後,依然能得到k的值。再看下面的代碼:

代碼2

function fn3(){
  var a = 'a';
};
fn3();
console.log(a);//報錯 a is not defined           

我們發現在函數裡面定義的變量a,在函數外面是拿不到的,這就是函數作用域能做到的。

函數作用域能讓我們定義一些函數内部使用的與外部環境同名的變量而不會跟外部環境沖突,我們用的較多的地方就是即時函數,如下:

var a = 'outer';
(function(){
  var a = 'inner';
  conosle.log(a);//輸出inner
})();
console.log(a);//輸出outer           

當然了在es6及後續版本javascript中,我們可以使用let和const标志符來定義塊級變量,這裡不作讨論!

這裡再做一下擴充,作用域中涉及到最多的一個概念就是作用域鍊,這個是什麼意思呢!看下面的代碼:

let a = 'a';
function fn4(){
  let b = 'b';
  return a+b;
};
let c = fn4();
console.log(c);//輸出'ab'           

作用域鍊描述的是一種函數在執行的過程中查找變量的方式,具體來說:函數執行,如果遇到某變量,會首先在自身作用域環境查找改變量,如果不存在,會向上一層作用域查找變量,也就是函數調用棧中目前執行函數的下一層函數環境查找變量,依次類推,直到到全局環境中查找,如果在全局中都沒有找到變量的話,那麼就會報錯!

其實原型鍊也是一種描述執行個體屬性查找的過程,跟作用域鍊類似!

  • 閉包

恐怕接觸過javascript的開發者,聽到最多與其有關的概念就是閉包了。而且網上有很多文章和書籍都對閉包進行了說明,其實我覺的了解閉包并不難哈!我們來看段代碼吧:

function fn5(){
  let a = 0;
  return function(){
    a++;
    console.log(a);
  };
};
let fn = fn5();
fn();           

我們在fn調用的地方打一個斷點,随後按一次f11鍵進入fn執行環境,看下Scope項結果,會發現Scope下有一項子項:

Closure(fn5)
|_ a           

我可以明确的告訴你,此時的fn5對于fn來說就是一個閉包(你可以了解是一個環境概念,該環境維護了一些内部傳回的匿名函數用到的一些外部變量)!

梳理一下,閉包指得是某個函數調用之後,自身執行環境已不複存在,但傳回了一個函數,由于該傳回函數内部用到了外部函數裡面的一些變量,并又該内部函數又指派給了其它變量,導緻雖然外部函數不存在了,但是它引用的那些外部變量卻不能回收的一個環境(閉包)。

閉包用好了,可以保證好多變更的作用域周期得以提升,減少變量命名沖突,但是過多的使用閉包,也會存在記憶體洩漏的問題,因為你的很多變量都沒有被垃圾回收器回收。

https://juejin.im/entry/5b2ba0a8f265da59a36e3ae7#%E8%BF%9B%E5%85%A5%E6%AD%A3%E9%A2%98%E5%8F%98%E9%87%8F%E5%AF%B9%E8%B1%A1 進入正題,變量對象

為了讓你對變量對象整體有個最初的概念。在詳細介紹之前,我對變量對象概念作如此表述:變量對象指函數執行過程中,函數自身用到資料從哪裡來的,函數怎麼管理這些資料的等等,其實變量對象裡面儲存了函數在執行過程中所有用到的資料以及某個時刻值等!

時間倉促,後續待更新……

原文釋出時間為:2018年06月21日

原文作者:掘金

本文來源: 

掘金 https://juejin.im/entry/5b3a29f95188256228041f46

如需轉載請聯系原作者

繼續閱讀