2.1剖析函數的定義
函數的定義很簡單,形如function square(x){return x*x;} 其中函數名為square。我想說的是在js中函數都是有傳回值的,即使你沒用return關鍵字,那它預設會返回undefined。另外需要強調的一點是js的一切都是值,函數也是值。
當代碼在執行的時候,會先掃描所有的函數定義,所以不用擔心函數定義如果寫在了執行代碼後面是否會去調用函數的問題。在執行之前準備工作其實已經做好了這些。
局部變數的定義前用var關鍵字,否則會視為定義了一個全域的變數。在函數中定義的變數一般不能被函數外調用,函數外的變數在函數內可隨便調用。
在js中還有一個重要的概念就是‘棧’。當代碼執行到一個函數的時候,代碼的控制權交給這個函數,函數執行完畢時再交出代碼控制權。而執行時該回到那個地方呢,這個資訊就存在‘棧’中,每個函數沒調用時它的上下文(也就是開始執行的位置資訊)都會被存放在‘棧’中。這一點也造就了js對函數的靈活使用,比如函數作為參數,函數作為傳回值等情況。當然如果利用不當也會出現問題:
View Code
function chicken(){ return egg(); }function egg(){ return chicken();}chicken();//由於死迴圈導致棧被佔滿了,發生棧溢出。
下面再講一個有意思的例子,能證明js的超強靈活性,同時也說明了js一切皆為值的特性:
View Code
var a = null;function b(){ return "B"; }(a || b)();//執行後得到 B 。
閉包的問題,說實話這本書講得很大概,閉包個人覺得是很難理解的一個問題,有諸多情況等你去糾結。我再查一下別人資料後再補充吧。
閉包部分的理解(讀了很多園子裡寫的文章,自己把部分理解寫下來)
首先指出在js的函數中,不僅能定義一個變數,而且也能定義一個函數。當函數執行的時候,函數內部的變數調入記憶體做相應操作,
當函數結束時記憶體釋放,函數內變數從此失效,而閉包可以使得原本暫存的變數在函數結束後也能重複調用。js的範圍告訴我們
內部能訪問外部,外部不能訪問內部,但是閉包卻能做到函數外訪問函數內部的變數,在下面的代碼中具體講解:
View Code
//閉包的一個定義是:如果一個函數訪問了它的外部變數,那麼它就是一個閉包。//至少現在我還沒理解上面的話,我覺得下面的1、2執行個體沒體現閉包的特點,應該//不算是一個閉包。//執行個體1var name = "zhangsan";function showName(){ //此處函數內部調用了函數外部的變數name。 var text = "my name is "+name; alert(text);}showName();//執行個體2function outer(three){ var two = 2; function inner(one){ alert(three+(++two)+one); } inner(1);}outer(3); //該執行個體雖然也是內建函式調用了外部函數的變數,但我覺得依舊不屬於閉包。多次執行outer(3),資料每次都是7,不會變//真正的閉包執行個體如下,執行個體3//執行個體3function outer(three){ var two = 2; return function inner(one){ alert(three+(++two)+one); }}var invoke = outer(3);invoke(1);invoke(1);//內建函式調用外部函數的變數,而且內建函式以傳回值的形式體現,這才是一個完整的閉包。//多次調用invoke(1),雖然前一次函數已經結束但是裡面的two變數並不消失。而是隨著下次的調用而累加。
閉包的概念五花八門,我的理解是閉包是一種狀態,當函數在某種特定使用的情況下(具體的使用方式上面代碼中有體現)突然達到
的一種狀態,在這種狀態下,即使函數執行完畢,函數內部的變數也不會消失,而是持續保持在記憶體中。
另外方便理解,可以把此時的函數想象成C#當中的對象。函數中的變數就是對象中的private成員變數,想調用該私人成員,需要一個
相對應的get、set方法。而js中,函數內部的return function就提供了相應的功能。
函數的參數都是選擇性參數,比如你能輕鬆的運行這行代碼alert("one","two","three"); 不過只能輸出'one',後面的幾個字串被忽略了。函數參數可變的原則就是,寫多了會忽略,寫少了則視為
undefined。所以參數數量上一般是沒啥限制的。
2.2技巧
函數最總要的作用是用來避免重複性的,當你的一部分代碼在程式中多次重複使用的時候就該考慮將它們寫成一個函數了。但是函數一定要保證功能單一性的原則,切記不要為了逞能把很複雜的功能封裝到一個函數中,這樣才調用的時候反而大大降低了其靈活性。例如,當數字只有個位時會習慣前面加0,5->05。這是常用的方法,把它寫在一個函數裡。
function zeropad(number,width){ var string = String(Math.round(number)); while(string.length<width){ string = "0" + string; } return string;}
上面的例子通過,通過一個width來判斷你的number前面需要填幾個0.
純函數 :當使用函數的時候同樣的參數總是返回同樣的值而沒有副作用。(題外話:其實書中會對一些小概念展開長篇幅的描述,這對部分人其實是雞肋,我的這個系列的筆記不想照搬書本練打字,盡量寫點有意思的事情,我一直頭疼這點怎麼去整啊)
遞迴 :關於遞迴,大家應該都熟悉了。遞迴有一個特點,某些問題用遞迴去解決很容易理解,但是弊端是遞迴效率很低,調用次數越多就越低。但有時候當迴圈調用次數不大時,遞迴就能用很簡潔的代碼快速解決你思路上很亂的問題。下面的例子還有意思,值得一想:傳入一個數,計算是否其由相加5或者相乘3得到,如果能返回其計算路徑,如果沒有則return null。8 -> (1*3)+5;
View Code
function findSequence(goal){ function find(start,history){ if(start == goal){ return history; }else if(start > goal){ return null; }else{ return find(start + 5,"("+history+" + 5)")||find(start * 3,"("+history+" * 3)"); } } return find(1,"1");}alert(findSequence(1234));
當然上面計算的並不一定時一條最短的路徑,只是路徑之一。