歸函數是一種特殊的函數,遞歸函數允許在函數定義中調用函數本身。考慮對于如下計算:n!=n*(n-1)*(n-2)*...*1
希望能寫一個簡單的函數完成對n!的求值。觀察上面等式發現:(n-1)!=(n-1)*(n-2)*...*1
則有如下等式:n!=n*(n-1)!
注意到等式左邊需要求n的階乘,而等式右邊則是求n-1的階乘。實質都是一個函數,因此可將求階乘的函數定義:
<script type="text/javascript">
//定義求階乘的函數
var factorial=function(n)
{
//只有n的類型是數值,才執行函數
if(typeof(n)=="number")
{
//當n等于1時,直接返回1
if(n==1)
{
return 1;
}
//當n不等于1時,通過遞歸返回值
else
{
return n*factorial(n-1);
}
}
//當參數不是數值時,直接返回
else
{
alert("參數類型不對!");
}
}
//調用階乘函數
alert(factorial(5));
</script>
結果:
上面程序中粗體字代碼再次調用了factorial()函數,這就是在函數里調用函數本身,也就是所謂的遞歸。上面程序執行的結果是120,可以正常求出5的階乘。注意到程序中判斷參數時,先判斷參數n是否為數值,而且要求n大于0才會繼續運算。事實上,這個函數不僅要求n為數值,而且必須是大于0的整數,否則函數不僅不能得到正確結果,而且將產生內存溢出。
對于上面遞歸函數,當n為一個大于0的整數,例如5時,5的階乘為4的階乘和5的乘積;同理,4的階乘為3的階乘和4的乘積……依次類推,直到最后1的階乘,代碼中已經寫明:當n=1時,返回值為1。然后反算回去,所有的值都變成已知的。反過來,當n為負數,例如-1時,-1的階乘為-1與-2的階乘的乘積,-2的階乘為-2和-3的階乘的乘積……這將一直追溯到負無窮大,沒有盡頭,導致程序溢出。
可見,遞歸的方向很重要,一定要向已知的方向遞歸。對于上例而言,因為1的階乘是已知的,因此遞歸一定要追溯到1的階乘,遞歸一定要給定中止條件,這一點與循環類型。沒有中止條件的循環是死循環,不向中止點追溯的遞歸是無窮遞歸。
注意:遞歸一定要向已知點追溯,這樣才能保證遞歸有結束的時候
好程序員web前端培訓分享JavaScript學習筆記之遞歸函數,什么是遞歸函數在編程世界里面,遞歸就是一個自己調用自己的手段,遞歸函數: 一個函數內部,調用了自己,循環往復
// 下面這個代碼就是一個最簡單的遞歸函數// 在函數內部調用了自己,函數一執行,就調用自己一次,在調用再執行,循環往復,沒有止盡function fn() {
fn()}fn()
· 其實遞歸函數和循環很類似
· 需要有初始化,自增,執行代碼,條件判斷的,不然就是一個沒有盡頭的遞歸函數,我們叫做 死遞歸
· 我們先在用遞歸函數簡單實現一個效果
· 需求: 求 1 至 5 的和
· 先算 1 + 2 得 3
· 再算 3 + 3 得 6
· 再算 6 + 4 得 10
· 再算 10 + 5 得 15
· 結束
· 開始書寫,寫遞歸函數先要寫結束條件(為了避免出現 “死遞歸”)
function add(n) {
// 傳遞進來的是 1 // 當 n===5 的時候要結束 if (n === 5) {
return 5
}}
add(1)
· 再寫不滿足條件的時候我們的遞歸處理
function add(n) {
// 傳遞進來的是 1 // 當 n===5 的時候要結束 if (n === 5) {
return 5
} else {
// 不滿足條件的時候,就是當前數字 + 比自己大 1 的數字 return n + add(n + 1)
}}add(1)
· 對象是一個復雜數據類型
· 其實說是復雜,但是沒有很復雜,只不過是存儲了一些基本數據類型的一個集合
var obj = {
num: 100,
str: 'hello world',
boo: true}
· 這里的 {} 和函數中的 {} 不一樣
· 函數里面的是寫代碼的,而對象里面是寫一些數據的
· 對象就是一個鍵值對的集合
· {} 里面的每一個鍵都是一個成員
· 也就是說,我們可以把一些數據放在一個對象里面,那么他們就互不干擾了
· 其實就是我們準備一個房子,把我們想要的數據放進去,然后把房子的地址給到變量名,當我們需要某一個數據的時候,就可以根據變量名里面存儲的地址找到對應的房子,然后去房子里面找到對應的數據
· 字面量的方式創建一個對象
// 創建一個空對象var obj = {}
// 像對象中添加成員obj.name = 'Jack'obj.age = 18
· 內置構造函數的方式創建對象
// 創建一個空對象var obj = new Object()
// 向對象中添加成員obj.name = 'Rose'obj.age = 20
· Object 是 js 內置給我們的構造函數,用于創建一個對象使用的
在前面:本文為DataHunter前端技術培訓系列的第一篇文章,后續將有更多精彩內容放出,趕緊關注我們的公眾號來get實時更新吧!
循環引用陷阱
JavaScript的GC機制是自動進行。當一個對象斷開所有的指針引用以后,GC就會回收這部分資源。但循環引用會導致指針無法斷開,請看如下代碼:
由于a和b兩個對象互相引用指針,形成了閉環,就會導致無法觸發GC,所占資源也就無法釋放。
在JSON.stringify對JavaScript數據結構進行序列化的時候,也會遇到循環引用的陷阱。例如:
JavaScript的數據類型和JSON序列化策略
通過JavaScript的typeof操作符,我們可以得到的數據類型有
string/number/ function/boolean/symbol/object/undefined。
其中object包括Object/Array/RegExp/Date/null等,function包括class類。所有這些類型,從JSON序列化的角度,我們可以分為三類:
由于JSON標準只具備string/number/boolean/null等有限的幾種值類型,所以JS對象在序列化后會有變化。下面是幾種特殊類型在轉換時需要注意的問題:
function無法被序列化。
Symbol對象無法被序列化。
Date對象無法被序列化。
ES6的class的typeof是function,無法序列化。
number的NaN會被轉成null。
RegExp默認會被轉成空對象。
TypedArray并不繼承自Array,所以會被序列化成對象而不是數組。
undefined由于會被JSON忽略,所以反序列化后對象會有鍵的變化。看例子:
可以看到,in操作符在obj和copy兩個對象中會出現不同的返回結果。所以在對反序列化的數據進行操作時,in操作符要小心使用。
通過JSON的序列化策略我們可以看出,循環引用可能會發生在mixed類型上,所以我們可以在序列化過程的最外層,建立一個mixed指針的數組。當數組已經具備此指針,就可以斷定該指針數據已經被序列化,從而中斷進一步序列化過程。
首先我們寫一個mixed類型判斷函數:
通過JSON.stringify的第二個回調處理理函數參數,我們植入這個類型判斷,并啟用循環引用陷阱規避。
JavaScript對象的分支遞歸和深拷貝
我們知道,Object.assign會實現一次Object或者Array的淺拷貝。但這種淺拷貝有時并不能滿足我們的需求。有時我們還會需要對對象的分支做值更改檢測,或者分支合并。這時候都需要對對象的所有分支做一次遞歸。
JSON.stringify其實就是做了了一次分支遞歸。對象分支遞歸的策略和序列化策略基本一致,我們拿對象的深拷貝來舉例說明。
我們首先針對上述三種數據類型,做一個類型判斷函數。
此外,由于Object和Array都是mixed類型,但不是同一個構造函數,所以我們需要對Object和Array再做一次類型細分,以便構造新的復制對象。
創建一個分支遞函數,并規避循環引用陷阱。
可以看到循環引用的部分都變成undefined了。
在ES6下,可以通過Symbol對象定義一個循環引用的標記并返回,在運行時對此類值做自定義的處理,防止對象log出來太難看。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。