@(javascript)[js函數(shù)]
[toc]
JavaScript中的函數(shù)
函數(shù)的分類與定義函數(shù)的方式
JavaScript中的函數(shù)可以分為兩類:有名函數(shù)與匿名函數(shù)。而定義函數(shù)的方式有兩種:函數(shù)聲明與函數(shù)表達(dá)式。
目標(biāo):定義一個(gè)函數(shù) fn ==> 有名函數(shù)
// 使用函數(shù)聲明
function fn(){
// 函數(shù)執(zhí)行體
}
// 使用函數(shù)表達(dá)式
var fn = function(){
// 函數(shù)執(zhí)行體
}
使用函數(shù)聲明的重要特征就是函數(shù)聲明提升,即在讀取代碼前會(huì)先讀取函數(shù)聲明。函數(shù)名()表示執(zhí)行函數(shù)
看看下面的代碼沒有任何問題。
// 定義一個(gè)有名函數(shù) fn1 使用函數(shù)聲明
function fn(){
console.log("fn1")
}
// 調(diào)用函數(shù) fn1
fn1(); // fn1
// 定義一個(gè)有名函數(shù) fn2 使用函數(shù)表達(dá)式
var fn2 = function(){
console.log("fn2")
}
// 調(diào)用函數(shù) fn2
fn2(); // fn2
但是如果是把調(diào)用放在定義函數(shù)前面,使用函數(shù)表達(dá)式的就會(huì)報(bào)錯(cuò)(Uncaught ReferenceError: fn1 is not defined)
// 調(diào)用函數(shù) fn1
fn1(); // fn1
// 定義一個(gè)有名函數(shù) fn1 使用函數(shù)聲明
function fn(){
console.log("fn1")
}
// 調(diào)用函數(shù) fn2
fn2(); // Uncaught ReferenceError: fn1 is not defined
// 定義一個(gè)有名函數(shù) fn2 使用函數(shù)表達(dá)式
var fn2 = function(){
console.log("fn2")
}
這就是使用兩種的區(qū)別。
函數(shù)的返回值
每一個(gè)函數(shù)在調(diào)用的時(shí)候都會(huì)默認(rèn)返回一個(gè)undefined。
function fn(){
console.log(1)
}
fn(); // 1
console.log(fn); // console出一個(gè)函數(shù) 即 fn
console.log(fn()); // undefined
這里需要注意的地方就是關(guān)于函數(shù)執(zhí)行過程與函數(shù)執(zhí)行結(jié)果。
fn()表示調(diào)用函數(shù)。那就會(huì)執(zhí)行函數(shù)體。并默認(rèn)返回一個(gè)undefined。只不過這個(gè)值undefined沒有變量接收或者說是我們沒有用這個(gè)值。
console.log(fn)就只是console出一個(gè)變量fn的值。只不過這個(gè)值是一個(gè)函數(shù)。
console.log(fn())與第一個(gè)的區(qū)別就是函數(shù)執(zhí)行了并返回了一個(gè)結(jié)果。這個(gè)結(jié)果呢與上面不同的就是現(xiàn)在這個(gè)結(jié)果我們用上了(放在了console)里面。再由于值是undefined,所以console了一個(gè)undefined。
既然函數(shù)是可以有返回值的,并且這個(gè)值默認(rèn)是一個(gè)undefined。那我們可以可以修改呢?答案是可以的。
我們使用return語句可以讓函數(shù)返回一個(gè)值
function fn(){
console.log(1)
return "哈哈"
}
fn(); // 1
console.log(fn); // 函數(shù) fn
console.log(fn()); // 哈哈
可以看一下第一個(gè)與第二個(gè)的結(jié)果與之前的是相同的。但是第三個(gè)的結(jié)果就不同了,他是字符串哈哈。因?yàn)槲覀冃薷牧撕瘮?shù)的默認(rèn)返回值。
所以,可以把默認(rèn)函數(shù)理解成這樣的
function fn(){
return undefined;
}
而我們修改返回值就是吧這個(gè)undefined給變成其他的值了。
注意:函數(shù)的返回值可以是任意的數(shù)據(jù)類型。
函數(shù)參數(shù)
函數(shù)是可以接收參數(shù)的,在定義函數(shù)的時(shí)候放的參數(shù)叫形式參數(shù),簡(jiǎn)稱形參。在調(diào)用函數(shù)的時(shí)候傳遞的參數(shù)叫實(shí)際參數(shù),簡(jiǎn)稱實(shí)參。一個(gè)函數(shù)可以擁有任意個(gè)參數(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ù)的時(shí)候有兩個(gè)形參。調(diào)用的時(shí)候分為了三種情況。
第一種,傳遞兩個(gè)參數(shù),在console時(shí)候a=3,b=5,a+b=8。老鐵,沒問題。
第二種,傳遞一個(gè)參數(shù),在console的時(shí)候a=3,b=undefined,a+b=NaN。哈哈,你不行。
第三種,傳遞3個(gè)。在console的時(shí)候a=3,b=5,a+b=8。握草,你牛逼。對(duì)第三個(gè)參數(shù)視而不見了。
以上就是三種情況。一句話:參數(shù)一一對(duì)應(yīng),實(shí)參少了,那么沒有對(duì)應(yīng)的就是undefined,實(shí)參多了,多出來的就是沒有用的
arguments
在不確定參數(shù)(或者定義函數(shù)的時(shí)候沒有形參)的時(shí)候,調(diào)用函數(shù)你傳遞參數(shù)了,但是你沒有使用新參去接收,就無法使用。把此時(shí)就有一個(gè)arguments對(duì)象可以獲取到實(shí)參的個(gè)數(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在嚴(yán)格模式下無法使用。
函數(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) ) )
可以看出實(shí)現(xiàn)的階層的功能。
不過需要注意一下每一個(gè)的執(zhí)行順序。不是5 * 4 * 3 * 2 * 1。而是5 * (4 * (3 * (2 * 1) ) )的順序。為了證明這一點(diǎn)??梢詫?code>*換為-
function fn(n){
if (n <= 1){
return 1;
} else {
return n - fn(n - 1);
}
}
fn(5) // 3
如果是按照不帶括號(hào)的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)用自身。這個(gè)方法就指向當(dāng)前運(yùn)行的函數(shù)
function stratum(n){
if (n <= 1){
return 1;
} else {
return n * arguments.callee(n - 1);
}
}
stratum(5) // 120
遞歸雖然可以讓代碼更簡(jiǎn)潔,但是能不使用遞歸的時(shí)候就不要使用,遞歸會(huì)影響性能(因?yàn)檫^多的調(diào)用自己會(huì)一直保存每一次的返回值與變量,導(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
兩個(gè)階層,一看。for循環(huán)快太多了。具體的性能問題可以看看<a href="http://www.itdecent.cn/p/6bdc8e3637f2" target=“_blank”>愛情小傻蛋</a>關(guān)于遞歸的算法改進(jìn)。
函數(shù)閉包
閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。
兩個(gè)條件:
- 函數(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ù)就是一個(gè)閉包。注意上面的直接調(diào)用返回值與先保存返回值在調(diào)用的區(qū)別。
閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值。this是無法在閉包函數(shù)中調(diào)用的。因?yàn)槊恳粋€(gè)函數(shù)都有一個(gè)this。
閉包函數(shù)中使用的變量是不會(huì)進(jìn)行銷毀的,像上面的var a = fn(),這個(gè)函數(shù)a中使用了函數(shù)fn中的變量,且a是一直存在的,所以函數(shù)fn里面的變量a是不會(huì)銷毀的。如果是直接調(diào)用函數(shù)fn()()只是相當(dāng)于調(diào)用一次fn函數(shù)的返回值。調(diào)用完函數(shù)返回值就銷毀了。所以變量a不會(huì)一直保存。
因?yàn)殚]包函數(shù)的變量會(huì)一直保存不會(huì)
call,apply與bind
三個(gè)方法都是改變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方法的第一個(gè)參數(shù)是改變this指向的東西,可以是任何的數(shù)據(jù)類型。只不過如果是null或者是undefined就會(huì)指向window。<span style="color: red;font-weight: 900;">其他的參數(shù)</span>依次對(duì)應(yīng)函數(shù)的每一個(gè)形參。
如果使用apply
fn.apply(obj,[1,2])
apply的使用與call的使用的唯一的區(qū)別就是它對(duì)應(yīng)函數(shù)每一項(xiàng)形參<span style="color: red;font-weight: 900;">是一個(gè)數(shù)組</span>而不是單獨(dú)的每一個(gè)。
call與applu都是在函數(shù)調(diào)用的時(shí)候去使用
bind則是在函數(shù)定義的時(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ù)表達(dá)式 + 匿名函數(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í)候也是可以傳遞參數(shù)的,但是不要這樣使用,因?yàn)槭褂?code>bind后你不調(diào)用函數(shù)那么參數(shù)還是沒有作用。既然還是要調(diào)用函數(shù),我們一般就把函數(shù)的實(shí)參傳遞到調(diào)用的括號(hào)里面。