@(javascript)[js函數(shù)]
[toc]
JavaScript中的函數(shù)
函數(shù)的分類與定義函數(shù)的方式
JavaScript中的函數(shù)可以分為兩類:有名函數(shù)
與匿名函數(shù)
徐伐。而定義函數(shù)的方式有兩種:函數(shù)聲明
與函數(shù)表達式
嗜傅。
目標:定義一個函數(shù) fn ==> 有名函數(shù)
// 使用函數(shù)聲明
function fn(){
// 函數(shù)執(zhí)行體
}
// 使用函數(shù)表達式
var fn = function(){
// 函數(shù)執(zhí)行體
}
使用函數(shù)聲明的重要特征就是函數(shù)聲明提升
损同,即在讀取代碼前會先讀取函數(shù)聲明菩混。函數(shù)名()
表示執(zhí)行函數(shù)
看看下面的代碼沒有任何問題。
// 定義一個有名函數(shù) fn1 使用函數(shù)聲明
function fn(){
console.log("fn1")
}
// 調(diào)用函數(shù) fn1
fn1(); // fn1
// 定義一個有名函數(shù) fn2 使用函數(shù)表達式
var fn2 = function(){
console.log("fn2")
}
// 調(diào)用函數(shù) fn2
fn2(); // fn2
但是如果是把調(diào)用放在定義函數(shù)前面汰扭,使用函數(shù)表達式
的就會報錯(Uncaught ReferenceError: fn1 is not defined)
// 調(diào)用函數(shù) fn1
fn1(); // fn1
// 定義一個有名函數(shù) fn1 使用函數(shù)聲明
function fn(){
console.log("fn1")
}
// 調(diào)用函數(shù) fn2
fn2(); // Uncaught ReferenceError: fn1 is not defined
// 定義一個有名函數(shù) fn2 使用函數(shù)表達式
var fn2 = function(){
console.log("fn2")
}
這就是使用兩種的區(qū)別德崭。
函數(shù)的返回值
每一個函數(shù)在調(diào)用的時候都會默認返回一個undefined
。
function fn(){
console.log(1)
}
fn(); // 1
console.log(fn); // console出一個函數(shù) 即 fn
console.log(fn()); // undefined
這里需要注意的地方就是關(guān)于函數(shù)執(zhí)行過程
與函數(shù)執(zhí)行結(jié)果
弟胀。
fn()
表示調(diào)用函數(shù)楷力。那就會執(zhí)行函數(shù)體。并默認返回一個undefined
孵户。只不過這個值undefined
沒有變量接收或者說是我們沒有用這個值萧朝。
console.log(fn)
就只是console出一個變量fn
的值。只不過這個值是一個函數(shù)夏哭。
console.log(fn())
與第一個的區(qū)別就是函數(shù)執(zhí)行了并返回了一個結(jié)果检柬。這個結(jié)果呢與上面不同的就是現(xiàn)在這個結(jié)果我們用上了(放在了console)里面。再由于值是undefined
,所以console了一個undefined
何址。
既然函數(shù)是可以有返回值的里逆,并且這個值默認是一個undefined
。那我們可以可以修改呢用爪?答案是可以的原押。
我們使用return
語句可以讓函數(shù)返回一個值
function fn(){
console.log(1)
return "哈哈"
}
fn(); // 1
console.log(fn); // 函數(shù) fn
console.log(fn()); // 哈哈
可以看一下第一個與第二個的結(jié)果與之前的是相同的。但是第三個的結(jié)果就不同了偎血,他是字符串哈哈
诸衔。因為我們修改了函數(shù)的默認返回值。
所以颇玷,可以把默認函數(shù)理解成這樣的
function fn(){
return undefined;
}
而我們修改返回值就是吧這個undefined給變成其他的值了笨农。
注意:
函數(shù)的返回值可以是任意的數(shù)據(jù)類型。
函數(shù)參數(shù)
函數(shù)是可以接收參數(shù)的帖渠,在定義函數(shù)的時候放的參數(shù)叫形式參數(shù)
谒亦,簡稱形參
。在調(diào)用函數(shù)的時候傳遞的參數(shù)叫實際參數(shù)
空郊,簡稱實參
诊霹。一個函數(shù)可以擁有任意個參數(shù)
function fn(a,b){
console.log(a)
console.log(b)
console.log(a+b)
}
// 調(diào)用函數(shù)并傳遞參數(shù)
fn(3,5); // 3,5,8
fn(3); // 3,undefined,NaN
fn(3,5,10) // 3,5,8
可以看看上面的例子。定義函數(shù)的時候有兩個形參渣淳。調(diào)用的時候分為了三種情況。
第一種伴箩,傳遞兩個參數(shù)入愧,在console時候a=3,b=5,a+b=8
。老鐵嗤谚,沒問題棺蛛。
第二種,傳遞一個參數(shù)巩步,在console的時候a=3,b=undefined,a+b=NaN
旁赊。哈哈,你不行椅野。
第三種终畅,傳遞3個。在console的時候a=3,b=5,a+b=8
竟闪。握草离福,你牛逼。對第三個參數(shù)視而不見了炼蛤。
以上就是三種情況妖爷。一句話:參數(shù)一一對應(yīng),實參少了理朋,那么沒有對應(yīng)的就是undefined絮识,實參多了绿聘,多出來的就是沒有用的
arguments
在不確定參數(shù)(或者定義函數(shù)的時候沒有形參)的時候,調(diào)用函數(shù)你傳遞參數(shù)了次舌,但是你沒有使用新參去接收熄攘,就無法使用。把此時就有一個arguments
對象可以獲取到實參的個數(shù)以及具體的值垃它。
function fn(){
console.log(arguments)
}
fn(1,2,3,4,5,6,7) // Arguments(7) [1, 2, 3, 4, 5, 6, 7, callee: ?, Symbol(Symbol.iterator): ?]
arguments
在嚴格模式下無法使用鲜屏。
函數(shù)遞歸
遞歸:就是函數(shù)自己調(diào)用自己。比如下面經(jīng)典的階層遞歸函數(shù)
function stratum(n){
if (n <= 1){
return 1;
} else {
return n * stratum(n - 1);
}
}
stratum(5) // 120 = 5 * (4 * (3 * (2 * 1) ) )
可以看出實現(xiàn)的階層的功能国拇。
不過需要注意一下每一個的執(zhí)行順序洛史。不是5 * 4 * 3 * 2 * 1
。而是5 * (4 * (3 * (2 * 1) ) )
的順序酱吝。為了證明這一點也殖。可以將*
換為-
function fn(n){
if (n <= 1){
return 1;
} else {
return n - fn(n - 1);
}
}
fn(5) // 3
如果是按照不帶括號的5-4-3-2-1 = -5
务热。但是結(jié)果卻是3
忆嗜。那3
是怎么來的呢?5 - (4 - (3 - (2 - 1) ) ) = 5 - (4 - (3 - 1)) = 5 - (4 - 2) = 5 - 2 = 3
還可以使用arguments.callee
方法調(diào)用自身崎岂。這個方法就指向當前運行的函數(shù)
function stratum(n){
if (n <= 1){
return 1;
} else {
return n * arguments.callee(n - 1);
}
}
stratum(5) // 120
遞歸雖然可以讓代碼更簡潔捆毫,但是能不使用遞歸的時候就不要使用,遞歸會影響性能(因為過多的調(diào)用自己會一直保存每一次的返回值與變量冲甘,導(dǎo)致內(nèi)存占用過多甚至內(nèi)存泄露)绩卤。
console.time(1);
function stratum(n){
if (n <= 1){
return 1;
} else {
return n * arguments.callee(n - 1);
}
}
console.log(stratum(5))
console.timeEnd(1) // 1: 4.470947265625ms
console.time(2)
var a = 1;
for (var i = 1; i <= 5; i++) {
a *= i;
}
console.log(a);
console.timeEnd(2) // 2: 0.2373046875ms
兩個階層,一看江醇。for循環(huán)快太多了濒憋。具體的性能問題可以看看<a href="http://www.reibang.com/p/6bdc8e3637f2" target=“_blank”>愛情小傻蛋</a>關(guān)于遞歸的算法改進。
函數(shù)閉包
閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)陶夜。
兩個條件:
- 函數(shù)嵌套函數(shù)
- 內(nèi)部函數(shù)使用包含函數(shù)的變量或者是參數(shù)
function fn(){
var a = 1;
return function(){
console.log(a);
a++;
}
}
fn()(); // 1
fn()(); // 1
var a = fn();
a(); // 1
a(); // 2
上面的例子中的函數(shù)就是一個閉包凛驮。注意上面的直接調(diào)用返回值與先保存返回值在調(diào)用的區(qū)別。
閉包只能取得包含函數(shù)中任何變量的最后一個值条辟。this
是無法在閉包函數(shù)中調(diào)用的黔夭。因為每一個函數(shù)都有一個this
。
閉包函數(shù)中使用的變量是不會進行銷毀的羽嫡,像上面的var a = fn()
纠修,這個函數(shù)a中使用了函數(shù)fn中的變量,且a
是一直存在的厂僧,所以函數(shù)fn
里面的變量a
是不會銷毀的扣草。如果是直接調(diào)用函數(shù)fn()()
只是相當于調(diào)用一次fn
函數(shù)的返回值。調(diào)用完函數(shù)返回值就銷毀了。所以變量a
不會一直保存辰妙。
因為閉包函數(shù)的變量會一直保存不會
call鹰祸,apply與bind
三個方法都是改變this指向
call apply
function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}
var name = "嘻嘻"
var obj = {
"name": "哈哈"
}
// 執(zhí)行函數(shù)fn
fn(1,2) // 1,2,嘻嘻
直接調(diào)用函數(shù)fn(1,2)
,this.name
的值是嘻嘻
如果使用call:
fn.call(obj,1,2) // 1,2,哈哈
call
方法的第一個參數(shù)是改變this
指向的東西密浑,可以是任何的數(shù)據(jù)類型蛙婴。只不過如果是null
或者是undefined
就會指向window
。<span style="color: red;font-weight: 900;">其他的參數(shù)</span>依次對應(yīng)函數(shù)的每一個形參尔破。
如果使用apply
fn.apply(obj,[1,2])
apply
的使用與call的使用的唯一的區(qū)別就是它對應(yīng)函數(shù)每一項形參<span style="color: red;font-weight: 900;">是一個數(shù)組</span>而不是單獨的每一個街图。
call與applu都是在函數(shù)調(diào)用的時候去使用
bind
則是在函數(shù)定義的時候使用
function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}
var name = "嘻嘻"
var obj = {
"name": "哈哈"
}
// 執(zhí)行函數(shù)fn
fn(1,2) // 1,2,嘻嘻
如果使用bind
可以是一下幾種方式
// 使用函數(shù)表達式 + 匿名函數(shù)
var fn = function(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}.bind(obj)
fn(1,2)
// 使用有名函數(shù)
function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}
fn.bind(obj)(1,2)
// 函數(shù)在自執(zhí)行
(function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}.bind(obj)(1,2))
(function fn(){
console.log(a)
console.log(b)
console.log(this.name)
}.bind(obj))(1,2);
(function fn(){
console.log(a)
console.log(b)
console.log(this.name)
}).bind(obj)(1,2);
使用bind
的時候也是可以傳遞參數(shù)的,但是不要這樣使用懒构,因為使用bind
后你不調(diào)用函數(shù)那么參數(shù)還是沒有作用餐济。既然還是要調(diào)用函數(shù),我們一般就把函數(shù)的實參傳遞到調(diào)用的括號里面胆剧。