JavaScript函式(二)
在前面我們已經對函式作了簡單的介紹,比如函式的定義、函式的宣告、函式的呼叫和函式的傳參等。本節將進一步介紹函式的應用,深度理解函式的各種使用。
函式是一個物件,每個函式時Function型別的一個例項,與其他引用型別一樣有屬性和方法。由於函式時物件,因此函式名實際上也是一個指向函式物件的指標,不會與某個函式繫結。函式通常是函式宣告語法定義的。
function sum(x,y){
return x+y;
}
上面使用函式宣告語法定義的。
var sum=function(a,b){ return a+b; };
上面使用函式表示式聲明瞭一個函式。定義了變數sum,並將其初始化為一個函式。在使用函式表示式定義函式時,沒有必要使用函式名。通過變數sum可以引用函式,另外函式的末尾有個分號,與普通的變數定義一樣。
另外可以使用Function建構函式來定義函式。Function建構函式可以定義任何數量的引數,最後一個函式被看成是函式體。
var sum= new Function("a","b","return a+b");
function sum(a,b){ return a+b; } var anotherSum=sum; console.log(sum(1,2)); sum=null; console.log(anotherSum(2,3));
上面定義了函式sum,用於求兩個數之和。定義了變數anotherSum,並將sum賦值給anthorSum。這樣anotherSum與sum指向了同一個指標。最後呼叫的時候,即使將sum=null,依然可以呼叫anotherSum。
var sum=function(a,b){ return a+b; } sum=function(a,b){ return a*b; } console.log(sum(2,4));//輸出8
上面的程式碼可以清楚地看出函式時沒有過載的。後定義的sum將前面的sum覆蓋。
JavaScript解析器在執行環境時,對於函式宣告和函式表示式定義的函式並不是一視同仁的。解析器會先呼叫函式的宣告,在執行其他程式碼之前可用。函式表示式,只有當函式執行到當前行,才會被解釋執行。
console.log(sum(2,3));//輸出5
function sum(a,b){
return a+b;
}
console.log(subtract(2,3));//報錯,subtract is not function
var subtract=function(a,b){
return a*b;
}
上面的程式碼可以看出區別。函式的宣告在呼叫前已經新增到執行環境中,會將函式宣告新增到頂部環境中。而通過函式表示式定義的函式,在宣告變數前,就呼叫會發生錯誤。經過上面的比較,不管是函式宣告,還是函式表示式,我們都要養成先定義,再使用的習慣。
JavaScript函式本身就是變數,所以函式可以作為值來使用。函式不僅可以作為引數來傳遞,而且可以將函式作為另一個函式的結果返回。
function callFunction(func,num){
return func(num);
}
function sum(num){
return num+10;
}
console.log(callFunction(sum,10));
上面的程式碼就是將函式作為另一個函式的結果返回,輸出20.
函式內部包含一個子函式,並且返回子函式,這就是所謂的函式閉包。閉包的好處就是可以快取資料。
function closeTest(){
var n=1;
Add=function(){
n=n+1;
}
return function F(){
return n+1;
}
}
var result = closeTest();
console.log(result());
Add();
console.log(result());
上面的程式碼就是一個閉包函式。在閉包函式內部可以使用函式的內部變數。第一個呼叫result,返回2.第二次呼叫result輸出3,因為前面已經呼叫了add方法,將n+1,所以最好輸出的結果是3.
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
var result=object.getNameFunc();
console.log(result());//undefined,this的指向是window
通過上面的程式碼可以看清楚,呼叫result方法的時候,this指向了window,this.name為undefined
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
}.bind(this);
}
};
var result=object.getNameFunc();
console.log(result());//My Object
這個程式碼我們加了bind方法,bind函式用於改變this的指向。this.name指向的是object物件,所以result有輸出值。bind函式並不會立即執行,當呼叫該函式的時候才會執行,同時bind函式可以給函式繫結引數。
var substract=function(a,b){
return a*b;
};
var sum=function(a,b){
var that=this;
console.log(that);
return a+b;
}.bind(substract,3,4);
console.log(sum(1,2));//輸出7
上面的程式碼使用bind函式改變了sum函式的this指向,this指向了substract函式,同時為sum添加了兩個引數3,4.雖然我在呼叫的時候又傳遞了引數1,2,但是我們bind的引數值是在前面的,所有輸出7.arguments[2]=1,arguments[3]=2。傳遞的引數通過arguments依然可以訪問到。
函式還有兩個常用的方法apply和call。apply和call兩個方法的效果是改變函式的指向,並能傳遞引數,但是立即執行。apply第一個引數是物件,用於改變函式的this指向;第二個引數是陣列,是傳遞給函式的值。call的第一個引數與apply一樣,但是call的第二個引數不是陣列,與bind類似,引數是一個一個傳遞的。
function sayHello(str){
console.log(str);
}
sayHello.bind(this,"bind");
sayHello.call(this,"call");
sayHello.apply(this,["apply"]);
上面的程式碼,我們比較了函式的bind、call和apply方法的不同。最後我們看輸出的值為 call apply。但是bind並沒有輸出,所以說bind並不能讓函式立即呼叫。函式呼叫bind方法之後,函式依然是一個函式。
sayHello.bind(this,"bind")();這個程式碼會輸出“bind”
apply方法和call方法還可以在函式內部進行呼叫,並能改變this的指向。
function Super(){
this.a="10";
}
function Sub(){
Super.call(this);
this.b="20";
}
var sub = new Sub();
console.log(sub.a);
console.log(sub.b);
上面的程式碼有點面向物件繼承的意思。通過在sub內部呼叫super的call方法,讓sub也擁有了super的屬性。最後的輸出結果是 10 20。
函式還擁有一些屬性可以使用。如果讓我們寫一個階乘函式,大多數人都會這樣寫:
function factorial(n){
if(n<1){
return 1;
}
else{
return n*factorial(n-1);
}
}
階乘演算法會用到函式的遞迴,上面的方法將函式名在函式內部再次呼叫,增加了函式的耦合。我們可以使用函式的屬性callee來解耦。callee是函式的屬性,它是一個指標,指向擁有這個函式名的函式。
上面的程式碼我們可以使用arguments.callee替換函式。
function factorial(n){
if(n<1){
return 1;
}
else{
return n*arguments.callee(n-1);
}
}
console.log(factorial(10));//3628800
閉包可以減少全域性變數的使用。
(function() {
window.oEvent=window.oEvent||{};
var addEvent = function() { /*程式碼的實現省略了*/ };
function removeEvent() {}
oEvent.addEvent = addEvent;
oEvent.removeEvent = removeEvent;
})(window);
console.log(oEvent.addEvent());
上面的程式碼只定義了一個全域性變數