@(javascript)[js函數]javascript
[toc]java
JavaScript中的函數能夠分爲兩類:有名函數
與匿名函數
。而定義函數的方式有兩種:函數聲明
與函數表達式
。算法
目標:定義一個函數 fn ==> 有名函數數組
// 使用函數聲明
function fn(){
// 函數執行體
}
// 使用函數表達式
var fn = function(){
// 函數執行體
}
複製代碼
使用函數聲明的重要特徵就是函數聲明提高
,即在讀取代碼前會先讀取函數聲明。函數名()
表示執行函數 看看下面的代碼沒有任何問題。閉包
// 定義一個有名函數 fn1 使用函數聲明
function fn(){
console.log("fn1")
}
// 調用函數 fn1
fn1(); // fn1
// 定義一個有名函數 fn2 使用函數表達式
var fn2 = function(){
console.log("fn2")
}
// 調用函數 fn2
fn2(); // fn2
複製代碼
可是若是是把調用放在定義函數前面,使用函數表達式
的就會報錯(Uncaught ReferenceError: fn1 is not defined)app
// 調用函數 fn1
fn1(); // fn1
// 定義一個有名函數 fn1 使用函數聲明
function fn(){
console.log("fn1")
}
// 調用函數 fn2
fn2(); // Uncaught ReferenceError: fn1 is not defined
// 定義一個有名函數 fn2 使用函數表達式
var fn2 = function(){
console.log("fn2")
}
複製代碼
這就是使用兩種的區別。函數
每個函數在調用的時候都會默認返回一個undefined
。性能
function fn(){
console.log(1)
}
fn(); // 1
console.log(fn); // console出一個函數 即 fn
console.log(fn()); // undefined
複製代碼
這裏須要注意的地方就是關於函數執行過程
與函數執行結果
。ui
fn()
表示調用函數。那就會執行函數體。並默認返回一個undefined
。只不過這個值undefined
沒有變量接收或者說是咱們沒有用這個值。this
console.log(fn)
就只是console出一個變量fn
的值。只不過這個值是一個函數。
console.log(fn())
與第一個的區別就是函數執行了並返回了一個結果。這個結果呢與上面不一樣的就是如今這個結果咱們用上了(放在了console)裏面。再因爲值是undefined
,因此console了一個undefined
。
既然函數是能夠有返回值的,而且這個值默認是一個undefined
。那咱們能夠能夠修改呢?答案是能夠的。 咱們使用return
語句可讓函數返回一個值
function fn(){
console.log(1)
return "哈哈"
}
fn(); // 1
console.log(fn); // 函數 fn
console.log(fn()); // 哈哈
複製代碼
能夠看一下第一個與第二個的結果與以前的是相同的。可是第三個的結果就不一樣了,他是字符串哈哈
。由於咱們修改了函數的默認返回值。
因此,能夠把默認函數理解成這樣的
function fn(){
return undefined;
}
複製代碼
而咱們修改返回值就是吧這個undefined給變成其餘的值了。 注意:
函數的返回值能夠是任意的數據類型。
函數是能夠接收參數的,在定義函數的時候放的參數叫形式參數
,簡稱形參
。在調用函數的時候傳遞的參數叫實際參數
,簡稱實參
。一個函數能夠擁有任意個參數
function fn(a,b){
console.log(a)
console.log(b)
console.log(a+b)
}
// 調用函數並傳遞參數
fn(3,5); // 3,5,8
fn(3); // 3,undefined,NaN
fn(3,5,10) // 3,5,8
複製代碼
能夠看看上面的例子。定義函數的時候有兩個形參。調用的時候分爲了三種狀況。
第一種,傳遞兩個參數,在console時候a=3,b=5,a+b=8
。老鐵,沒問題。
第二種,傳遞一個參數,在console的時候a=3,b=undefined,a+b=NaN
。哈哈,你不行。
第三種,傳遞3個。在console的時候a=3,b=5,a+b=8
。握草,你牛逼。對第三個參數視而不見了。
以上就是三種狀況。一句話:參數一一對應,實參少了,那麼沒有對應的就是undefined,實參多了,多出來的就是沒有用的
在不肯定參數(或者定義函數的時候沒有形參)的時候,調用函數你傳遞參數了,可是你沒有使用新參去接收,就沒法使用。把此時就有一個arguments
對象能夠獲取到實參的個數以及具體的值。
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
在嚴格模式下沒法使用。
遞歸:就是函數本身調用本身。好比下面經典的階層遞歸函數
function stratum(n){
if (n <= 1){
return 1;
} else {
return n * stratum(n - 1);
}
}
stratum(5) // 120 = 5 * (4 * (3 * (2 * 1) ) )
複製代碼
能夠看出實現的階層的功能。 不過須要注意一下每個的執行順序。不是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
。可是結果倒是3
。那3
是怎麼來的呢?5 - (4 - (3 - (2 - 1) ) ) = 5 - (4 - (3 - 1)) = 5 - (4 - 2) = 5 - 2 = 3
還能夠使用arguments.callee
方法調用自身。這個方法就指向當前運行的函數
function stratum(n){
if (n <= 1){
return 1;
} else {
return n * arguments.callee(n - 1);
}
}
stratum(5) // 120
複製代碼
遞歸雖然可讓代碼更簡潔,可是能不使用遞歸的時候就不要使用,遞歸會影響性能(由於過多的調用本身會一直保存每一次的返回值與變量,致使內存佔用過多甚至內存泄露)。
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循環快太多了。具體的性能問題能夠看看愛情小傻蛋關於遞歸的算法改進。
閉包是指有權訪問另外一個函數做用域中的變量的函數。 兩個條件:
function fn(){
var a = 1;
return function(){
console.log(a);
a++;
}
}
fn()(); // 1
fn()(); // 1
var a = fn();
a(); // 1
a(); // 2
複製代碼
上面的例子中的函數就是一個閉包。注意上面的直接調用返回值與先保存返回值在調用的區別。
閉包只能取得包含函數中任何變量的最後一個值。this
是沒法在閉包函數中調用的。由於每個函數都有一個this
。
閉包函數中使用的變量是不會進行銷燬的,像上面的var a = fn()
,這個函數a中使用了函數fn中的變量,且a
是一直存在的,因此函數fn
裏面的變量a
是不會銷燬的。若是是直接調用函數fn()()
只是至關於調用一次fn
函數的返回值。調用完函數返回值就銷燬了。因此變量a
不會一直保存。
由於閉包函數的變量會一直保存不會
三個方法都是改變this指向
function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}
var name = "嘻嘻"
var obj = {
"name": "哈哈"
}
// 執行函數fn
fn(1,2) // 1,2,嘻嘻
複製代碼
直接調用函數fn(1,2)
,this.name
的值是嘻嘻
若是使用call:
fn.call(obj,1,2) // 1,2,哈哈
複製代碼
call
方法的第一個參數是改變this
指向的東西,能夠是任何的數據類型。只不過若是是null
或者是undefined
就會指向window
。其餘的參數依次對應函數的每個形參。
若是使用apply
fn.apply(obj,[1,2])
複製代碼
apply
的使用與call的使用的惟一的區別就是它對應函數每一項形參是一個數組而不是單獨的每個。
call與applu都是在函數調用的時候去使用
bind
則是在函數定義的時候使用
function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}
var name = "嘻嘻"
var obj = {
"name": "哈哈"
}
// 執行函數fn
fn(1,2) // 1,2,嘻嘻
複製代碼
若是使用bind
能夠是一下幾種方式
// 使用函數表達式 + 匿名函數
var fn = function(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}.bind(obj)
fn(1,2)
// 使用有名函數
function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}
fn.bind(obj)(1,2)
// 函數在自執行
(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
的時候也是能夠傳遞參數的,可是不要這樣使用,由於使用bind
後你不調用函數那麼參數仍是沒有做用。既然仍是要調用函數,咱們通常就把函數的實參傳遞到調用的括號裏面。