函數的定義:函數是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語言中,只有函數能夠被調用。當函數被調用的時候,若是存在參數傳遞,那麼會把實參的值傳遞給形參,並按照從上到下的順序逐條執行函數體內部的代碼。開發工具
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屬性--->原型對象(空對象)
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調用一個函數時,能夠理解爲調用該函數的「調用」屬性。
函數名後面跟上()代表這是一個函數調用。調用運算符:
是跟在任何產生一個函數值的表達式以後的一對圓括號。圓括號內能夠包含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方法做用基本相同,參數傳遞的形式有所差異。
函數調用時,會完成實際參數對形式參數的賦值工做。
當實際參數的個數和形式參數的個數不匹配時,並不會致使運行錯誤。
若是實際參數的數量過多,那麼超出的那些參數會被忽略。
若是實際參數的數量不足,那麼缺失的那些參數會被設置爲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>
函數的調用:調用一個函數會暫停當前代碼的執行,把控制權和參數傳遞給正被調用的函數。當一個函數被調用的時候,它會先根據實際參數來對函數的形式參數進行初始化,而後從函數體中的第一個語句開始執行並遇到關閉函數體的 } 時結束。而後把控制權交還給調用該函數的上下文。
函數的返回值:函數體中return語句能夠用來讓函數提早返回。當retun語句被執行時,函數會當即返回而再也不執行餘下的語句,return語句後面跟上返回的具體數據,能夠是任意類型(包括函數)。
❐ 函數老是會有一個返回值,若是沒有使用return語句指定,那麼將老是返回
undefined
。❐ 函數的返回值還和它的調用方式有關係,若是使用new也就是也構造函數的方式來調用,若函數體中沒有經過return語句顯示的返回一個對象類型的數據,則
默認返回this(新建立的實例對象)
。❗️ JavaScript不容許在return關鍵字和表達式之間換行。