本文只是關(guān)于JavaScript函數(shù)的一些入門介紹,寫的有些寬泛和簡(jiǎn)單,目的是對(duì)JavaScript的函數(shù)有一個(gè)大概的理解谐丢。
函數(shù)定義
聲明函數(shù)
一般創(chuàng)建(聲明)一般分成四部分:
- function關(guān)鍵字
- 函數(shù)名稱
- 函數(shù)的參數(shù)列表
- 函數(shù)體:包含需要執(zhí)行的所有語(yǔ)句爽航。特別注意return語(yǔ)句,它決定了調(diào)用函數(shù)的返回值。
一般格式如下示例:
function findMax(a, b) {
if (a >b ) {
return a;
} else {
return b;
}
}
//函數(shù)調(diào)用
var maxNumber=findMax(12, 56);
函數(shù)表達(dá)式
把上面函數(shù)聲明中的代碼去除函數(shù)名稱,就得到了一個(gè)函數(shù)表達(dá)式乾忱。一般可以把一個(gè)函數(shù)表達(dá)式賦值給一個(gè)變量,
然后我們就可以把這個(gè)變量當(dāng)成函數(shù)名稱一樣使用,進(jìn)行函數(shù)調(diào)用讥珍。把上面的函數(shù)聲明修改成表達(dá)式方式的創(chuàng)建,如下示例
var findMax = function (a, b){
if (a >b ) {
return a;
} else {
return b;
}
}
//函數(shù)調(diào)用
var maxNumber=findMax(12, 56);
使用 new Function()創(chuàng)建函數(shù)
除了上面的函數(shù)聲明和函數(shù)表達(dá)式的方式創(chuàng)建函數(shù),我們也可以使用new Function()
的方式創(chuàng)建函數(shù)。new Function()
傳入的最后一個(gè)參數(shù)是函數(shù)體,
其他參數(shù)是創(chuàng)建的函數(shù)的函數(shù)參數(shù)窄瘟。上面的示例修改成new Function()
方式如下:
var fundMax=new Function('a','b','if(a>b){return a;} else{return b;}');
//函數(shù)調(diào)用
var maxNumber=findMax(12, 56);
自調(diào)用函數(shù)
自調(diào)用函數(shù)一般用于一次性調(diào)用,而相比于直接把函數(shù)體中的代碼寫在全局作用域,這種方式可以避免全局作用域變量污染串述。很多JS類型就是使用這種方式編寫的。
示例代碼如下:
var maxNumber = (
function(a, b){
if (a >b ) {
return a;
} else {
return b;
}
}
)(12,56);
函數(shù)聲明的提升(Hoisting)
函數(shù)聲明的提升與變量聲明的提升是一樣的概念,JS解析器會(huì)把函數(shù)的聲明提升到作用域的最上面執(zhí)行寞肖。所以可以在聲明函數(shù)之前進(jìn)行函數(shù)調(diào)用纲酗。
注意:函數(shù)表達(dá)式和new Function()的方式創(chuàng)建函數(shù)是不會(huì)被提升的。
如下示例:
//在函數(shù)聲明前進(jìn)行函數(shù)調(diào)用
var maxNumber=findMax(12, 56);
//函數(shù)聲明
function findMax(a, b) {
if (a >b ) {
return a;
} else {
return b;
}
}
函數(shù)是一種特殊的對(duì)象
JS中除了值類型(string/number/boolean/null/undefined)的變量,其他變量都是引用類型新蟆。而所有的引用類型的變量都是繼承Object對(duì)象觅赊。
函數(shù)也是一種特殊的對(duì)象,也是繼承自O(shè)bject∏淼荆可以使用如下代碼進(jìn)行測(cè)試吮螺。
function findMax(a, b){
if (a >b ) {
return a;
} else {
return b;
}
}
//所有的函數(shù)都是Function類型的
console.log(findMax instanceof Function); //輸出true
//而Function是繼承自O(shè)bject,所以所有的函數(shù)也是繼承自O(shè)bject。(也就是所有的函數(shù)都是Object的子類)
console.log(findMax instanceof Object); //輸出true
函數(shù)參數(shù)
函數(shù)的顯示參數(shù)和隱式參數(shù)
在函數(shù)聲明中的參數(shù)列表的參數(shù)都稱為顯示參數(shù),也叫做形參帕翻。
在函數(shù)調(diào)用時(shí)傳入的所有的參數(shù)都稱為隱式參數(shù),也叫做實(shí)參鸠补。
如下代碼示例:
//這是函數(shù)聲明。a和b兩個(gè)參數(shù)稱之為函數(shù)的顯示參數(shù),也叫做形參嘀掸。
function findMax(a, b) {
if (a >b ) {
return a;
} else {
return b;
}
}
//這是函數(shù)調(diào)用紫岩。傳入的12和56成為函數(shù)的隱式參數(shù),也叫做實(shí)參。
var maxNumber=findMax(12, 56);
函數(shù)參數(shù)的一些規(guī)則
JS是弱類型語(yǔ)言,所以函數(shù)參數(shù)存在如下一些規(guī)則:
- 聲明函數(shù)的時(shí)候不需要指定形參的數(shù)據(jù)類型
- 函數(shù)調(diào)用的時(shí)候不會(huì)對(duì)實(shí)參的數(shù)據(jù)類型和個(gè)數(shù)進(jìn)行檢測(cè)睬塌。
如果需要對(duì)函數(shù)參數(shù)的數(shù)據(jù)類型和個(gè)數(shù)進(jìn)行檢測(cè),則需要自己在函數(shù)內(nèi)部編寫代碼進(jìn)行參數(shù)類型和個(gè)數(shù)的檢測(cè)泉蝌。
函數(shù)參數(shù)的默認(rèn)值
很多語(yǔ)言都可以對(duì)函數(shù)的參數(shù)設(shè)置默認(rèn)值:當(dāng)調(diào)用函數(shù)時(shí),對(duì)應(yīng)的參數(shù)沒有傳入,則該參數(shù)的值就為設(shè)置的默認(rèn)值。
但是在ES6版本
之前的JS語(yǔ)法是不支持函數(shù)參數(shù)默認(rèn)值設(shè)置的揩晴。所以我們使用如下的方式實(shí)現(xiàn)函數(shù)參數(shù)默認(rèn)值的效果:
function findMax(a, b) {
//相當(dāng)于給參數(shù)a設(shè)置默認(rèn)值為0
a = a || 0;
//相當(dāng)于給參數(shù)b設(shè)置默認(rèn)值為0
b = b || 0;
if (a >b ) {
return a;
} else {
return b;
}
}
函數(shù)的arguments對(duì)象
在函數(shù)體中我們可以使用arguments對(duì)象來(lái)獲取函數(shù)調(diào)用時(shí)傳入的實(shí)參數(shù)據(jù)勋陪。這并不受形參的影響。如下代碼示例:
//沒有一個(gè)形參
function findMax(){
//使用使用arguments.length獲取實(shí)參個(gè)數(shù),可以像數(shù)組一樣訪問實(shí)參的值
for(var i=0, maxNumber=arguments[0]; i< arguments.length; i++){
if(arguments[i] > maxNumber){
maxNumber = arguments[i];
}
}
return maxNumber;
}
//函數(shù)調(diào)用傳入的實(shí)參
var maxNumber=findMax(12,-1,34,544,55,0);
函數(shù)參數(shù)的值傳遞和引用傳遞
數(shù)據(jù)類型分成兩大類:值類型和引用類型硫兰。如果函數(shù)調(diào)用時(shí),實(shí)參是值類型就成為值傳遞,實(shí)參為引用類型就成為引用傳遞诅愚。如果需要深入了解其中的原理,
就需要知道值類型和引用類型變量的內(nèi)存分配機(jī)制和內(nèi)存中堆
和棧
的知識(shí)。這里不做深入,相關(guān)知識(shí)網(wǎng)上有很多劫映。
這里通過如下示例解釋一下參數(shù)的值傳遞和引用傳遞违孝。
function double(number,numberList){
number *= 2;
for(var i=0; i< numberList.length; i++){
numberList[i] *= 2;
}
}
var num=3,
numList=[3, 5];
double(num,numList);
//因?yàn)閚um是值類型,所以num在double(num,numList)函數(shù)執(zhí)行后值沒有改變:3
console.log(num);
//因?yàn)閚umList是引用類型,所以numList在double(num,numList)函數(shù)執(zhí)行之后值發(fā)生了改變:[6,10]
console.log(numList);
函數(shù)調(diào)用
把函數(shù)調(diào)用表達(dá)式看作一個(gè)值使用
可以把函數(shù)調(diào)用看作一個(gè)值來(lái)使用刹前,這個(gè)值是由函數(shù)的返回值決定的。如下示例代碼:
function findMax(){
for(var i=0,maxNumber=arguments[0]; i<arguments.length; i++){
if(maxNumber<arguments[i]){
maxNumber=arguments[i];
}
}
return arguments[i];
}
//這里我們就可以把函數(shù)調(diào)用findMax(12,45,2)看作一個(gè)整數(shù)等浊,因?yàn)樵摵瘮?shù)調(diào)用表達(dá)式返回一個(gè)整數(shù)腮郊。
var maxDouble=findMax(12,45,2) * 2;
全局函數(shù)
在全局作用域中創(chuàng)建的函數(shù)叫做全局函數(shù)。而web應(yīng)用中所有的全局變量和函數(shù)都屬于全局對(duì)象window
的屬性筹燕。
所以我們可以把全局函數(shù)看作是window對(duì)象的方法轧飞。如下示例代碼:
function sum(a, b){
return a+b;
}
var a;
//下面兩個(gè)調(diào)用是等價(jià)的
a= sum(1,3);
a=window.sum(1,3);
函數(shù)中的this關(guān)鍵字
函數(shù)中的this是一個(gè)關(guān)鍵字,this指向調(diào)用當(dāng)前函數(shù)的對(duì)象撒踪。如下示例:
function setFirstName(firstName){
this.firstName=firstName;
}
//在全局作用域中直接調(diào)用过咬,則this指向全局對(duì)象(window)。因?yàn)樵谌终{(diào)用setFirstName()相當(dāng)于window.setFirstName()
setFirstName("james"); //等價(jià)于window.setFirstName("james");
var obj={
firstName:"pual",
setFirstName:setFirstName
};
//這是this指向obj對(duì)象制妄。
obj.setFirstName("james");
//把函數(shù)當(dāng)成構(gòu)造函數(shù)使用時(shí)掸绞,this指向構(gòu)造的對(duì)象。
var obj1=new setFirstName("james");
函數(shù)作為方法調(diào)用
當(dāng)函數(shù)作為對(duì)象成員的時(shí)候耕捞,我們稱該函數(shù)為對(duì)象的方法衔掸。這時(shí)我們可以通過對(duì)象來(lái)調(diào)用該方法。
如下示例:
var person={
firstName:"james",
lastName:"young",
getFullName:function(){
return this.firstName + " " + this.lastName;
}
};
//通過對(duì)象調(diào)用該對(duì)象的方法
var fullName=person.getFullName();
構(gòu)造函數(shù)
構(gòu)造函數(shù)本身就是一個(gè)普通的函數(shù)俺抽。所以所有的函數(shù)都可以是構(gòu)造函數(shù)敞映,只需要在函數(shù)調(diào)用的前面加上new
關(guān)鍵字時(shí),
我們就稱該函數(shù)為構(gòu)造函數(shù)磷斧。但是構(gòu)造函數(shù)的一般情況下主要目的是用于構(gòu)造對(duì)象振愿。
如下示例:
function Person(name,age){
this.name=name;
this.age=age;
this.grow=function(){
this.age += 1;
}
}
//使用上面的函數(shù)構(gòu)造對(duì)象,則這個(gè)函數(shù)就稱為構(gòu)造函數(shù)了
var person=new Person("趙四",34);
//使用對(duì)象成員(屬性和方法)
console.log(person.name);
person.grow();
函數(shù)的call和apply方法
函數(shù)是對(duì)象類型的弛饭,所以函數(shù)也有屬性和方法冕末。函數(shù)有兩個(gè)常用的方式:call
和apply
。
如果存在一個(gè)函數(shù)dumpObject
,即便它不是某個(gè)對(duì)象obj
的方法侣颂,
也可以通過dumpObject
函數(shù)的call
或者apply
方法實(shí)現(xiàn):對(duì)象obj
對(duì)函數(shù)dumpObject
的調(diào)用,
也就是函數(shù)dumpObject
中的this
關(guān)鍵字指向?qū)ο?code>obj档桃。
如下示例:
function dumpObject(prefix,suffix){
var dumpString=prefix;
for(var prop in this){
dumpString += prop + ':' + this[prop] + '\n\r';
}
dumpString += suffix;
console.log(dumpString);
}
//創(chuàng)建一個(gè)對(duì)象,該對(duì)象不存在dumpObject方法
var obj={
name:"趙四",
age:78,
score:70
};
//可以使用函數(shù)的call或者apply方法實(shí)現(xiàn):obj像調(diào)用自己方法一樣的效果調(diào)用dumpObject函數(shù)
var prefix='***start***\n\r',
suffix='***start***\n\r';
dumpObject.call(obj,prefix,suffix);
//從函數(shù)調(diào)用中可以看出横蜒,apply與call的唯一不用是:apply方法通過數(shù)組的形式胳蛮,把函數(shù)dumpObject的參數(shù)組織成apply的一個(gè)參數(shù)。
dumpObject.apply(obj,[prefix,suffix]);
閉包
什么是閉包
閉包的概念其實(shí)很簡(jiǎn)單,一句話就可以表達(dá)丛晌。
閉包是可以訪問上一層函數(shù)作用域里變量的函數(shù),即便上一層函數(shù)已經(jīng)關(guān)閉个盆。
但是需要理解這句話還是需要些功夫的哗讥。首先提取一下這句話,得到三個(gè)關(guān)鍵點(diǎn):
- 閉包是函數(shù)
- 閉包可以訪問上一層函數(shù)作用域變量:閉包是內(nèi)嵌函數(shù),而且可以訪問了上一層函數(shù)的局部變量。
- 即便是上一層函數(shù)已經(jīng)關(guān)閉:需要把閉包使用
return
語(yǔ)句返回躏鱼。
還需要清楚的一個(gè)概念是:JavaScript中所有函數(shù)都能訪問它們上層作用域中的變量蜕窿。
閉包實(shí)現(xiàn)一個(gè)計(jì)數(shù)器的實(shí)例代碼:
//使用一個(gè)自調(diào)用函數(shù),得到了一個(gè)閉包
var counter=(
function(){
var count = 0;
//返回的函數(shù)就是閉包,訪問了上一層函數(shù)的count局部變量
return function(){
count += 1;
}
}
)();
為什么需要閉包
上面介紹了什么是閉包谋逻。但是我們?yōu)槭裁葱枰]包呢?需要明白這個(gè)問題前需要了解局部變量
和全局變量
的局限性,已經(jīng)閉包的特性呆馁。
局部變量
的特點(diǎn)是:函數(shù)執(zhí)行完就被銷毀,所以無(wú)法實(shí)現(xiàn)變量的累積(如計(jì)數(shù)器的功能)。
全局變量
的特點(diǎn)是:在全局都可以訪問,只有在頁(yè)面關(guān)閉后才銷毀毁兆≌懵耍可以實(shí)現(xiàn)指定的計(jì)數(shù)器功能。問題在于:全局變量是所有地方都可以修改,
而且盡量少使用全局變量,避免全局變量污染
气堕。
針對(duì)全局變量
和局部變量
在這種場(chǎng)景下存在的問題,閉包
的相關(guān)特性正好能夠解決這個(gè)問題纺腊。因?yàn)殚]包可以使用上一層函數(shù)的局部變量,
這樣避免了全局變量的污染
。而且因?yàn)殚]包被上一層函數(shù)返回,并且被外部引用,所以閉包引用了的上層函數(shù)的變量在上層函數(shù)結(jié)束后依然不會(huì)被銷毀茎芭。
這樣又避免了普通局部變量
在函數(shù)執(zhí)行完就被銷毀,無(wú)法累積的問題揖膜。
所以這就是閉包的特性。也是閉包被使用的場(chǎng)景梅桩。
在編寫javascript代碼時(shí)壹粟,我們也要警惕閉包
帶來(lái)的問題:引用的局部變量遲遲不能釋放,容易導(dǎo)致內(nèi)存的泄露。
這里只是簡(jiǎn)單的介紹了一下閉包,以后有機(jī)會(huì)可以專門寫一篇關(guān)于閉包
的文章宿百。