1、序面試
定義函數的方式有兩種:一種是函數聲明,另外一種就是函數表達式;算法
1.1 函數聲明閉包
function functionName(arg){ //函數體 }
關於函數聲明,它有一個重要特徵就是函數聲明提高,意思就是在執行代碼以前會先讀取函數聲明。這就意味着能夠把函數放在調用它的語句後面。以下所示:函數
helloworld(); //在代碼執行以前會先讀取函數聲明 function helloworld(){ console.log("hello world"); }
1.2 函數表達式學習
var functionName=function(arg){ //函數體 }
這種形式看起來好像是常規的變量賦值語句,即建立一個函數並將它賦值給變量functionName。這種狀況下建立的函數叫作匿名函數。由於function關鍵字後面沒有標識符。spa
函數表達式與其餘表達式同樣,在使用以前必須先賦值;以下面代碼就會致使錯誤;指針
helloworld(); //錯誤,還未賦值,函數不存在 var helloworld=function(){ console.log("hello world"); }
有了函數表達式,咱們就能夠動態給函數表達式賦值了;以下面代碼:code
var helloworld; //聲明 if(condition){ //條件 helloworld=function(){ //賦值 console.log("hello world"); } } else{ helloworld=function(){ //賦值 console.log("你好,世界"); } }
2、遞歸函數對象
遞歸函數是在一個函數經過名字調用自身的狀況下構成的(和C#等語言同樣,因此程序的核心思想是差很少,只是在語法上有些差別,學好一門語言的基礎,學習其餘就會輕鬆不少),舉個經典的遞歸面試題,一列數的規則以下 : 1 、 1 、 2 、 3 、 5 、 8 、 13 、 21 、 34…… 求第 30 位數是多少, 用遞歸算法實現,代碼以下所示:blog
function foo(n) { if (n <= 0) return 0; else if (n > 0 && n <= 2) return 1; else return foo(n - 1) + foo(n - 2); }
雖然這個函數代表看來沒有什麼問題,但下面的代碼卻可能致使它出錯:
var foo1 = foo; foo = null; console.log(foo1(34));
以上代碼先把foo()函數保存在變量foo1中,而後將foo變量設爲null,結果指向原始函數的引用只剩下一個。但在接下來調用foo1()時,因爲必須執行foo(),而foo已經爲null了,因此就會致使錯誤;在這種狀況下,使用arguments.callee能夠解決這個問題。arguments.callee是一個指向正在執行的函數的指針,所以能夠用它來實現對函數的遞歸調用
function foo(n) { if (n <= 0) return 0; else if (n > 0 && n <= 2) return 1; else return arguments.callee(n - 1) + arguments.callee(n - 2); }
也可使用命名函數表達式來達成相同的結果。例如:
var foo = (function f(n) { if (n <= 0) return 0; else if (n > 0 && n <= 2) return 1; else return f(n - 1) + f(n - 2); });
3、閉包
3.1 閉包是指有權訪問另外一個函數做用域中的變量的函數,建立閉包的常見方式,就是在一個函數內部建立另外一個函數。要理解閉包,首先必須理解JavaScript特殊變量的做用域。變量的做用域無非就是兩種,全局變量和局部變量;接下來寫幾個demo來直觀表達;
函數內部直接讀取全局變量:
var n = 100; //定義一個全局變量 function fn() { console.log(n); //函數內部直接讀取全局變量 } fn();
函數外部不能直接讀取局部變量:
function fn() { var n = 100; } console.log(n); //n is not defined
在這裏有個地方須要注意的是,在函數內部聲明變量的時候,必定要用var ,若是沒用,則會變成全局變量:
function fn() { n = 100; } fn(); console.log(n); //100
有時候咱們須要獲得函數內部聲明的變量,因此可使用上面提到建立閉包的經常使用方式,在函數內部建立另外一個函數:
function fn() { n = 100; function fn1() { console.log(n); } fn1(); } fn(); //100
在上面的代碼中,函數fn1就被包括在函數fn內部,這時fm內部的全部局部變量,對fn1都是可見的。可是反過來就不行,fn1內部的局部變量,對fn就是不可見的。這就是Javascript語言特有的"鏈式做用域"結構,子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立。
居然fn1能夠讀取fn內部變量,那麼只要把fn1做爲返回值,這要咱們就能夠在外部讀取fn的變量了
function fn() { n = 100; function fn1() { console.log(n); } return fn1; } var result=fn(); result(); //100
在這裏fn1就是閉包,閉包就是可以讀取其餘函數內部變量的函數。因爲在Javascript語言中,只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成"定義在一個函數內部的函數"。因此,在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。
3.2 閉包的用途
它的最大用處有兩個,一個是前面提到的能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中。以下面代碼所示:
function fn() { n = 100; nadd = function () { n += 1; } function fn1() { console.log(n); } return fn1; } var result = fn(); result(); //100 nadd(); result(); //101
注意:因爲閉包函數會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存,過分使用閉包可能會致使內存佔用過多,因此在退出函數以前,將不使用的局部變量所有刪除。
4、塊級做用域
塊級做用域(又稱爲私有做用域)的匿名函數的語法以下所示:
(function(){ //塊級做用域 })();
不管在什麼地方,只要臨時須要一些變量,就可使用私有做用域,好比:
(function () { var now = new Date(); if (now.getMonth() == 0 && now.getDate() == 1) { alert("新年快樂"); } })();
把上面這段代碼放到全局做用域中,若是到了1月1日就會彈出「新年快樂」的祝福;這種技術常常在全局做用域中被用在函數外部,從而限制向全局做用域中添加過多的變量和函數。通常來講,咱們都應該儘可能少向全局做用域中添加變量和函數。在一個由不少開發人員共同參與的大型應用程序中,過多的全局變量和函數很容易致使命名衝突。而經過建立私用做用域,每一個開發人員既可使用本身的變量,又沒必要擔憂搞亂全局做用域。