JavaScript之深入函數(一)

来源:https://www.cnblogs.com/ruhaoren/archive/2019/09/04/11459963.html
-Advertisement-
Play Games

在任何編程語言中,函數的功能都是十分強大的,JavaScript也不例外。之前已經講解了函數的一些基本知識,諸如函數定義,函數執行和函數返回值等,今天就帶大家深入瞭解JavaScript中函數的原理及執行過程。 一 函數參數 1,聲明函數時可以添加參數,相當於在函數內部隱式的聲明瞭變數。它的學名叫形 ...


  在任何編程語言中,函數的功能都是十分強大的,JavaScript也不例外。之前已經講解了函數的一些基本知識,諸如函數定義,函數執行和函數返回值等,今天就帶大家深入瞭解JavaScript中函數的原理及執行過程。

 

一   函數參數

  1,聲明函數時可以添加參數,相當於在函數內部隱式的聲明瞭變數。它的學名叫形式參數,簡稱形參,在執行函數使實際傳遞的值叫實際參數,簡稱實參。

1 function add(a,b){
2     return a+b;
3 }
4 add(1,2);//3
5 //a和b就是形參,1和2就是實參

  2,JS中函數的參數不限制個數和數據類型,這意味著函數的形參和實參個數可以不相等,都可以是無限多個。

1 function add(a,b){
2     return a + b;
3 }
4 add(1);//NaN    1+undefined
5 add(1,2,3);//3   忽略3
6 add(1,"3");//"13"

  3,函數甚至可以沒有形參,函數的arguments屬性,以類數組的形式存儲了函數執行時的實參。

  4,函數有一個length屬性,表示函數的形參個數。

1 function test(){
2     console.log(arguments);
3     console.log(test.length);
4 }
5 test(1,2,3);
6 //[0:1,1:2,2:3,length:3,...]
7 //0

  5,arguments中的實參和形參是相互綁定的,修改其中一個,另一個也會改變,但形參和實參實際是兩個變數。實際上實參列表的個數是不可更改的,函數執行時傳遞幾個就是幾個。

 1  function test(a,b){
 2      console.log(arguments);
 3      console.log(b);
 4      b = 2;
 5      console.log(arguments);
 6      console.log(b);
 7      a = 10;
 8      console.log(arguments);
 9  }
10  test(1);
11 /*
12 [0:1,length:1,...]
13 undefined
14 [0:1,length:1,...]
15 2
16 [0:10,length:1,...]
17 */

 

 

二   預編譯

         前面介紹JavaScript時,提到它是單線程,解釋性語言,即讀到一行就執行一行。其實這隻是它的表象,實際上JavaScript執行代碼分為了3個大的步驟:

  1,  語法分析

  語法分析的工作大體就是檢測是否有語法錯誤,是否符合版本規則等等。如果沒有問題則進入預編譯階段,如果遇到問題則會拋出錯誤。

  2,  預編譯

  在理解預編譯之前,我們應該先明白兩個概念:

    a:如果變數未申明即訪問將報錯,但是變數未聲明即賦值,那麼該變數將自動升級成全局對象(window)的屬性。

    b:在全局申明的變數,也會自動升級成window的屬性。

1 console.log(a);//Reference error:a is not defined
2 *************************************************
3 a = 10;//10
4 a === window.a;//true
5 var b;
6 b === window.b;//true

  程式預編譯發生在即將執行之前,分為三步:

    1)  創建一個GO(Global Object)對象(也稱為全局作用域),實際上就是window對象。

    2)  查找變數聲明,並把他們作為GO對象的屬性,值為undefined。

    3)  查找是否有函數聲明,若有,則把函數名作為GO對象的屬性,並把函數體賦值給該屬性,若函數名和變數名相同,則會覆蓋他們。

  函數也有預編譯過程,函數的預編譯發生在函數即將執行之前,分為四步:

    1)  創建一個AO(Active Object)對象(即執行期上下文,也叫函數作用域或本地作用域)。

    2)  查找形參和變數聲明,並把他們作為AO對象的屬性,值為undefined。

    3)  將實參賦值給形參。

    4)  查找函數內部是否有函數聲明,若有,則把函數名作為AO對象的屬性,並把函數體賦值給該屬性,若函數名和形參或變數名相同,則會覆蓋他們。

  總結下來,可以簡單概括為:變數聲明時,聲明提升。函數聲明時,整體提升。函數聲明優先順序大於變數和形參。

 1 function fn(a) {
 2       console.log(a); //ƒ a() {}
 3       console.log(b); //undefined
 4       console.log(c); //ƒ c() {}
 5       var a = 123;
 6       console.log(a); //123
 7       function a() {}
 8       console.log(a); //123
 9       var b = function b() {};
10       console.log(b); //ƒ b() {}
11       function c() {}
12 }
13 fn(1);
14 /*
15 GO  -->  {fn:fn}
16 
17 AO
18 第一步:AO  -->  {}
19 第二步:AO  -->  {a:undefined,b:undefined}
20 第三步:AO  -->  {a:1,b:undefined}
21 第四步:AO  -->  {a:function a() {},b:undefined,c:function c() {}}
22 
23 註意這裡只是預編譯過程,函數真正執行時,AO中的屬性值會動態改變。所以:
24 第一行代碼直接列印fn a
25 第二行列印undefined
26 第三行列印fn c
27 第四行聲明變數a已經被提前執行了,這裡直接賦值123
28 第五行列印123
29 第六行函數聲明被整體提前了,這一行代碼將被直接跳過
30 第七行依然列印123
31 第八行只執行賦值操作,b == fn b
32 第九行則列印fn b
33 第十行聲明函數已經被整體提升了
34 */

  3,  解釋執行

  根據預編譯後的代碼順序,一條一條的執行。

 

三        作用域和作用域鏈

         每一個函數都有一個隱式的屬性[[scope]],它存儲的是函數在執行時創建的執行期上下文集合,即一堆AO和GO對象,當然他們是有順序的,類似一個數組,它只能被系統調用,而不能被我們訪問和使用。

         當一個函數在全局被創建時(沒有被執行,這時還沒有產生它自身的AO對象),[[scope]]將被插入GO對象。當函數執行時(這時已經創建了自己的AO對象),[[scope]]的頭部將被插入一個自己的AO對象,類似數組的unshift()方法。現在[[scope]]中已經有兩個執行期上下文的對象了:第0位的AO,第1位的GO。

1 function fn(){}
2 //fn.[[scope]]  -->  {0:GO}
3 fn();
4 //fn.[[scope]]  -->  {0:AO,1:GO}

         如果全局函數的內部定義了一個子函數,那麼該子函數的[[scope]]屬性類似的會存儲:第0位父函數的AO,第1位GO(因為只有父函數執行才會產生子函數的定義,所以子函數被定義時就已經有兩個執行期上下文對象了),這時子函數還沒被執行,所以它只會存儲這兩個對象。當他被執行時,子函數[[scope]]屬性的頭部將被插入它自己的AO對象。如果子函數內部還定義的有其他函數,那麼它的[[scope]]屬性生成方式和上面相同。

1 function fn(){
2     function son(){};
3 }
4 //fn.[[scope]]  -->  {0:GO}
5 //son.[[scope]] --> {} fn還沒執行,son都還沒聲明
6 fn();
7 //fn.[[scope]]  -->  {0:AO(fn),1:GO}
8 //son.[[scope]]  -->  {0:AO(fn),1:GO} 這裡只是聲明瞭函數son,所以並沒有AO(son),如果function son(){}後面還有一行代碼:son();那麼當執行到這一行時,son.[[scope]]  -->  {0:AO(son),1:AO(fn),2:GO}

  這樣就形成了函數的作用域鏈。當我們在函數內部訪問變數時,實際上是在函數的[[scope]]屬性里依次查找(從第0位開始),直到全局GO(window)對象。函數作用域鏈的最終表現就是:子函數可以訪問父函數的變數,父函數不能訪問子函數的變數。

  每次函數執行產生的執行期上下文都是獨一無二的,當函數執行完畢,它自己的AO將被永遠銷毀,並更新自己的[[scope]]屬性。下一次執行將產生一個新的AO對象,並添加到[[scope]]屬性中。

 

  下一次將更新將介紹JavaScript函數中另外兩個重要的應用:立即執行函數和閉包。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • easyui中formatter的用法 當我們使用easyui需要對某一列進行格式化處理value數據時,可以使用formatter進行格式化 商品類型值為0時,前端將顯示“主體商品”。 商品類型值為1時,前端將顯示“附屬商品”。 value:欄位值 row:行記錄數據 index:行所在索引 js ...
  • 1.不管什麼語言,上來就應該是數據類型了。js也不例外。那麼基本的數據類型我們有,boolean, number, string, null, undefine, symbol, object, function. 2. 有了基本類型,那麼我們怎麼去判斷一個變數的類型尼? 3. 如何去判斷是否是一個 ...
  • Vue 組件系統 vue.js既然是框架,那就不能只是簡單的完成數據模板引擎的任務,它還提供了頁面佈局的功能。本文詳細介紹使用vue.js進行頁面佈局的強大工具,vue.js組件系統。 每一個新技術的誕生,都是為瞭解決特定的問題。組件的出現就是為瞭解決頁面佈局等等一系列問題。vue中的組件分為兩種, ...
  • 用easyui從servlet傳遞json數據到前端頁面的兩種方法 兩種方法獲取的數據在servlet層傳遞的方法相同,下麵為Servlet中代碼,以查詢表中所有信息為例。 通過easyui包含的table標簽中的屬性來獲取後端傳遞的數據 jsp代碼: url:傳遞數據的地址(本篇使用的是servl ...
  • 今天實現網站註銷功能時,需要清除cookie緩存,開始在網上搜索的是“js清除緩存”,發現很多都是預先防患緩存存儲的內容,千篇一律,不過也學習到了;後來換成"js清除cookie"才找到自己想要的結果。 先學習一下預先防治緩存存儲的方式吧 在http中,控制緩存開關的欄位有兩個:Pragma 和 C ...
  • ### es5新增的數組的api + indexOf() 搜索數組中的元素,並返回它所在的位置。 arr.indexOf(str,index) 參數: str為要查找的字元串 index為開始查找的下標 , index可省略 查找字元串a在數組中的位置,返回值為a在數組中第一次出現的位置的下標,如果 ...
  • 網上各種言論說 React 上手比 Vue 難,可能難就難不能深刻理解 JSX,或者對 ES6 的一些特性理解得不夠深刻,導致覺得有些點難以理解,然後說 React 比較難上手,還反人類啥的,所以我打算寫兩篇文章來講新手學習 React 的時候容易迷惑的點寫出來,如果你還以其他的對於學習 React ...
  • 前言 Odoo 是一個開源框架,針對 ERP 的需求發展而來,適合定製出符合客戶各種需求的 ERP 系統和電子商務系統 但是正因為是框架,且是一個集成框架,別人的界面與代碼早已完成 所以在別人的代碼上修改(二次開發),對我來說是一個很大的挑戰 odoo前端的組成 odoo運用的框架 odoo前端是一 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...