javascript--函數定義與函數作用域--詳解

来源:http://www.cnblogs.com/Uncle-Keith/archive/2016/08/20/5789385.html
-Advertisement-
Play Games

最近在學習javascript的函數,函數是javascript的一等對象,想要學好javascript,就必須深刻理解函數。本人把思路整理成文章,一是為了加深自己函數的理解,二是給讀者提供學習的途徑,避免走彎路。內容有些多,但都是筆者對於函數的總結。 1.函數的定義 1.1:函數聲明 1.2:函數 ...


 

最近在學習javascript的函數,函數是javascript的一等對象,想要學好javascript,就必須深刻理解函數。本人把思路整理成文章,一是為了加深自己函數的理解,二是給讀者提供學習的途徑,避免走彎路。內容有些多,但都是筆者對於函數的總結。

 

 

1.函數的定義

  1.1:函數聲明

  1.2:函數表達式

  1.3:命名函數的函數表達式

  1.4:函數的重覆聲明

  1.5:不能在條件語句中聲明函數

2.函數的部分屬性和方法

  2.1:name屬性

  2.2:length屬性

  2.3:toString()方法

3.函數作用域

  3.1:全局作用域和局部作用域

  3.2:函數內部的變數提升

  3.3:函數自身的作用域

 

 

1.函數的定義

  1.1:函數聲明

  函數就是一段可以反覆調用的代碼塊。函數聲明由三部分組成:函數名,函數參數,函數體。整體的構造是function命令後面是函數名,函數名後面是一對圓括弧,裡面是傳入函數的參數。函數體放在大括弧裡面。當函數體沒有使用return關鍵字返回函數時,函數調用時返回預設的undefined;如果有使用return語句,則返回指定內容。函數最後不用加上冒號。

    function keith() {}
    console.log(keith())   // 'undefined'

    function rascal(){
        return 'rascal';
    }
    console.log(rascal())    // 'rascal'

  函數聲明是在預執行期執行的,也就是說函數聲明是在瀏覽器準備解析並執行腳本代碼的時候執行的。所以,當去調用一個函數聲明時,可以在其前面調用並且不會報錯。

1     console.log(rascal())   // 'rascal'
2     function rascal(){
3         return 'rascal';
4     }

  其實這段代碼沒有報錯的原因還有一個,就是與變數聲明提升一樣,函數名也會發生提升。函數名提升會在下麵小節談到。

 

  1.2:函數表達式

  函數表達式是把一個匿名函數賦給一個全局變數。這個匿名函數又稱為函數表達式,因為賦值語句的等號右側只能放表達式。函數表達式末尾需要加上分號,表示語句結束。

1     var keith = function() {
2         //函數體
3     };

  函數表達式與函數聲明不同的是,函數表達式是瀏覽器解析並執行到那一行才會有定義。也就是說,不能在函數定義之前調用函數。函數表達式並不像函數聲明一樣有函數名的提升。如果採用賦值語句定義函數並且在聲明函數前調用函數,JavaScript就會報錯。

1     keith();
2     var keith = function() {};
3     // TypeError: keith is not a function

  上面的代碼等同於下麵的形式。

1     var keith;
2     console.log(keith());   // TypeError: keith is not a function
3     keith = function() {};

  上面代碼第二行,調用keith的時候,keith只是被聲明瞭,還沒有被賦值,等於undefined,所以會報錯。

 

  1.3:命名函數的函數表達式

  採用函數表達式聲明函數時,function命令後面不帶有函數名。如果加上函數名,該函數名只在函數體內部有效,在函數體外部無效。

1     var keith = function boy(){
2       console.log(typeof boy);
3     };
4 
5     console.log(boy);
6     // ReferenceError: boy is not defined
7 
8     keith();
9     // function

上面代碼在函數表達式中,加入了函數名boy。這個boy只在函數體內部可用,指代函數表達式本身,其他地方都不可用。這種寫法的用處有兩個,一是可以在函數體內部調用自身,二是方便除錯(除錯工具顯示函數調用棧時,將顯示函數名,而不再顯示這裡是一個匿名函數)。

 

  1.4:函數的重覆聲明

  如果同一個函數被多次聲明,後面的聲明就會覆蓋前面的聲明。

1     function keith() {
2         console.log(1);
3     }
4     keith(); //2
5     function keith() {
6         console.log(2);
7     }
8     keith(); //2

  上面代碼中,後一次的函數聲明覆蓋了前面一次。而且,由於函數名的提升,前一次聲明在任何時候都是無效的。JavaScript引擎將函數名視同變數名,所以採用函數聲明的方式聲明函數時,整個函數會像變數聲明一樣,被提升到代碼頭部。錶面上,上面代碼好像在聲明之前就調用了函數keith。但是實際上,由於“變數提升”,函數keith被提升到了代碼頭部,也就是在調用之前已經聲明瞭。再看一個典型的例子。

 1     if (true) {
 2         function foo() {
 3             return 1;
 4         }
 5     } else {
 6         function foo() {
 7             return 2;
 8         }
 9     }
10 
11     console.log(foo()) //2

  這個例子十分典型,調用foo函數之後返回的是2,而不是1。在條件語句中聲明函數會在下麵說到。

 

  1.5:不能在條件語句中聲明函數

  參考這篇文章,原文有那麼一句話(本人翻譯):在條件語句中聲明函數是非標準結構的特征。也就是說,在if代碼塊聲明瞭函數,按照語言規範,這是不合法的。但是,實際情況是各家瀏覽器往往並不報錯,能夠運行。

  由於存在函數名的提升,所以在條件語句中聲明函數,可能是無效的。

1     if (false) {
2       function f() {}
3     }
4     console.log(f());   //undefined

  上面代碼的原始意圖是不聲明函數f,但是由於f的提升,導致if語句無效,所以上面的代碼不會報錯。要達到在條件語句中定義函數的目的,只有使用函數表達式。

1     if (false) {
2       var f = function () {};
3     }
4 
5     console.log(f()) //Uncaught TypeError: f is not a function

 

2.函數的部分屬性和方法

 

  2.1:name屬性

  name屬性返回緊跟在function關鍵字之後的那個函數名。

1     function k1() {};
2     console.log(k1.name); //'k1'
3 
4     var k2 = function() {};
5     console.log(k2.name); //''
6 
7     var k3 = function hello() {};
8     console.log(k3.name); //'hello'

  上面代碼中,name屬性返回function 後面緊跟著的函數名。對於k2來說,返回一個空字元串,註意:匿名函數的name屬性總是為空字元串。對於k3來說,返回函數表達式的名字(真正的函數名為k3,hello這個函數名只能在函數內部使用。)

 

  2.2:length屬性

  length屬性返回函數預期傳入的參數個數,即函數定義之中的參數個數。返回的是個數,而不是具體參數。

1     function keith(a, b, c, d, e) {}
2     console.log(keith.length)    // 5

  上面代碼定義了空函數keith,它的length屬性就是定義時的參數個數。不管調用時輸入了多少個參數,length屬性始終等於5。也就是說,當調用時給實參傳遞了6個參數,length屬性會忽略掉一個。

 

  2.3:toString()方法

  函數的toString方法返回函數的代碼本身。

1     function keith(a, b, c, d, e) {
2         // 這是註釋。
3     }
4     console.log(keith.toString());
5     //function keith(a, b, c, d, e) { // 這是註釋。 }

  可以看到,函數內部的註釋段也被返回了。

 

3.函數作用域

 

  3.1:全局作用域和局部作用域

  作用域(scope)指的是變數存在的範圍。Javascript只有兩種作用域:一種是全局作用域,變數在整個程式中一直存在,所有地方都可以讀取,在全局作用域中聲明的變數稱為全局變數;另一種是局部作用域,變數只在函數內部存在,此時的變數被稱為局部變數。

  在全局作用域中聲明的變數稱為全局變數,也就是在函數外部聲明。它可以在函數內部讀取。

1     var a=1;
2     function keith(){
3         return a;
4     }
5     console.log(keith())    //1

  上面代碼中,全局作用域下的函數keith可以在內部讀取全局變數a。

  在函數內部定義的變數,只能在內部訪問,外部無法讀取,稱為局部變數。註意這裡必須是在函數內部聲明的變數。

1     function keith(){
2         var a=1;
3         return a;
4     }
5     console.log(a)    //Uncaught ReferenceError: a is not defined

  在上面代碼中,變數a在函數內部定義,所以是一個局部變數,外部無法訪問。

  函數內部定義的變數,會在該作用域下覆蓋同名變數。註意以下兩個代碼段的區別。

 1     var a = 2;
 2 
 3     function keith() {
 4         var a = 1;
 5         console.log(a);
 6     }
 7     keith(); //1
 8 
 9     var c = 2;
10 
11     function rascal() {
12         var c = 1;
13         return c;
14     }
15     console.log(c); //2
16   console.log(rascal()); //1

  上面代碼中,變數a和c同時在函數的外部和內部有定義。結果,在函數內部定義,局部變數a覆蓋了全局變數a

   註意,對於var命令來說,局部變數只能在函數內部聲明。在其他區塊聲明,一律都是全局變數。比如說if語句。

1     if (true) {
2         var keith=1;
3     }
4     console.log(keith);    //1

  從上面代碼中可以看出,變數keith在條件判斷區塊之中聲明,結果就是一個全局變數,可以在區塊之外讀取。但是這裡如果採用ES6中let關鍵字,在全局作用域下是無法訪問keith變數的。

 

  3.2:函數內部的變數聲明提升

  與全局作用域下的變數聲明提升相同,局部作用域下的局部變數在函數內部也會發生變數聲明提升。var命令聲明的變數,不管在什麼位置,變數聲明都會被提升到函數體的頭部。

 1     function keith(a) {
 2         if (a > 10) {
 3             var b = a - 10;
 4         }
 5     }
 6 
 7     function keith(a) {
 8         var b;
 9         if (a > 10) {
10             b = a - 10;
11         }
12     }

  上面兩個函數段是相同的。

 

  3.3:函數本身的作用域

  函數本身也是一個值,也有自己的作用域。它的作用域與變數一樣,就是其聲明時所在的作用域,與其運行時所在的作用域無關。

 1     var a = 1;
 2     var b = function() {
 3         console.log(a);
 4     };
 5     function c() {
 6         var a = 2;
 7         b();
 8     }
 9     c(); //1
10 
11     var a = 1;
12     var b = function() {
13         return a;
14     };
15     function c() {
16         var a = 2;
17         return b();
18     }
19     console.log(c()); //1

  以上兩個代碼段相同。函數b是在函數c外部聲明的。所以它的作用域綁定在函數外層,內部函數a不會到函數c體內取值,所以返回的是1,而不是2。

  很容易犯錯的一點是,如果函數A調用函數B,卻沒考慮到函數B不會引用函數A的內部變數。

 1     var b = function() {
 2         console.log(a);
 3     };
 4     function c(f) {
 5         var a = 1;
 6         f();
 7     }
 8     c(b); //Uncaught ReferenceError: a is not defined
 9 
10 
11     var b = function() {
12         return a;
13     };
14     function c(f) {
15         var a = 1;
16         return f();
17     }
18     console.log(c(b)); //Uncaught ReferenceError: a is not defined

  上面代碼將函數b作為參數,傳入函數c。但是,函數b是在函數c體外聲明的,作用域綁定外層,因此找不到函數c的內部變數a,導致報錯。

   同樣的,函數體內部聲明的變數,作用域綁定在函數體內部。

 1     function keith() {
 2         var a = 1;
 3 
 4         function rascal() {
 5             console.log(a);
 6         }
 7         return rascal;
 8     }
 9 
10     var a = 2;
11     var f = keith();
12     f(); //1

  上面代碼中,函數keith內部聲明瞭rascal變數。rascal作用域綁定在keith上。當我們在keith外部取出rascal執行時,變數a指向的是keith內部的a,而不是keith外部的a。這裡涉及到函數另外一個重要的知識點,即在一個函數內部定義另外一個函數,也就是閉包的概念。下次有機會會分享。

  總之,函數執行時所在的作用域,是定義時的作用域,而不是調用時所在的作用域。

 

 

 

 

 

 

  完。

  感謝大家的閱讀。

 


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

-Advertisement-
Play Games
更多相關文章
  • 1、字體屬性:①字體格式:font-family:取值:“microsoft yahei”/Arial……;②字體大小:font-size:取值:pt/px;③字體加粗:font-weight:取值:normal(預設值)/bold(粗體,hn,b,strong的預設值)/400-900;④字體樣式 ...
  • 一、html概述 htyper text markup language 即超文本標記語言 超文本: 就是指頁面內可以包含圖片、鏈接,甚至音樂、程式等非文字元素。 標記語言: 標記(標簽)構成的語言. 網頁==HTML文檔,由瀏覽器解析,用來展示的 靜態網頁:靜態的資源,如xxx.html 動態網頁 ...
  • 一、什麼是JavaScrip JavaScript是一種動態類型、弱類型、基於原型的客戶端腳本語言。它的解釋器被稱為JavaScript引擎,為瀏覽器的一部分,廣泛用於客戶端的腳本語言,在HTML網頁上使用,用來給HTML網頁增加動態功能。 動態: 在運行時確定數據類型,通常變數的類型取決於值的類型 ...
  • html、javascript會涉及到三個解析器,html解析器、xml解析器、javascript解析器。那麼好了,問題來了,以上代碼經常混編在一起,各自有各自的規則,終究會有衝突的,如下就是衝突。 根據W3C XHTML 1.0的規定:在XHTML中,因為<和&這兩個符號有特殊意義(小於號用於標 ...
  • 網站logo既要考慮seo又需要用圖片代替網站名字,所有H1標簽帶來的權重還是需要使用 有些人喜歡直接把<H1></H1>標簽直接hidden掉,個人喜歡使用css Text-indent還是放到屏幕外 .logo h1 { overflow: hidden; position: absolute; ...
  • 4.選擇與分組 (1).分組 字元組[]:表示匹配若幹個字元之一 字元組可以淺顯的理解為一些字元的組合,字元組與普通字元的區別在於:abc普通字元表示匹配a接下來b接下來c而字元組[abc]表示在同一位置匹配a或者b或者c;由於字元組本身的含義也決定了可以將這個字元組看成是一個普通的特殊字元。 普通 ...
  • 前言 在還未接觸webpack,就有幾個疑問: 1. webpack本質上是什麼? 2. 跟非同步模塊載入有關係嗎? 3. 可否生成多個文件,一定是一個? 4. 被引用的文件有其他非同步載入模塊怎麼辦? 在學習webpack時,也有幾個疑問: 1. webpack有哪些常用的插件? 2. 常用的Load ...
  • 今天我又寫了個很酷的實例:星級評分系統(可自定義星星個數、顯示信息) 使用預設值5個星星,預設信息 `var msg = [........]; sufuStar.star(10,msg);`自定義星星個數為10、顯示信息msg格式參考預設值,條數必須和星星個數一致; 自己實現一些實例,有個好處,能 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...