成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入理解JavaScript的執行上下文和執行棧

原創
開發 前端
執行上下文和執行棧是JavaScript中關鍵概念之一,是JavaScript難點之一。 理解執行上下文和執行棧同樣有助于理解其他的 JavaScript 概念如提升機制、作用域和閉包等。本文盡可能用通俗易懂的方式來介紹這些概念。

【51CTO.com原創稿件】

前言

如果你是一名 JavaScript 開發者,或者想要成為一名 JavaScript 開發者,那么你必須知道 JavaScript 程序內部的執行機制。執行上下文和執行棧是 JavaScript 中關鍵概念之一,是 JavaScript 難點之一。 理解執行上下文和執行棧同樣有助于理解其他的 JavaScript 概念如提升機制、作用域和閉包等。本文盡可能用通俗易懂的方式來介紹這些概念。

一、執行上下文(Execution Context)

1.什么是執行上下文

簡而言之,執行上下文就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。

2.執行上下文的類型

執行上下文總共有三種類型:

  • 全局執行上下文: 這是默認的、最基礎的執行上下文。不在任何函數中的代碼都位于全局執行上下文中。它做了兩件事:1. 創建一個全局對象,在瀏覽器中這個全局對象就是 window 對象。2. 將 this 指針指向這個全局對象。一個程序中只能存在一個全局執行上下文。

  • 函數執行上下文: 每次調用函數時,都會為該函數創建一個新的執行上下文。每個函數都擁有自己的執行上下文,但是只有在函數被調用的時候才會被創建。一個程序中可以存在任意數量的函數執行上下文。每當一個新的執行上下文被創建,它都會按照特定的順序執行一系列步驟,具體過程將在本文后面討論。

  • Eval 函數執行上下文: 運行在 eval 函數中的代碼也獲得了自己的執行上下文,但由于 Javascript 開發人員不常用 eval 函數,所以在這里不再討論。

二、執行上下文的生命周期

執行上下文的生命周期包括三個階段:創建階段→執行階段→回收階段,本文重點介紹創建階段。

1.創建階段

當函數被調用,但未執行任何其內部代碼之前,會做以下三件事:

  • 創建變量對象:首先初始化函數的參數 arguments,提升函數聲明和變量聲明。下文會詳細說明。

  • 創建作用域鏈(Scope Chain):在執行期上下文的創建階段,作用域鏈是在變量對象之后創建的。作用域鏈本身包含變量對象。作用域鏈用于解析變量。當被要求解析變量時,JavaScript 始終從代碼嵌套的最內層開始,如果最內層沒有找到變量,就會跳轉到上一層父作用域中查找,直到找到該變量。

  • 確定 this 指向:包括多種情況,下文會詳細說明。

在一段 JS 腳本執行之前,要先解析代碼(所以說 JS 是解釋執行的腳本語言),解析的時候會先創建一個全局執行上下文環境,先把代碼中即將執行的變量、函數聲明都拿出來。變量先暫時賦值為 undefined,函數則先聲明好可使用。這一步做完了,然后再開始正式執行程序。

另外,一個函數在執行之前,也會創建一個函數執行上下文環境,跟全局上下文差不多,不過函數執行上下文中會多出 this arguments 和函數的參數。

2.執行階段

執行變量賦值、代碼執行。

3.回收階段

執行上下文出棧等待虛擬機回收執行上下文。

三、變量提升和 this 指向的細節

1.變量聲明提升

大部分編程語言都是先聲明變量再使用,但在 JS 中,事情有些不一樣:

  1. console.log(a)// undefined 
  2. var a = 10 

上述代碼正常輸出undefined而不是報錯Uncaught ReferenceError: a is not defined,這是因為聲明提升(hoisting),相當于如下代碼:

  1. var a; //聲明 默認值是undefined “準備工作” 
  2. console.log(a); 
  3. a=10; //賦值 

2.函數聲明提升

我們都知道,創建一個函數的方法有兩種,一種是通過函數聲明function foo(){} 另一種是通過函數表達式var foo = function(){} ,那這兩種在函數提升有什么區別呢?

  1. console.log(f1) // function f1(){} 
  2. function f1() {} // 函數聲明 
  3. console.log(f2) // undefined 
  4. var f2 = function() {} // 函數表達式 

接下來我們通過一個例子來說明這個問題:

  1. function test() { 
  2.   foo(); // Uncaught TypeError "foo is not a function" 
  3.   bar(); // "this will run!" 
  4.   var foo = function () { // function expression assigned to local variable 'foo' 
  5.       alert("this won't run!"); 
  6.   } 
  7.   function bar() { // function declaration, given the name 'bar' 
  8.       alert("this will run!"); 
  9.   } 
  10. test(); 

在上面的例子中,foo()調用的時候報錯了,而bar能夠正常調用。

我們前面說過變量和函數都會上升,遇到函數表達式 var foo = function(){}時,首先會將var foo上升到函數體頂部,然而此時的foo的值為undefined,所以執行foo()報錯。

而對于函數bar(), 則是提升了整個函數,所以bar()才能夠順利執行。

有個細節必須注意:當遇到函數和變量同名且都會被提升的情況,函數聲明優先級比較高,因此變量聲明會被函數聲明所覆蓋,但是可以重新賦值。

  1. alert(a);//輸出:function a(){ alert('我是函數') } 
  2. function a(){ alert('我是函數') }// 
  3. var a = '我是變量'
  4. alert(a);   //輸出:'我是變量' 

function 聲明的優先級比 var 聲明高,也就意味著當兩個同名變量同時被 function 和 var 聲明時,function 聲明會覆蓋 var 聲明。

這代碼等效于:

  1. function a(){alert('我是函數')}  
  2. var a;   //hoisting 
  3. alert(a);   //輸出:function a(){ alert('我是函數') } 
  4. a = '我是變量';//賦值 
  5. alert(a);   //輸出:'我是變量' 

***我們看個復雜點的例子:

  1. function test(arg){ 
  2.   // 1. 形參 arg 是 "hi" 
  3.   // 2. 因為函數聲明比變量聲明優先級高,所以此時 arg 是 function 
  4.   console.log(arg);   
  5.   var arg = 'hello'; // 3.var arg 變量聲明被忽略, arg = 'hello'被執行 
  6.   function arg(){ 
  7. console.log('hello world')  
  8.   } 
  9.   console.log(arg);   
  10. test('hi'); 
  11. /* 輸出: 
  12. function a() { 
  13.   console.log('fun'); 
  14.   } 
  15. hello  
  16. */ 

這是因為當函數執行的時候,首先會形成一個新的私有的作用域,然后依次按照如下的步驟執行:

  • 如果有形參,先給形參賦值。

  • 進行私有作用域中的預解釋,函數聲明優先級比變量聲明高,***后者會被前者所覆蓋,但是可以重新賦值。

  • 私有作用域中的代碼從上到下執行。

3.確定this的指向

先搞明白一個很重要的概念 —— this 的值是在執行的時候才能確認,定義的時候不能確認! 為什么呢 —— 因為 this 是執行上下文環境的一部分,而執行上下文需要在代碼執行之前確定,而不是定義的時候??慈缦吕樱?/span>

  1. // 情況1 
  2. function foo() { 
  3. console.log(this.a) //1 
  4. var a = 1 
  5. foo() 
  6. ​ 
  7. // 情況2 
  8. function fn(){ 
  9. console.log(this); 
  10. var obj={fn:fn}; 
  11. obj.fn(); //this->obj 
  12. ​ 
  13. // 情況3 
  14. function CreateJsPerson(name,age){ 
  15. //this是當前類的一個實例p1 
  16. this.name=name; //=>p1.name=name 
  17. this.age=age; //=>p1.age=age 
  18. var p1=new CreateJsPerson("尹華芝",48); 
  19. ​ 
  20. // 情況4 
  21. function add(c, d){ 
  22. return this.a + this.b + c + d; 
  23. var o = {a:1, b:3}; 
  24. add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 
  25. add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34 
  26. ​ 
  27. // 情況5 
  28. <button id="btn1">箭頭函數this</button> 
  29. <script type="text/javascript">    
  30.   let btn1 = document.getElementById('btn1'); 
  31.   let obj = { 
  32.       name'kobe'
  33.       age: 39, 
  34.       getName: function () { 
  35.           btn1.onclick = () => { 
  36.               console.log(this);//obj 
  37.           }; 
  38.       } 
  39.   }; 
  40.   obj.getName(); 
  41. </script> 

接下來我們逐一解釋上面幾種情況

  • 對于直接調用 foo 來說,不管 foo 函數被放在了什么地方,this 一定是 window。

  • 對于 obj.foo() 來說,我們只需要記住,誰調用了函數,誰就是 this,所以在這個場景下 foo 函數中的 this 就是 obj 對象。

  • 在構造函數模式中,類中(函數體中)出現的 this.xxx=xxx 中的 this 是當前類的一個實例。

  • call、apply和bind:this 是***個參數。

  • 箭頭函數 this 指向:箭頭函數沒有自己的 this,看其外層的是否有函數,如果有,外層函數的this就是內部箭頭函數的 this,如果沒有,則 this 是 window。

四、執行上下文棧(Execution Context Stack)

函數多了,就有多個函數執行上下文,每次調用函數創建一個新的執行上下文,那如何管理創建的那么多執行上下文呢?

JavaScript 引擎創建了執行上下文棧來管理執行上下文。可以把執行上下文棧認為是一個存儲函數調用的棧結構,遵循先進后出的原則。

從上面的流程圖,我們需要記住幾個關鍵點:

  • JavaScript 執行在單線程上,所有的代碼都是排隊執行。

  • 一開始瀏覽器執行全局的代碼時,首先創建全局的執行上下文,壓入執行棧的頂部。

  • 每當進入一個函數的執行就會創建函數的執行上下文,并且把它壓入執行棧的頂部。當前函數執行完成后,當前函數的執行上下文出棧,并等待垃圾回收。

  • 瀏覽器的 JS 執行引擎總是訪問棧頂的執行上下文。

  • 全局上下文只有唯一的一個,它在瀏覽器關閉時出棧。

我們再來看個例子:

  1. var color = 'blue'
  2. function changeColor() { 
  3.   var anotherColor = 'red'
  4.   function swapColors() { 
  5.       var tempColor = anotherColor; 
  6.       anotherColor = color; 
  7.       color = tempColor; 
  8.   } 
  9.   swapColors(); 
  10. changeColor(); 

上述代碼運行按照如下步驟:

  • 當上述代碼在瀏覽器中加載時,JavaScript 引擎會創建一個全局執行上下文并且將它推入當前的執行棧。

  • 調用 changeColor 函數時,此時 changeColor 函數內部代碼還未執行,js 執行引擎立即創建一個 changeColor 的執行上下文(簡稱 EC),然后把這執行上下文壓入到執行棧(簡稱 ECStack)中。

  • 執行 changeColor 函數過程中,調用 swapColors 函數,同樣地,swapColors 函數執行之前也創建了一個 swapColors 的執行上下文,并壓入到執行棧中。

  • swapColors 函數執行完成,swapColors 函數的執行上下文出棧,并且被銷毀。

  • changeColor 函數執行完成,changeColor 函數的執行上下文出棧,并且被銷毀。

參考文章

 浪里行舟,慕課網認證作者,前端愛好者,立志往全棧工程師發展,從事前端一年多,目前技術棧有vue全家桶、ES6以及less等,樂于分享,最近一年寫了五六十篇原創技術文章,得到諸多好評!

【51CTO原創稿件,合作站點轉載請注明原文作者和出處為51CTO.com】 

 

責任編輯:龐桂玉 來源: 51CTO
相關推薦

2020-07-24 10:00:00

JavaScript執行上下文前端

2022-09-14 13:13:51

JavaScript上下文

2019-05-06 14:36:48

CPULinux寄存器

2021-09-07 09:53:42

JavaScript變量提升

2020-09-28 08:44:17

Linux內核

2021-04-20 23:25:16

執行函數變量

2021-05-27 07:02:05

JavaScript代碼設施

2021-09-26 09:59:14

MYSQL開發數據庫

2017-05-11 14:00:02

Flask請求上下文應用上下文

2025-05-12 00:00:15

2021-02-17 11:25:33

前端JavaScriptthis

2012-07-18 11:39:18

ibmdw

2020-06-22 08:41:34

JS語言代碼

2017-03-28 21:39:41

ErrorsStack trace代碼

2017-04-25 15:30:23

堆棧函數JavaScript

2024-07-18 10:12:04

2015-11-04 09:57:18

JavaScript原型

2023-05-05 07:41:42

執行上下文JavaScript

2012-12-31 10:01:34

SELinuxSELinux安全

2020-12-16 09:47:01

JavaScript箭頭函數開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 污免费网站 | 日韩免费网站 | 日韩视频高清 | 国产精品久久毛片av大全日韩 | 成人午夜免费视频 | 性欧美精品一区二区三区在线播放 | 国精产品一品二品国精在线观看 | 91五月天| 国产一二三区精品视频 | 韩国精品在线观看 | 在线成人免费视频 | 日韩精品一区二区三区老鸭窝 | 久久精品| 午夜丰满少妇一级毛片 | 久久鲁视频 | 久久尤物免费一区二区三区 | 国产精品美女久久久久久免费 | 日韩中文一区二区三区 | 欧美狠狠操 | 亚洲美女在线视频 | a亚洲精品 | 亚洲欧美一区二区三区情侣bbw | 欧美视频网 | 亚洲视频在线观看免费 | 国产日韩精品在线 | 国产乱码精品一区二区三区五月婷 | 亚洲视频中文字幕 | 综合久久亚洲 | 免费黄色的视频 | 久久久久国产一区二区三区四区 | 成人免费看电影 | 综合二区 | 日韩综合一区 | 久产久精国产品 | 中文字幕乱码一区二区三区 | 久久亚洲国产精品 | 国产高清视频在线播放 | 亚洲欧洲精品成人久久奇米网 | 老司机午夜性大片 | 国产欧美日韩在线观看 | 日韩中出|