目錄
1.函數(shù)的聲明
2.函數(shù)的屬性和方法
3.函數(shù)的作用域
4. 函數(shù)的重載
5.閉包知識點
6.小結(jié)
關(guān)于函數(shù),可以從以下3個方面去理解:
首先,數(shù)據(jù)類型上看:函數(shù)在JavaScript中是一種數(shù)據(jù)類型,是對象的一種;
其次,從功能上看:函數(shù)本質(zhì)上是一段反復(fù)調(diào)用的代碼塊;
最后,從地位上看:函數(shù)在JavaScript中和其他基本數(shù)據(jù)類型一樣,可以作為參數(shù)和賦值,是“第一等公民”
1.函數(shù)的聲明
聲明函數(shù)的方式有三種:
1.聲明式
function fn(){
console.log(1)
}
fn()//1
2.表達式
var fn = function(args){
console.log(args)
}
fn(a)//a
3.構(gòu)造函數(shù)式
var fn = new Function("arg1","arg2","return arg1+arg2")
fn(1,2)//3
函數(shù)本質(zhì)上是對象的一種,所以函數(shù)名保存的實際上是指向函數(shù)對象的指針,第3種構(gòu)造函數(shù)的方法對于理解“函數(shù)是對象,函數(shù)名是指針”的概念更加直觀,但是第3種方法解析效率較低而且書寫不簡潔,所以一般不用構(gòu)造函數(shù)方法去聲明函數(shù);
當聲明式和表達式同時聲明同一個函數(shù)時,表達式會覆蓋聲明式,原因是函數(shù)作為變量在js解析階段進行變量提升,聲明式和表達式都會提升至當前作用域頭部,然后表達式會重新為fn賦值,從而覆蓋聲明式定義的函數(shù);
function fn(){
console.log(1)
}
var fn = function(){
console.log(2)
}
fn()//2
關(guān)于return語句的理解,如果聲明的函數(shù)沒有return語句,則默認return undefined,否則返回定義的值;函數(shù)會在執(zhí)行完return語句后就立即退出,位于return語句的代碼將不會執(zhí)行;
var a = function(){
console.log(1)
}
var b = function(){
return 2;
}
a() === undefined//true
2.函數(shù)的屬性和方法
函數(shù)作為一個對象,同樣擁有屬性和方法,下面主要歸納一下比較常用和重要的屬性和方法:
- name屬性:返回該函數(shù)名的字符串;
- length屬性:返回形參的個數(shù),即預(yù)期傳入?yún)?shù)的個數(shù);
function fn (a,b){
console.log(fn.name)
console.log(fn.length)
}
fn()
//"fn"
//2
-
arguments對象:是包含傳入函數(shù)實參的類數(shù)組對象,只有在函數(shù)執(zhí)行階段并且存在參數(shù)才會有值,未調(diào)用函數(shù)是為
null
;
function fn (a,b){
console.log(arguments)
}
fn(1,2)
//[1,2]
arguments對象的length屬性代表實參的個數(shù),注意和函數(shù)的length屬性的區(qū)別,函數(shù)的length代表形參的個數(shù),arguments的length屬性代表實參個數(shù);
arguments對象有一個callee屬性,返回arguments對象所在的函數(shù)指針;
可以利用callee實現(xiàn)函數(shù)的遞歸,例如累加或階乘操作:
function increment(arg){
if(arg === 1){
return 1
}
return arg+arguments.callee(arg-1)
}
function increMultipler(arg){
if(arg === 1){
return 1
}
return arg*arguments.callee(arg-1)
}
這里另外提一個函數(shù)的caller屬性,該屬性保存調(diào)用當前函數(shù)的函數(shù)的引用,注意的是如果在全局作用域下讀取該屬性,值為null,因為頂層對象在瀏覽器中為window不是函數(shù);
function outer(){
inner();
}
function inner(){
console.log(arguments.callee.caller)
}
outer();
- this對象:代表函數(shù)執(zhí)行時的環(huán)境對象,簡單的說就是誰調(diào)用了該函數(shù),this的指向是動態(tài)的,只有在函數(shù)調(diào)用時this對象才能確定;
//在瀏覽器全局環(huán)境下,即window對象下
var print = function(){
console.log(this)
}
print()//this指向Window,因為這是Window對象調(diào)用了print方法
//在特定對象的環(huán)境下
var o = {
print: function(){
console.log(this)
}
}
o.print()//this指向o,因為這是o對象調(diào)用print方法
函數(shù)提供call
、apply
和bind
3種方法可以改變this對象;
1.call方法
function fn(){
return this
}
var o = {}
fn() === this//true,this指向window對象
fn.call(o) === o//true,this指向o對象
//call方法還可以傳入?yún)?shù);
function add(x,y){
return x+y
}
add.call(null,1,2)
2.apply方法,與call不同的是apply傳入的參數(shù)為數(shù)組
var arr = [1,2]
function add(x,y){
return x+y
}
add.apply(null,arr)
實際上,apply和call的區(qū)別只在于傳遞參數(shù)的不同,它們真正強大的地方在于能夠擴充函數(shù)賴以運行的作用域,比如slice函數(shù)原本只存在于數(shù)組當中,當中通過使用call方法,可以實現(xiàn)不同作用域下調(diào)用該方法;
function fn(a,b){
console.log(Array.prototype.slice.call(arguments))
}
fn(1,2)//[1,2]
bind方法會創(chuàng)建一個函數(shù)實例,并將該函數(shù)的this對象綁定到傳入該方法的參數(shù);
function fn(){
return this
}
var o = {}
var newFn =fn.bind(o)
newFn() === o//true
更多this的相關(guān)介紹,詳見【what's this???】
3.函數(shù)的作用域
作用域指的是變量存在的范圍,作用域可分為全局作用域和局部作用域,變量在全局范圍可訪問到;局部作用域由函數(shù)所構(gòu)造,變量只能在函數(shù)內(nèi)部可訪問到;
var a =1//a處于全局作用域
function fn(){
var a = 2//a處于局部作用域,外部無法訪問;
return a;
}
a//1
fn()//2
值的注意的是,函數(shù)執(zhí)行時所在的作用域是定義時所在的作用域,而不是調(diào)用時所在的作用域;
var a = 1;
function fn(){
console.log(a)
}
function fn2(){
var a = 2;
fn()
}
fn2()//1
4.函數(shù)的重載
JavaScript的函數(shù)無法實現(xiàn)重載,所謂重載是為同一個函數(shù)編寫兩個不同的定義簽名(接受的參數(shù)的類型和數(shù)量);
JavaScript函數(shù)之所以沒有重載,是因為其參數(shù)受包含零個或多個值的arguments對象表示的,也就是說函數(shù)不存在函數(shù)簽名的特性;
function fn(num1){
return num1;
};
function fn(num1,num2){
return num1+num2;
};
上述代碼聲明了同一個函數(shù),雖然二者的函數(shù)簽名不同,但是js引擎在解析時會把后面的函數(shù)覆蓋前面的函數(shù);
實際上,在JavaScript中參數(shù)在內(nèi)部是用arguments對象存儲,它不介意你定義時設(shè)定1個、2個或多個參數(shù),它只在意函數(shù)在執(zhí)行階段時傳遞的實際參數(shù);
函數(shù)定義時設(shè)定參數(shù)唯一目的是為了對開發(fā)者編寫代碼和維護更加友好,即使你不定義函數(shù)簽名照樣可以引用參數(shù);
但是,一般在定義函數(shù)時是建議命名形參的,這樣更加符合編程規(guī)范;
可以利用JavaScript函數(shù)的以上特性,模擬出函數(shù)的重載功能;
function fn(){
if(arguments.length === 1){
return arguments[0]
};
if(arguments.length ===2){
return arguments[0]+arguments[1]
}
}
fn(5);
fn(5,10)
5. 閉包知識點
關(guān)于閉包的知識點,將會單獨開一章節(jié)詳談,具體請看《JavaScript閉包(三)》
6.小結(jié)
通過《JavaScript函數(shù)(二)》,我們大致了解關(guān)于函數(shù)的知識點如下:
- 函數(shù)本質(zhì)上是一段反復(fù)調(diào)用的代碼塊,是對象的一種,在js中作為“第一等公民”,可以賦值和傳參;
- 函數(shù)聲明的方法有3種:聲明式、表達式和構(gòu)造函數(shù)式;其中構(gòu)造函數(shù)是能夠直觀理解函數(shù)的“函數(shù)是對象,函數(shù)名是指針”的概念;當表達式和聲明式同時聲明同名函數(shù)時,表達式會覆蓋聲明式,原因是變量提升的作用;
- 函數(shù)作為對象,同樣具有屬性和方法;
name
返回該函數(shù)名的字符串,length返回形參的個數(shù); - arguments對象是包含傳入函數(shù)的實參的類數(shù)組對象,只有在執(zhí)行階段該對象才有值,未調(diào)用函數(shù)時為
null
,arguments.length表示實參的個數(shù),arguments.callee返回arguments對象所在的函數(shù)的指針; - 函數(shù)的caller返回調(diào)用當前函數(shù)的函數(shù)的指針,在全局作用域下讀取該屬性為null;
- this對象代表當前函數(shù)執(zhí)行時的環(huán)境對象,this對象只有在函數(shù)執(zhí)行階段才能確定;
- 可以使用call、apply和bind改變this的指向;call和apply的區(qū)別在于二者傳遞的參數(shù)不同,call為零散的數(shù)據(jù),apply為數(shù)組,二者最大的用處是擴展函數(shù)的作用域;bind方法可以返回一個函數(shù)的實例,并綁定this對象至傳入bind的參數(shù);
- 函數(shù)可以開辟一個獨立的作用域,這使得js當中經(jīng)典的閉包得以實現(xiàn)提供可能性;此外,函數(shù)執(zhí)行時所在的作用域是在定義時所在的作用域,而不是調(diào)用時所在的作用域;
- JavaScript的函數(shù)是無法實現(xiàn)重載功能的,但可以通過函數(shù)的結(jié)合arguments對象的特性模擬出重載的效果;
參考資料
- 《JavaScript高級程序設(shè)計(第3版)》
- 《JavaScript標準參考教程》——阮一峰