(第三天)函数
定義函數
關鍵字function用來定義函數。定義函數有兩種方法
(1)函數定義表達式
1 var f = function(x) { return x+1; }(2)函數聲明語句
1 function funcname([arg1 [, arg2 [...,argn]]]) { 2 3 }?函數聲明語句通常出現在JavaScript代碼的最頂層,也可以嵌套在其他函數體內。但在嵌套時,函數聲明只能出現在所嵌套函數的頂部。也就是說函數定義不能出現在if語句、while語句后者其他語句中。
二者異同
(1)都創建了相同的新函數對象
(2)函數聲明語句中的函數名是一個變量名,變量指向函數對象。函數定義表達式并未聲明一個變量,如果一個函數定義表達式包含名稱,函數的局部作用域將會包含一個綁定到函數對象的名稱。所以名稱存在函數體中,并指代該函數本身,也就是說函數的名稱將成為函數內部的一個局部變量。【注】函數定義表達式特別適合用來定義那些只會用到一次的函數
(3)函數聲明語句中的函數被顯式地“提前”到了腳本或函數頂部。因此它們在整個腳本和函數內都是可見的。但以函數表達式定義函數則不同,想要調用此定義的函數,必須要引用它,而要使用一個表達式方式定義的函數之前,必須要把他賦值給一個變量,前面說過:變量的聲明提前了,但給變量賦值是不會提前的,所以,以表達式方式定義的函數在定義之前無法調用。如下代碼:
1 person(); /*函數聲明語句顯式提前至頂部,所以能在定義之前調用*/ 2 function person() {} 3 person(); /*在函數定義之后調用毋庸置疑正確*/ 1 p(); 2 3 var p = function(){ 4 5 } 6 7 /* 8 打印出:undefined is not a function 變量p還未初始化,因此函數定義表達式無法在函數定義之前調用 9 */函數調用?
(1)作為函數
1 function person(age,name){ 2 3 } 4 5 person(12,"嘿嘿"); /*函數調用*/(2)作為方法
1 var calculator = { 2 operator1 : 1, 3 operator2 : 2, 4 add: function(){ 5 this.result = this.operator1 + this.operator2; 6 } 7 8 }; 9 10 calculator.add(); //方法調用計算1+1的結果 11 calculator.result =>2 12 13 /* 14 通過對象直接量來創建屬性和方法,此時calculator作為調用上下文,所以this關鍵字則引用該對象 15 */(3)作為構造函數
1 var o = new Object(); 2 3 var o = new Obejct; 4 5 /* 6 構造函數調用創建一個新的空對象,這個對象繼承自構造函數的prototype屬性。構造函數試圖初始化這個新創建的對象,并將這個對象用做其調用上下文,因此構造函數可以使用this關鍵字來引用這個新創建的對象 7 */【注】如果構造函數沒有形參,JavaScript構造函數調用的語法是允許省略實參列表和圓括號的。所以凡是沒有形參的構造函數調用都可以省略圓括號。
(4)通過它們的call()和apply方法間接調用 【后面講】
this關鍵字
this是 一個關鍵字,不是變量,也不是屬性名。JavaScript的語法不允許給this賦值。和變量不同,關鍵字this沒有作用域的限制,嵌套的函數不會從調用它的函數中繼承this。對此我們用實例說明
1 var o ={ 2 m: function() { 3 var self = this; 4 console.log(this === o); (1) 5 f(); 6 7 function() { 8 console.log(this === o); (2) 9 console.log(self == o); (3) 10 } 11 } 12 } 13 14 /* 15 上述(1)中打印出true,因為this就指代o。(2)中輸出false,此時this是全局對象或undined。(3)輸出true,self指代外部函數的this值 16 */綜上所知:
(1)如果嵌套函數作為方法調用,其this的值指向調用它的對象。
(2)如果嵌套函數做為函數調用,其this值不是全局對象(非嚴格模式下)就是undefined(嚴格模式下)。
(3)如果想訪問外部函數的this的值,需要將this的值保存在一個變量里,這個變量和內部函數都同在一個作用域內。通常使用變量self來保存this。
作為命名空間的函數
引入
當有一段JavaScript模塊代碼,這段代碼將要用在不同的JavaScript程序中(對于客戶端JavaScript來講通常是用在各種各樣的網頁中)。假如這段代碼定義了一個用以存儲中間計算結果的變量。如此就出現一個問題,當模塊代碼放在不同的程序中時,你無法得知這個變量是否已經建好,如果已經存在這個變量,那么將會和代碼發生沖突。解決辦法就是將代碼放入一個函數內,然后調用這個函數。這樣全局變量就變成了函數內的局部變量。用匿名函數來定義,用法如下
1 (function(){//模塊代碼中所使用的變量都是局部變量 2 console.log(12) (1) 3 }()); //結束函數定義并立即調用它 4 5 或者 6 7 (function(){ 8 console.log(12); (2) 9 })();
這里有個疑問尚未解決,如果有園友知道,希望能幫助我解決,謝謝!不知道大家注意到上述兩個匿名函數的不同之處,對,在(1)中輸出的最后沒有加分號,若加分號則錯誤,無法輸出,不加分號則正常輸出,(2)中則是好使的!希望得到各位園友的幫助,在此表示感謝!
這種定義匿名函數并立即在單個表達式中調用它的寫法很常見,如代碼檢測中是否出現了一個bug,如果出現這個bug,就返回一個帶補丁的函數的版本!?
函數屬性、方法
(1)length屬性
?length到沒什么可說的,最主要是要屬函數的arguments的屬性了,在函數體內,arguments.length表示傳入函數的實參的 個數,以此來模擬函數重載。請看下面代碼
1 function person(age, name, gender, addr) { 2 this.age = age; 3 this.name = name; 4 this.gender = gender; 5 this.addr = addr; 6 /*獲得傳入實參的個數*/ 7 switch(arguments.length) 8 { 9 case 1:// 10 break; 11 case 2:
// 12 break; 13 case 3:
// 14 break; 15 case 4:
// 16 break; 17 } 18 } 19 20 var p = new person(1,'嘿嘿'); 21 var p1 = new person(1,'嘿嘿',false); 22 var p2 = new person(1,'嘿嘿',false,'hunan'); 23 24
補充:arguments的callee屬性
引入
1 var factorial = function(x) { 2 if (x <= 1) return 1; 3 return x * factorial(x - 1); 4 } 5 console.log(factorial(5)); 6 /*打印出120*/上述代碼為求一個數的階乘,毫無疑問沒有錯誤。現在進行一點小改動,如下
1 var factorial = function(x) { 2 if (x <= 1) return 1; 3 return x * factorial(x - 1); 4 } 5 var fact2 = factorial; 6 factorial = function() { 7 return 0; 8 } 9 console.log(fact2(5));上述很明顯fact2變量指向兩個函數,當要求調用fact2(5)時,先執行?第一個 factorial函數,當執行到 return x * factorial(x - 1); 時,這時執行的就是 第二個 factorial函數,所以此時打印出0。很顯然這不是我們想要的結果,我們需要的是求階乘即執行的函數接下來還是它本身也就是第一個,所以這個時候callee()方法就派上了用場:用于調用自身。所以上述代碼這樣修改即可?
1 var factorial = function(x) { 2 if (x <= 1) return 1; 3 return x * arguments.callee(x - 1); 4 } 5 var fact2 = factorial; 6 factorial = function() { 7 return 0; 8 } 9 console.log(fact2(5));?
【注】arguments還有一個長得相似callee的屬性就是caller,而在非嚴格模式下,ECMAScript標準規范規定callee屬性指代當前正在執行的函數。caller是非標準的但大多數瀏覽器都 實現了這個屬性,它指代當前正在執行的函數的函數。通過caller屬性可以訪問調用棧。而通過callee來進行遞歸調用自身,因為它保存了當前執行方法的地址,而不會出差錯。
(2) prototype屬性
每個函數都包含一個prototype屬性,這個屬性指向一個對象的引用,這個對象叫做原型對象。每一個函數都包含不同的原型對象。當將函數用做構造函數的時候,新創建的對象會從原型對象上繼承屬性。有關原型對象前面已講,請參考原型、繼承這一講
(3)call()和apply()方法
這兩種方法可以用來間接地調用函數,兩個方法都允許顯式指定調用所需的this的值,任何函數可以作為任何對象的方法來調用,哪怕這個函數不是那個對象的方法。兩個方法都可以指定調用的實參。call()方法使用它自有的實參列表作為函數的實參,apply()方法則要求以數組的形式傳入參數。在ECMAScript5的嚴格模式中,call()和apply()的第一個實參都會變為this的值,哪怕傳入的實參是原始值甚至是null或undifined。在ECMAScript3和非嚴格模式中,傳入的null和undefined都會被全局對象代替,而其他原始值則會被相應的包裝對象所替代。
?下面用代碼來解釋上述概念
(1)call()方法
1 function person(age,name){ 2 this.age=age; 3 this.name=name; 4 } 5 6 var obj=new Object(); 7 person.call(obj,12,'小黑'); 8 console.log(obj.age); 9 console.log(obj.name); 10 11 /* 12 創建空對象obj,此時調用person類的call()方法,并將obj傳遞進去,此時obj成為其調用上下文,此時this即obj,最終能打印出12和小黑 13 */(2)apply()方法?
?更多用法請參考:apply()詳情
(3)bind()方法
bind()是在ECMAScript5中新增的方法,但在ECMASript3中可以輕易模擬bind(),從名字可以看出,這個方法的主要作用就是將函數綁定至某個對象上。
1 function f(y){ 2 return this.x + y; 3 } 4 var o = { x : 1 }; 5 var g = f.bind(o); 6 console.log(g(2)); 7 8 function bind(f,o){ 9 if(f.bind) return f.bind(o); 10 else return function(){ 11 return f.apply(o, arguments); 12 }; 13 }上述將函數f綁定至對象o上,當在函數f()上調用bind()方法并傳入一個對象o作為參數,這個方法將返回一個新的函數。調用新的函數將會把原始的函數f()當做o的方法來調用。傳入新函數的任何實參都將傳入原始函數。上述中?f.bind(o);?則此時函數f()中的this即為o,此時this.x=1,綁定后返回新的函數g,再調用函數g(2),此時函數f()即為其參數,所以打印出3。
函數傳參
(1)傳遞原始類型(所謂c#中的值類型)
1 function person(age){ 2 age++; 3 } 4 var age = 12; 5 person(age); 6 console.log(age);根據上述代碼你覺得會打印出多少呢?我們一句一句分析:
?(1)var age = 12;棧上開辟一段空間地址假設為0x1,此時0x1存的是變量age并且其值為12。
?(2)person(age);調用函數person并將實參傳進去,此時person中的形參相當于是局部變量,所以同樣在棧上開辟一段空間地址為0x2,變量為age,將上述(1)中的值賦給(2)中的age所以同樣為12,然后進入函數體內,將age++,此時相加的是新創建的局部變量的值,并未改變(1)中的age值。
(3)綜上打印出12。
(2)傳遞對象(所謂c#中的引用類型)
1 function person(p){ 2 p.age++; 3 } 4 var p = { age : 12 }; 5 person(p); 6 console.log(p.age);同理進行分析
(1) var p = { age : 12 };首先在棧上開辟一段空間地址為0x1值為null,然后右邊在堆上首先開辟一段空間地址為0x2的空對象,然后定義屬性age并且其值為12,并且此對象指向變量p,所以此時變量的值為0x2。
(2)person(p);同樣p此時相當于是person類的局部變量,首先在棧上開辟一段空間地址為ox3名字為p的變量,此時將(1)中p的存的值0x2賦給0x3中p的值,所以此時0x3中的值為0x2,也就是此時ox2指向了堆上那個地址為0x2,屬性為age值為12的對象。然后進入函數體內部,對其年齡進行age++,此時堆上對象的屬性age變為13。
(3)綜上,此時打印出的P中的age為13。
?總結傳參
無論是傳遞原始類型(值類型)還是對象(引用類型)只需要看棧上變量的值是存的值還是地址,若存的是值,則相當于復制一份即副本不會改變原來的值,若是變量的值存的是地址,若在函數體內改變值則原來對象中的值也會受影響。
?
轉載于:https://www.cnblogs.com/CreateMyself/p/4692357.html
總結
- 上一篇: 微信应用开发简单示例,学生自助报道系统
- 下一篇: 供给、需求、有效供给、有效需求