函數定義
函數 ( Function ) 是執行特定任務並可以在程式中重複使用的程式碼區塊
函數有兩種定義方式,分別為函數語句 ( Function statement ) 和函數運算式 ( Function expression)
Statement 和 Expression 差別
Statement 是不會直接回傳值,因此也無法被定義成變數,像是 if
Expression 則是說輸入後可以直接回傳值的一段程式,可以被存成變數,像是 a === b
Expression 則是說輸入後可以直接回傳值的一段程式,可以被存成變數,像是 a === b
函數語句定義法
函數語句定義法與其他語言類似,使用 function 來宣告,如下:function 函數名稱 ( 參數1 , 參數2 ... ) { 內部語句 }
函數運算式定義法
函數運算式為建立匿名函數物件,並將引用賦值給左邊的函數型變數,如下let log = function(log) { console.log(log); }
兩者的區別
平常應盡量使用函數語句定義法,因為此種方法較為標準、簡潔,且函數語句定義法在執行的時候會被提升 ( hoisting ) 到最前面,也就是說只要該 function 有被定義,都可以被成功呼叫,而函數運算式定義法則是一定要先定義,否則會報錯,如下:testA(); // 報錯,函數未定義 testB(); // B let testA = function() { console.log('A');} function testB() { console.log('B'); }
兩者還有一個差別,因為函數運算式並非像物件一樣獨立存在,等同於匿名函數,因此當引用這個函數的物件消失或不再使用的話,該函數就會被回收
參數
在 JavaScript 的世界裡面沒有分成傳值和傳引用,所以參數傳入都是引用,但是如果參數為基本資料型別的話,傳值和傳引用是一樣的;其他資料型別的話都是傳入引用,如下:let a = 1; let b = [1,2]; let c = {1:1, 2:2}; function testA(a, b, c) { a = a + 1; b.push(3); c[1] = 3; } console.log(a); // 1 console.log(b); // [1, 2, 3] console.log(JSON.stringify(c)); // {1:3 , 2:2}
變數 a 為基本資料型別,因此只有在 function 裡面的 a 改變了,變數 b 和變數 c 都是傳入引用,因此不論內外都被改變了
預設參數
在宣告函數的時候,可以為傳入的參數設定預設值,當呼叫函數但是沒有帶入參數時,就會由預設值代替,如下:function add(a=1, b=2) { console.log(a + b); } add(); // 3 add(2); // 4 add(2,3); // 5
存取參數資訊
傳入函數的參數都會被保留在一個自動建立的 arguments 物件中,並依照傳入的順序存入,如下:
function add(a=1, b=2) { console.log(JSON.stringify(arguments)); } add(); // {} add(1,2); // {"0":1, "1":2} add(1,2,3); // {"0":1, "1":2, "2":3}
需要注意的是,雖然 arguments 看起來很像陣列,但是他僅具有索引、length 屬性和callee屬性,callee 為一個持有指向目前函數的引用,通常被用來建立遞迴,但是 callee 在嚴謹模式中已經被禁用了,如下:
function add(a=1, b=2) { console.log("a: "+a+" b: "+b); if(a > 1) { arguments.callee(a-1,b-1); } } add(1,2); // a: 1 b: 2 // a: 0 b: 1
除了 arguments 之外,其餘參數 ( Rest Operator ) 也可以達到同樣的效果,而且其餘參數是真的陣列,如下:
function add(a=1, b=2, ...params) { console.log(JSON.stringify(params)); } add(); // [] add(2); // [] add(2,3); // [] add(2,3,4); // [4]
由上面兩個範例可以發現說,如果函數有預先定義參數的話,對於其餘參數而言傳入的值會先給預先定義的參數,其餘的才會存入,而 arguments 則是傳入的值通通存入
展開運算子 ( Spread Operator )
如果將 ...params 用在 console 等情況下,會變成所謂的展開運算子,也就是把陣列中的值一個一個分開來,如下:
let show = [1,2,3,4]; console.log(...show); // 1 2 3 4
函數的本質
function add(a=1, b=2) { console.log(JSON.stringify(arguments)); } let functionType = Object.getPrototypeOf(add).constructor.name; console.log(functionType); // Function functionType = Object.getPrototypeOf(Object.getPrototypeOf(add)).constructor.name; console.log(functionType); // Object
函數可以擁有獨立的屬性和方法,而函數一旦被執行的話,就會建立一種名為 Active Object 不可存取的特殊物件,同時每個函數都有內建的範圍鏈 ( Scope chain ) ,也可以稱為有效範圍,如下:
let a = 1; let show = function(){ let a = 10; a = a + 100 return a; } console.log(show()); // 110 console.log(a); // 1
因為在 show 裡面有定義 a ,因此 return 的 a 會先抓到內部定義的 a(區域變數),因此 console 的結果為內部的 10 + 100 ,但是這個變數 a 只存在於函數內,與外部定義的 a 為不同個、互不影響,所以第二次 console 的結果就直接是外部定義的 a
如果內部沒有定義的話,函數則會往外抓取變數(全域變數),如下:
let a = 1; let showB = function(){ a = a + 100; return a; } console.log(showB()); // 101 console.log(a); // 101
所以如果抓取的是全域變數,而且在函數內更改的話,就會有所影響了
沒有留言:
張貼留言