深入學習JavaScript函數(shù)


前言:

函數(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ù):thisarguments。參數(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é),不足的地方希望小伙伴們能夠提出來,大家一起學習,一起進步:)

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評論 19 139
  • 第3章 基本概念 3.1 語法 3.2 關鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,524評論 0 21
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(3).初始化時...
    歐辰_OSR閱讀 30,246評論 8 265
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,565評論 0 13
  • 小陳同學今天早早就爬起床了,昨晚餃子吃的真有點多,早飯讓他吃說不吃了不餓。做完一張數(shù)學卷子就去張凱瑞家玩去了。...
    史曉輝閱讀 206評論 0 0

友情鏈接更多精彩內(nèi)容