javaScript系列 [01]-javaScript函數基礎

 [01]-javaScript函數基礎

1.1 函數的建立和結構

函數的定義:函數是JavaScript的基礎模塊單元,包含一組語句,用於代碼複用、信息隱蔽和組合調用。java

函數的建立:在javaScript語言中,能夠說函數是其最重要也最成功的設計。咱們能夠經過三種方式來建立函數。編程

① 函數聲明
② 字面量方式建立
③ 使用Function構造函數建立json

代碼示例數組

 1 //01 函數聲明
 2 //函數名稱爲:f1,a和b爲該函數的形式參數(形參)
 3 function f1(a,b) {
 4     return a + b;
 5 }
 6 //02 字面量建立函數
 7 //使用字面量建立匿名函數並賦值給f2,能夠經過f2來調用,a和b爲該函數的形式參數(形參)
 8 var f2 = function (a,b) {
 9     return a + b;
10 };
11 //03 構造函數建立
12 //f3函數爲Function這個構造函數的實例化對象,若是不傳遞參數,那麼建立出來的函數沒有任何用處。
13 //在建立實例對象的時候咱們能夠經過參數列表的方式來指定f3的結構。
14 //構造函數的參數中最後一個參數爲函數體的內容,其他均爲函數的形參。
15 var f3 = new Function("a","b","return a + b");
16 //函數的調用
17 console.log(f1(1,2));    //3
18 console.log(f2(1,2));    //3
19 console.log(f3(1,2));    //3

函數的結構
函數的通常表現形式爲:app

1 //函數聲明
2 function fn(n1,n2) {
3     //函數體的內容
4     return n1 + n2; //返回值
5 }

一般,函數包括四個部分:
(1)保留字,function。
(2)函數名,這裏爲fn。
(3)圓括號以及包圍在圓括號中的一組參數。
(4)包括在花括號中的一組語句。編程語言

❐ 函數名能夠被省略(稱爲匿名函數),函數名可用於函數調用或者是遞歸調用,另外函數名能夠被調試器和開發工具識別。函數

❐ 函數聲明時的參數爲形參,能夠有多個,多個參數之間使用逗號進行分隔。形參將在函數調用的時候被定義爲函數中的局部變量,[注意]形參並不會像普通變量同樣被初始化爲undefined,它們的值根據函數調用時傳入的實際參數值設置。另外,函數調用的時候並不會對實參的類型進行檢查。工具

❐ 函數體是一組語句,它們在函數被調用的時候執行。函數執行完畢後,會返回一個值。post

函數的調用:函數聲明後能夠經過()運算符來進行調用,JavaScript語言中,只有函數能夠被調用。當函數被調用的時候,若是存在參數傳遞,那麼會把實參的值傳遞給形參,並按照從上到下的順序逐條執行函數體內部的代碼。開發工具

1.2 函數和對象的關係

JavaScript中的函數本質上就是對象。
在使用typeof 關鍵字對數據進行類型檢查的時候,獲得的結果可能會讓咱們產生錯覺。

1    var o = {};
2    var f = function () {};
3    console.log(typeof o);      //object
4    console.log(typeof f);      //function

實際上,函數和對象沒有質的區別,函數是特殊的對象。

函數的特殊性
① 函數能夠被()運算符調用[最重要]。
② 函數能夠建立獨立的做用域空間。
③ 函數擁有標配的prototype屬性。

由於函數自己就是對象,因此在代碼中函數能夠像對象同樣被使用,凡是對象能夠出現的地方函數均可以出現。

❐ 函數能夠擁有屬性和方法。
❐ 函數能夠保存在變量、對象和數組中。
❐ 函數能夠做爲其它函數的參數(稱爲函數回調)。
❐ 函數能夠做爲函數的返回值進行返回。

函數和對象的原型鏈結構
咱們能夠經過下面列出的簡單示例代碼來分析對象的原型鏈結構。

1 //字面量方式建立普通的對象
2 var o = {name:"文頂頂",age:"18"};
3 //關於普通對象的結構研究
4 console.log("① 打印o對象\n",o);
5 console.log("② 打印o.__proto__\n",o.__proto__);
6 console.log("③ 打印o.__proto__ === Object.prototype\n",o.__proto__ === Object.prototype)
7 console.log("④ 打印o.constructor\n",o.constructor);
8 console.log("⑤ 打印o.constructor === Object\n",o.constructor === Object);

經過對該代碼的運行和打印分析,能夠獲得下面的圖示。

咱們也可使用一樣的方式來分析函數對象的原型鏈結構。

 1 //使用構造函數Function 來建立函數(對象)
 2 var f = new Function("a","b","return a + b");
 3 //調用函數,證實該函數是合法可用的
 4 console.log(f(2, 3));  //獲得打印結果5
 5 //關於函數對象的結構研究
 6 console.log("① 打印函數對象\n",f);
 7 console.log("② 打印f.__proto__\n",f.__proto__);
 8 console.log("③ 打印f.__proto__===Function.prototype\n",f.__proto__===Function.prototype);
 9 console.log("④ 打印f.constructor\n",f.constructor);
10 console.log("⑤ 打印f.constructor === Function\n",f.constructor === Function);
11 //注意
12 console.log(f.hasOwnProperty("constructor"));   //檢查constructor是否爲函數f的實例成員(false)
13 console.log(f.__proto__.hasOwnProperty("constructor")); //true

順便貼出研究Function原型結構的代碼

 1 //說明:下面三行代碼代表Function的原型對象指向一個空函數
 2 console.log(Function.prototype);  //ƒ () { [native code] }  是一個空函數
 3 console.log(typeof Function.prototype); //function
 4 console.log(Object.prototype.toString.call(Function.prototype));  //[object Function]
 5 //檢查Function.prototype的原型鏈結構
 6 //Function.prototype是一個空函數,是一個對象,而對象均由構造函數實例化產生
 7 //檢查Function.prototype的構造函數以及原型對象
 8 console.log(Function.prototype.constructor  === Function);
 9 //注意:按照通常邏輯實例對象的__proto__(原型對象)應該指向建立該實例對象的構造函數的原型對象
10 //即此處應該表現爲Function.prototype.__proto__--->Function.prototype.constructor.prototype
11 //彷佛能夠得出推論:Function.prototype.__proto__ === Function.prototype == 空函數 但這是錯誤的
12 console.log(Function.prototype.__proto__ === Object.prototype);

經過對函數對象原型結構的代碼探索,能夠獲得下圖的原型鏈結構圖(:原型鏈並不完整)

函數的其它隱藏細節

① 函數天生的prototype屬性

每一個函數對象在建立的時候會隨配一個prototype屬性,即每一個函數在建立以後就天生擁有一個與之相關聯的原型對象,這個關聯的原型對象中擁有一個constructor屬性,該屬性指向這個函數。
簡單描述下就是:

function f(){ //......}  //聲明函數
//函數聲明被建立後,默認擁有prototype屬性--->原型對象(空對象)

 

這裏須要注意的是,不少人容易被本身的經驗誤導,認爲新建立的函數對象身上除prototype實例屬性外,還擁有constructor這個實例屬性,由於咱們常常看到 f.constructor相似的代碼,其實這裏使用的constructor屬性是從原型鏈中獲取的,實際上是f構造函數關聯原型對象上面的屬性,即Function.prototype.constructor。

 

備註:在ECMAScript標準中函數建立相關章節有這樣一句話:NOTE A prototype property is automatically created for every function, to allow for the possibility that the function will be used as a constructor.解釋了給新建立函數添加prototype屬性的意義在於便於該函數做爲構造函數使用。

② 函數何以可以被調用

咱們已經理解了函數自己就是對象,但又區別於普通對象,最大的區別在於函數能夠被調用,()被稱爲調用運算符。

❗️ 函數能夠被調用的緣由在於JavaScript建立一個函數對象時,會給該對象設置一個「調用」屬性。當JavaScript調用一個函數時,能夠理解爲調用該函數的「調用」屬性。

1.3 函數的調用和this參數

函數名後面跟上()代表這是一個函數調用。
調用運算符:是跟在任何產生一個函數值的表達式以後的一對圓括號。圓括號內能夠包含N(N>=0)個用逗號分隔開的表達式,每一個表達式產生一個參數值。每一個參數值被賦予函數聲明時定義的形式參數名。

函數的調用
JavaScript中有四種調用函數的模式

① 對象方法調用模式
② 普通函數調用模式
③ 構造函數調用模式
③ 上下文的調用模式

除了聲明函數時定義的形參外,每一個函數還接收兩個附加的參數,分別是this和arguments。其中arguments用於存儲函數調用時接收到的實際參數,this的值則取決於函數的調用模式,下面分別講解。

普通函數調用模式

當函數並不做爲其餘對象的屬性,直接使用調用運算符來調用時,咱們認爲它使用普通函數調用模式

1 <script>
2     //01 聲明函數fn
3     function fn() {
4         console.log(this);
5     }
6     //02 以普通函數調用模式來調用fn函數
7     fn(); //this被綁定到全局對象window
8 </script>

備註:在咱們看來上面的調用方式很是簡單清楚,並且this的指向也沒有任何問題。但JSON的做者Douglas Crockford指出這是JavaScript語言設計上的一個錯誤。由於把this直接綁定給全局變量的方式沒有考慮到函數做爲內部函數(在其它函數內部聲明的函數)使用過程當中須要共享外部對象訪問權的問題。
他指出正確的語言設計應該是,當內部函數被調用時,函數內的this應該和外部函數的this保持一致,即這個this應該被綁定到外部函數的this變量。無疑,這值得思考和討論。

對象方法調用模式

對象是鍵值對的集合,對象能夠擁有屬性和方法。
當函數被保存爲對象的屬性時,咱們稱之爲方法。
對象的方法須要經過對象.方法()或者是對象[方法]()的方式進行調用。
以對象方法的模式來對函數進行調用,函數內部的this被綁定給該對象。

 1    //01 字面量的方式建立對象
 2    //02 o對象中擁有name屬性和showName方法
 3    var o = {
 4        name:"文頂頂",
 5        showName:function () {
 6            console.log(this);
 7            console.log(this.name);   //文頂頂
 8        }};
 9    //03 以對象方法調用模式來調用showName函數
10    o.showName(); //this被綁定到o對象

❗️ this到對象的綁定發生在方法調用的時候。

構造函數調用模式

構造函數:若是一個函數建立出來以後,咱們老是但願使用new 前綴來調用它,那這種類型的函數就被稱爲構造函數。構造函數和普通函數本質上沒有任何區別,開發者老是約定以函數名首字母大寫的方式來人爲進行區分。

若是以構造函數的方式來調用函數,那麼在調用時,默認會建立一個鏈接到該構造函數原型對象上面的新對象,同時讓this綁定到該新對象上。

1    //01 聲明構造函數Person
2    function Person() {
3        console.log(this);
4    }
5    //02 以構造函數的方式來調用Person
6    new Person();  //this被綁定到Person的實例化對象

❗️ 構造函數調用方式也會改變函數中return語句的行爲,若是顯示的return語句後面跟着的不是對象類型的數據,那麼默認返回this綁定的新對象。

上下文的調用模式

上下文的調用模式,即便用apply或則call方法來調用函數。
由於JavaScrip是一門函數式的面向對象編程語言,全部JavaScript中的函數本質上是對象,也所以函數也能夠擁有方法。使用上下文模式對函數進行調用的時候,函數內部的this根據參數傳遞的狀況進行綁定。

 1    //聲明函數f
 2    function f(a,b) {
 3        console.log(a, b, a+b);
 4        console.log(this);       //使用上下文模式調用時,this被綁定給o對象
 5        console.log(this.name);  //wendingding
 6    }
 7    //字面量的方式建立對象
 8    var o = {name:"wendingding"};
 9    //使用apply和call方法來調用函數
10    f.apply(o, [1,2]);
11    f.call(o,3,4);
12 console.log(f.hasOwnProperty("apply"));                 //false
13 console.log(Function.prototype.hasOwnProperty("apply"));//true

❐ apply和call方法調用函數,函數內部的this綁定給第一個參數。
❐ apply和call方法定義於Function的原型對象上,因此全部的函數均可訪問。
❐ apply和call方法做用基本相同,參數傳遞的形式有所差異。

1.4 函數的參數(arguments)

函數調用時,會完成實際參數對形式參數的賦值工做。
當實際參數的個數和形式參數的個數不匹配時,並不會致使運行錯誤。

若是實際參數的數量過多,那麼超出的那些參數會被忽略。
若是實際參數的數量不足,那麼缺失的那些參數會被設置爲undefined。
JavaScript在進行函數調用時不會對參數進行任何的類型檢查。

在函數的內部,咱們老是能夠得到一個免費配送的arguments參數。
arguments用於接收函數調用時傳入的實際參數,它被設計成一個相似於數組的結構,擁有length屬性,但由於它不是一個真正的數組因此不能使用任何數組對應的方法。

arguments參數的存在,使得咱們能夠編寫一些無須指定形參個數的函數。
下面提供一份示例代碼用於對傳入的全部參數進行累加計算。

 1 <script>
 2     function sum() {
 3         var sum = 0;
 4         var count = arguments.length;
 5         for (var i = 0; i < count; i++) {
 6             sum += arguments[i];
 7         }
 8         return sum;
 9     }
10     console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 89));   //125
11 </script>

1.5 函數的返回值

函數的調用:調用一個函數會暫停當前代碼的執行,把控制權和參數傳遞給正被調用的函數。當一個函數被調用的時候,它會先根據實際參數來對函數的形式參數進行初始化,而後從函數體中的第一個語句開始執行並遇到關閉函數體的 } 時結束。而後把控制權交還給調用該函數的上下文。

函數的返回值:函數體中return語句能夠用來讓函數提早返回。當retun語句被執行時,函數會當即返回而再也不執行餘下的語句,return語句後面跟上返回的具體數據,能夠是任意類型(包括函數)。

❐ 函數老是會有一個返回值,若是沒有使用return語句指定,那麼將老是返回undefined

❐ 函數的返回值還和它的調用方式有關係,若是使用new也就是也構造函數的方式來調用,若函數體中沒有經過return語句顯示的返回一個對象類型的數據,則默認返回this(新建立的實例對象)

❗️ JavaScript不容許在return關鍵字和表達式之間換行。


相關文章
相關標籤/搜索