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