前言:“函數是對象,函數名是指針。”,函數名僅僅是指向函數的指針,與其他包含函數指針的變量沒有什么區別,話句話說,一個函數可能有多個名字。
-1.函數聲明,function+函數名稱。調用方法:函數名(參數);
function f1(x,y){ return x+y; //函數體 }
console.log(f1(2,3));
這是最常見的指定函數名聲明函數,在函數體內返回參數值,函數調用時才會輸出結果。既然說到函數,那就免不了提一提它的預解析以及作用域。
此類方法定義的函數,在代碼開始執行之前會通過解釋器進行一個函數聲明提前的過程,並將其添加到執行環境中, JavaScript 引擎會將其提升到代碼樹的頂端,率先執行,所以即使聲明函數的代碼在函數調用代碼的后面,也能正確訪問,上述過程就被稱為函數的預解析。例如:
console.log(sum); //控制台輸出函數源代碼,證明函數可以被調用 console.log(sum(3,2)); // 5 function sum(x,y){ return x+y; }
執行環境定義了變量或者函數有權訪問其他數據,每個環境中都有一個與之關聯的變量對象,環境中定義的變量和函數都保存在這個對象中,解析器在處理數據時就會使用這個對象。
- 2.匿名函數,即沒有命名的函數,通過給將函數賦值給變量的形式聲明函數,也稱之為函數表達式。調用方法:函數名(參數);
var f2 = function(x,y){ return x+y; } f2(4,5);
此類方法定義的函數不能在函數聲明前調用函數,因為在預解析機制中:
1.把變量的聲明提升到當前作用域的最前面,只會提升聲明,不會提升賦值。
2.把函數的聲明提升到當前作用域的最前面,只會提升聲明,不會提升調用。
3.先提升var,在提升function
所以上述代碼如果提前調用函數,在預解析的執行環境中是:
var f2; //變量聲明提升,但賦值沒有 f2(4,5); //報錯 console.log(f2(4,5)); f2 = function(x,y){ return x+y; }
匿名函數是用一個變量去接收函數,因此預解析只將變量的聲明提前了,此時的函數位於一個初始化語句中,在執行到函數體代碼之前變量不會保存對函數的引用,所以直接報錯。
-3.自調用函數,顧名思義就是自己調用自己的函數,在聲明的同時進行調用,雖然方便但只能執行一次。
(function(){ console.log("這是一個自調用函數"); })();
/* * * 而它的演變過程也很簡單,是由函數表達式演變而來。 * var f1 = function(){ * console.log("這是一個自調用函數"); * } * f1(); * 這里把調用時的 f1 替換成 function(){} * 所以調用時就是function(){}(); * 為了保持代碼的整體性,所以在最外層加了一個括號。 * * */
//這里還有一個點需要注意,自調用函數前的一個函數如果沒有輸出調用,需要在函數表達式的結尾加上分號,以和自調用函數區分開來,防止將上一個函數也解析成自調用函數。
-4.Function 構造函數,Function 構造函數可以接受任意數量的參數,但最后一個參數始終被看做是函數體,而前面則看作是函數體的參數。調用:函數名();
var sum = new Function("sum1","sum2","return sum1+sum2");
但這種定義函數的方法並不推薦使用,因為在此類函數在執行時會先解析一次常規ECMAscript 代碼,再解析傳入函數中的字符串,反復調用時將會影響性能。
函數也是有數據類型的,所有函數的類型都是 function 。
關於作用域鏈
作用域鏈的用途是,保證執行環境中,有權訪問的所有函數和變量的有序訪問。簡單來說就是變量的使用范圍。
全局變量:在函數以外,用 var 聲明的變量都是全局變量,在全局執行環境中都可以使用。 !!! 但需要注意,全局變量只有在整個程序退出或者銷毀后才會被釋放,否則就一直占內存。
局部變量:在函數內部定義的變量就是局部變量,只能在函數內部使用。
隱式全局變量:沒有var 聲明的,也是作用於全局,但是可以使用delete刪除。
全局作用域:全局變量的使用范圍,始終是作用域鏈中的最后一個對象。
局部作用域:局部變量的使用范圍。
塊級作用域:指的是在一對大括號內聲明的變量,就只能在這對大括號中使用,但是js中全部都可以使用,所以js沒有塊級作用域,函數除外。
使用過程:1.作用域鏈的前端始終是當前執行代碼所在環境的變量對象,解析過程是按照作用域鏈一級一級搜索的過程。
2.始終是從作用域的前端開始的,然后逐級向后回溯。
3.內部變量可以通過作用域鏈訪問外部變量,但外部變量不能訪問任何內部的變量或者函數。(自內向外訪問)
最后給大家舉個栗子,綜合解說函數調用、預解析以及作用域:
f1(); console.log(c); console.log(b); console.log(a); function f1(){ var a = b = c = 9; console.log(a); console.log(b); console.log(c); }
輸出的結果為:9 9 9 9 9 報錯,原因如下
function f1(){ // var a = b = c = 9; 變量的聲明也提升 var a; a = 9; // 此時的 a 是被 var 聲明的,作為函數內的局部變量,只能在函數內被調用,所以最后的a會報錯 b = 9; c = 9; // b,c 則是隱式全局變量 console.log(a); console.log(b); console.log(c); } f1(); //調用f1函數,預解析后f1函數的聲明提升到作用域的最前面,此時的代碼為 console.log(c); console.log(b); console.log(a);
第二個小栗子
f1(); var f1 = function (){ console.log(a); var a = 10; };
//結果是報錯 //因為預解析的存在,所以函數和變量提前 //由於是函數表達式的形式,所以預解析后的代碼為:
// var f1; // f1(); // function (){ // console.log(a); // var a = 10; // };
