前言:
函數(shù)對于任何一門語言來說都是核心的概念,通過函數(shù)可以封裝任意多條語句,而且可以在任何地方、任何時候調(diào)用執(zhí)行。而JavaScript中最好的特性就是它對函數(shù)的實現(xiàn)。它幾乎無所不能。但是,函數(shù)在JavaScript中并非萬能的,就像《JavaScript語言精粹》中所說:函數(shù)在JavaScript里也并非萬能藥。
一、函數(shù)對象
在JavaScript中函數(shù)就是對象,對象是“名值”對的集合并擁有一個連到原型對象的隱藏連接。
我們都知道,對象字面量產(chǎn)生的對象連接到Object.prototype,例如:
var obj={
name:"JoeWright"
};
console.log(Object.prototype.isPrototypeOf(obj)); //true
而函數(shù)對象連接到Function.prototype(該原型對象本身連接到Object.prototype)。例如:
var foo=new Function("a","b","return a+b");
console.log(foo(2,3)); //5
console.log(obj.constructor); //? Object()
console.log(Function.prototype.isPrototypeOf(foo)); //true
//Function對象本身連接到Object.prototype
console.log(Object.prototype.isPrototypeOf(Function)); //true
一些屬性:
- constructor:返回創(chuàng)建該對象的構造函數(shù)
- length:返回函數(shù)定義的參數(shù)個數(shù)
- caller(ECMA標準之外的屬性):返回調(diào)用當前函數(shù)的函數(shù)
- argument:返回該函數(shù)執(zhí)行時內(nèi)置的arguments對象
例如:
function foo(a,b,c){
return foo2.caller;
}
function bar(){
return foo2();
}
console.log(foo.length); //3
console.log(foo.name); //foo
console.log(bar()); //f bar()
注意:Function構造函數(shù)雖然使你的腳本在運行時重新定義函數(shù)的情況下具有更大的靈活性,但它也會減慢代碼的執(zhí)行速度。為了避免減慢腳本速度,應該盡可能少使用Function構造函數(shù),多使用function關鍵字的形式聲明函數(shù)。
二、函數(shù)字面量
函數(shù)字面量包括四部分:
- 第一部分是保留字
function。 - 第二部分是函數(shù)名,它可以被省略(匿名函數(shù))。函數(shù)可以用它的名字來遞歸調(diào)用自己。
- 第三部分是包圍在圓括號中的一組參數(shù)(形參)。其中每個參數(shù)用逗號分隔。
- 第四部分是包圍在花括號中的一組語句
格式如下:
function functionName(parameters) {
執(zhí)行的代碼
}
例如:
function add(a,b) {
return a+b;
}
三、函數(shù)的調(diào)用
- 調(diào)用一個函數(shù)將暫停當前函數(shù)的執(zhí)行,傳遞控制權和參數(shù)給新函數(shù)。
- 函數(shù)除了聲明時定義的形式參數(shù),每個函數(shù)接收兩個附加的參數(shù):
this和arguments。參數(shù)this在面向?qū)ο缶幊讨蟹浅V匾?strong>它的值取決于調(diào)用的模式。 - 在
JavaScript中一共有四種調(diào)用模式:方法調(diào)用模式、函數(shù)調(diào)用模式、構造器調(diào)用模式和apply調(diào)用模式。這些調(diào)用模式在如何初始化關鍵參數(shù)this上存在差異。
上面講到在JavaScript中一共有四種調(diào)用模式,在這里我們分別對這幾種模式一一詳解。
方法調(diào)用模式
- 當一個函數(shù)被保存為對象的一個屬性時,我們稱它為一個方法
- 當一個方法被調(diào)用時,this被綁定到該對象(這句話對于我們理解this關鍵字非常重要?。。。。?/li>
- 如果一個調(diào)用表達式包含一個屬性存取表達式(即.表達式或者是[subscript]下標表達式,注意:如果屬性不確定,要用[])
下面是方法調(diào)用模式的一個例子:
var myObj={
name:"JoWright",
value:0,
increment:function(inc){
this.value+=typeof inc ==="number"?inc:1;
}
};
myObj.increment();
console.log(myObj.value); //1
myObj.increment(3)
console.log(myObj.value); //4
var key="name"; //將name屬性賦給比變量key,此時屬性名不確定
console.log(myObj.key); //undefined
console.log(myObj[key]); //JoeWright
this到對象的綁定 發(fā)生在調(diào)用的時候,這個綁定又稱為 “超級”遲綁定(very late binding)。這樣的綁定使得函數(shù)可以對this 高度復用。通過this可取得它們所屬對象的上下文的方法稱為公共方法
函數(shù)調(diào)用模式
- 當一個函數(shù)并非一個對象的屬性時,那么它被當作一個函數(shù)調(diào)用:
function add(a,b){
return a+b;
}
console.log(add(1,2)); //3
- 當函數(shù)以此模式調(diào)用時,this被綁定到全局對象(瀏覽器下為Window,node下為 global)。
例如:
var myObj={
value:1,
multiply:function(){
var add=function(){
return this.value*2;
};
console.log(add());
}
};
myObj.multiply(); //NaN
為什么是NaN呢?因為add方法是一個匿名函數(shù),此時add函數(shù)中的this綁定的是全局對象,不是myObj(實際上add方法是被Window對象調(diào)用的,所以它沒有對myObj對象的訪問權)
解決方法:利用 閉包 的特性(先用that變量來存儲this)
var myObj={
value:1,
multiply:function(){
var that=this;
var add=function(){
return that.value*2;
};
console.log(add());
}
};
myObj.multiply(); //2
構造器調(diào)用模式
-
JavaScript是一門基于原型繼承的語言。這意味著對象可以直接從其他對象繼承屬性。 - 如果在函數(shù)前面帶上new關鍵字來調(diào)用(當作構造函數(shù)調(diào)用),那么將創(chuàng)建一個隱藏連接到改函數(shù)的prototype成員的新對象,同時this 將會綁定到那個新對象上。
例如:
function Person(name){
this.name=name;
}
//為Person的所有實例提供一個名為getName的公共方法
Person.prototype.getName=function(){
return this.name;
}
var p=new Person("JoeWright");
console.log(p);//Person {name: "JoeWright"}
- 按照約定,構造器函數(shù)保存在以大寫格式命名的變量里。如果調(diào)用構造器函數(shù)時沒有在前面加上new,可能會發(fā)生非常糟糕的事情(即沒有編譯時的警告,也沒有運行時的警告),所以 大寫約定非常重要
Apply調(diào)用模式(類似的還有call、bind)
格式如下:
functionName.apply(thisArg[, [arg1, arg2, ...]])
- thisArg:將被綁定給this的值
- [arg1,arg2,..]:參數(shù)數(shù)組(可選)
例如:
function add(a,b){
return a+b;
}
var sum=add.apply(null,[3,4]);
console.log(sum); //7
function Person(name){
this.name=name;
}
//為Person的所有實例提供一個名為getName的公共方法
Person.prototype.getName=function(){
return this.name;
}
var Pet={
name:"dogg"
};
//Pet并沒有繼承自Person.prototype,但我們可以在Pet上調(diào)用getName方法,盡管Pet并沒有一個名為getName的方法。
var petName=Person.prototype.getName.apply(Pet);
console.log(petName); //dogg
四、參數(shù)
當函數(shù)被調(diào)用時,會得到一個“免費”奉送的參數(shù),這個參數(shù)就是arguments數(shù)組(arguments對象)。通過它函數(shù)可以訪問所有它被調(diào)用時傳遞給它的參數(shù)列表,這使得編寫一個無須指定參數(shù)個數(shù)的函數(shù)成為可能。
例如:
//用arguments對象求最大值
function max(){
var max=0;
for(var i=0,len=arguments.length;i<len;i++){
if(arguments[i]>max){
max=arguments[i];
}
}
return max;
}
console.log(max(2,1,4,3,7)); //7
arguments對象的callee屬性:引用當前被調(diào)用的函數(shù)對象
function bar(){
return arguments.callee;
}
console.log(bar()); //? bar()
典型應用:函數(shù)的遞歸調(diào)用
//匿名函數(shù)的遞歸調(diào)用
(
function(count){
if(count<=3){
console.log(count);
arguments.callee(++count);
}
}
)(0);//0 1 2 3
默認參數(shù)
實現(xiàn)方法一:
function add(x,y){
x=x||0;
y=y||1;
return x+y;
}
console.log(add()); //1
console.log(add(3,4)); //7
實現(xiàn)方法二:
function add(x,y){
x=x===undefined?0:x;
y=y===undefined?1:y;
return x+y;
}
console.log(add()); //1
console.log(add(3,4)); //7
五、返回
- return語句可以使函數(shù)提前返回。當return被執(zhí)行時,函數(shù)立即返回而不再執(zhí)行余下的語句。
- 一個函數(shù)總是會返回一個值。如果沒有指定值,就返回undefined
function f(a,b){
return a+b;
return "hello JoeWright"; //永遠不會被執(zhí)行
}
console.log(f(1,2)); //3
六、異常
JavaScript提供了一套異常處理機制。異常是干擾程序正常流程的非正常的事故。
例如:
var add=function(a,b){
if(typeof a!=="number"||typeof b!=="number"){
throw{
name:"TypeError",
message:"add needs numbers"
};
}
return a+b;
};
var try_it=function(){
try{
add("hello");
}catch(e){
console.log(e.name+":"+e.message);
}
}
try_it(); //TypeError add needs numbers
七、給類型添加方法
JavaScript允許給語言的基本類型添加方法。這樣的方式對函數(shù)(Function)、數(shù)組(Array)、字符串(String)、正則表達式(RegExp) 和 布爾值(Boolean) 同樣適用。
例如:String對象原來沒有reverse方法,我們可以從它的原型上擴展該方法。
String.prototype.reverse=function(){
return Array.prototype.reverse.apply(this.split("")).join("");
}
console.log("hello".reverse()); //olleh
八、遞歸
遞歸函數(shù)是直接或間接地調(diào)用自身的一種函數(shù)。
例如:
function sum1(n){
return n==1?1:f3(n-1)+n;
}
console.log(sum1(100)); //5050
//等價于
var sum2=function(n){ //匿名函數(shù)的遞歸
return n==1?1:arguments.callee(n-1)+n;
}
console.log(sum2(100)); //5050
九、作用域
在編程語言中,作用域控制著變量與參數(shù)的可見性及生命周期。這對開發(fā)者來說是一個重要的幫助,因為它減少了名稱沖突,并且提供了自動內(nèi)存管理
var fun1=function(){
var a=3,b=5;
var fun2=function(){
var b=7,c=11;
console.log(a+" "+b+" "+c); //3 7 11
a+=b+c;
console.log(a+" "+b+" "+c); //21 7 11
};
console.log(a+" "+b); //3 5
fun2();
console.log(a+" "+b);// 21 5
};
fun1();
作用域的知識就寫這么多,下次的文章深入學習:)
十、閉包
各種專業(yè)文獻上的"閉包"(closure)定義非常抽象,很難看懂。所以這里引用阮一峰老師的理解:
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù),閉包讓這些變量的值始終保持在內(nèi)存中,不會在調(diào)用結(jié)束后,被垃圾回收機制(garbage collection)回收。
閉包的例子:
function func1(){
var n=5;
function func2(){
console.log(n);
}
return func2;
}
var res=func1();
res(); //5
上面的例子中,func2函數(shù)就是閉包。
由于在Javascript語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡單理解成 "定義在一個函數(shù)內(nèi)部的函數(shù)"。
所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。
十一、回調(diào)
回調(diào)函數(shù)就是一個參數(shù),將這個函數(shù)作為參數(shù)傳到另一個函數(shù)里面,當那個函數(shù)執(zhí)行完之后,再執(zhí)行傳進去的這個函數(shù)。這個過程就叫做回調(diào)。
看下面的例子:
function addOne(a){
return a+1;
}
function test(a,b,c,callback){
var array=[];
for(var i=0;i<3;i++){
array[i]=callback(arguments[i]*2);
}
return array;
}
console.log(test(10,20,30,addOne)); //21,41,61
//匿名函數(shù)的寫法(推薦)
console.log(test(10,20,30,function(a){return a+1}));//21,41,61
十二、模塊
模塊就是實現(xiàn)特定功能的一組方法。
原始寫法:
function f1(){
//...
}
function f2(){
//...
}
上述函數(shù)f1、f2組成了一個模塊,要使用時直接調(diào)用就可以了
缺點:容易造成變量污染
對象寫法:
var obj={
name:"JoeWright",
f1:function(){
//...
},
f2:function(){
//...
}
};
上面的函數(shù)f1()和f2(),都封裝在obj對象里。使用的時候,就是調(diào)用這個對象的屬性obj.f1()。
缺點:暴露所有模塊成員,內(nèi)部狀態(tài)可以被外部改寫,例如:obj.name="Tom";
完善:
var obj={};
Object.defineProperties(obj,{
name:{
value:"JoeWright",
writable:false //設置可寫為false(默認為 false)
},
f1:function(){
//...
},
f2:function(){
//...
}
});
obj.name="Joe";
console.log(obj.name); //JoeWright
立即執(zhí)行函數(shù)寫法:
var obj=(function(){
var value="JoeWright";
var f1=function(){
//...
};
var f2=function(){
//...
}
return {
f1:f1,
f2:f2
};
});
console.info(obj.value); //undefined
使用上面的寫法,外部代碼無法讀取內(nèi)部的value變量。
十三、套用
函數(shù)也是值,從而我們可以用有趣的方式去操作函數(shù)值。套用允許我們將函數(shù)與傳遞給它的參數(shù)相結(jié)合去產(chǎn)生一個新函數(shù)。
例如:
function sum1(n){
return n==1?1:f3(n-1)+n;
}
console.log(sum1(100)); //5050
var sum2=sum1; //把sum1函數(shù)賦給變量sum2,實現(xiàn)套用
console.log(sum2(100)); //5050
完結(jié)
以上就是我對js函數(shù)的一些總結(jié),不足的地方希望小伙伴們能夠提出來,大家一起學習,一起進步:)