JavaScript中的函數

@(javascript)[js函數]javascript

[toc]java

JavaScript中的函數

函數的分類與定義函數的方式

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

在不肯定參數(或者定義函數的時候沒有形參)的時候,調用函數你傳遞參數了,可是你沒有使用新參去接收,就沒法使用。把此時就有一個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循環快太多了。具體的性能問題能夠看看愛情小傻蛋關於遞歸的算法改進。

函數閉包

閉包是指有權訪問另外一個函數做用域中的變量的函數。 兩個條件:

  1. 函數嵌套函數
  2. 內部函數使用包含函數的變量或者是參數
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不會一直保存。

由於閉包函數的變量會一直保存不會

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": "哈哈"
}
// 執行函數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後你不調用函數那麼參數仍是沒有做用。既然仍是要調用函數,咱們通常就把函數的實參傳遞到調用的括號裏面。

相關文章
相關標籤/搜索