首頁 web前端 js教程 詳解Javascript函數聲明與遞歸調用

詳解Javascript函數聲明與遞歸調用

Dec 09, 2016 pm 01:21 PM
javascript

Javascript的函數的聲明方式和調用方式已經是令人厭倦的老生常談了,但有些東西就是這樣的,你來說一遍然後我再說一遍。每次看到書上或部落格裡寫的Javascript函數有四種呼叫方式,我會想起孔乙己:麋字有四種寫法,你造嗎?

儘管缺陷有一堆,但Javascript還是令人著迷的。 Javascript眾多優美的特性的核心,是作為頂級物件(first-class objects)的函數。函數就像其他普通物件一樣被建立、被指派給變數、被傳遞為作為傳回值、持有屬性和方法。函數作為頂級對象,賦予了Javascript強大的函數式程式設計能力,也帶來了較不容易控制的靈活性。

1、函數宣告

變數式宣告先建立一個匿名函數,然後把它賦值給一個指定的變數:

var f = function () { // function body };
登入後複製

   

通常我們不必關心等號右邊表達式的作用域是全域還是某一類型個閉包內,因為它只能透過等號左邊的變數f來引用,應該關注的是變數f的作用域。如果f指向函數的引用被破壞(f = null),且函數沒有被賦值給任何其它變數或物件屬性,匿名函數會因為失去所有引用而被垃圾回收機制銷毀。

也可以使用函數表達式建立函數:

function f() { // function body }
登入後複製

   

與變數式不同的是,這種宣告方式會為函數的一個內建屬性name賦值。同時把函數賦值給目前作用域的一個同名變數。 (函數的name屬性,configurable、enumerable和writable都是false)

function f() { // function body }
console.log(f.name); // "f"
console.log(f); // f()
登入後複製

   


Javascript變數有一個的特別之處,就是會把變數的宣告提前,表達式

Javascript變數有一個的特別之處,就是會把變數的宣告提前,表達式的函數宣告,也會把整個函數的定義前置,因此你可以在函數定義之前使用它:

console.log(f.name); // "f"
console.log(f); // f()
function f() { // function body }
登入後複製

   

函數表達式的聲明會被提升到作用域頂層,試試下面的程式碼,它們不是本文的重點:

var a = 0;
console.log(a); // 0 or a()?
function a () {}
登入後複製


Crockford建議永遠使用第一種方式聲明函數,他認為第二種方式放寬了函數必須先聲明後使用的要求從而會導致混亂。 (Crockford是一個類似於羅素口中用來比喻維特根斯坦的"有良心的藝術家"那樣的"有良心的程序員",這句話很拗口吧)

函數式聲明

function f() {}
登入後複製

   

看起來是

var f = function f(){};
登入後複製

   

的簡寫。而

var a = function b(){};
登入後複製
登入後複製


的表達式,建立一個函數並把內建的name屬性賦值為"b",然後把這個函數賦值為變數a,你可以在外部使用a()來呼叫它,但卻不能使用b(),因為函數已被賦值給a,所以不會再自動建立一個變數b,除非你使用var b = a宣告一個變數b。當然這個函數的name是"b"而不是"a"。

使用Function建構函數也可用來建立函數:

var f = new Function("a,b,c","return a+b+c;");
登入後複製

   


這種方式其實是在全域作用域內產生匿名函數,並且將它賦值給變數

這種方式其實是在全域作用域內產生一個匿名函數,並且將它賦值給變數
這個變數。

2、遞歸調用

遞歸被用來簡化許多問題,這需要在一個函數體中調用它自己:

// 一个简单的阶乘函数
var f = function (x) {
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
};
登入後複製

   

函數名稱遇到困難,對於上面的變量式聲明,f是一個變量,所以它的值很容易被替換:

var fn = f;
f = function () {};
登入後複製

   


函數是個值,它被賦給fn,我們期待使用fn(

函數是個值,它被賦予fn,我們期待使用fn( 5)可以計算出一個數值,但是由於函數內部依然引用的是變數f,所以它不能正常運作了。


函數式的聲明看起來好些,但很可惜:

function f(x) {
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
}
var fn = f;
f = function () {}; // may been warning by browser
fn(5); // NaN
登入後複製

   


看起來,一旦我們定義了一個遞歸函數,便須注意不要輕易改變變數的名字。

上面談論的都是函數式調用,函數還有其它調用方式,例如當作物件方法調用。


我們常常這樣聲明物件:

var obj1 = {
  num : 5,
  fac : function (x) {
    // function body
  }
};
登入後複製

   

宣告一個匿名函數並將它賦值給物件的屬性(fac)。


如果我們要在這裡寫一個遞歸,就要引用屬性本身:

var obj1 = {
  num : 5,
  fac : function (x) {
    if (x === 1) {
      return 1;
    } else {
      return x * obj1.fac(x - 1);
    }
  }
};
登入後複製

   


當然,它也會遭遇和功能被賦值給obj2的fac屬性後,內部仍要引用obj1.fac,於是…失敗了。


換一種方式會有所改進:

var obj2 = {fac: obj1.fac};
obj1 = {};
obj2.fac(5); // Sadness
登入後複製

   


透過this關鍵字取得函數執行時的context中的屬性,這樣執行obj2.fac時,函數內部透過this關鍵字取得函數執行時的context中的屬性,這樣執行obj2.fac時,函數內部就會引用obj2的context 。


可是函數還可以被任意修改context來調用,那就是萬能的call和apply:

var obj1 = {
   num : 5,
   fac : function (x) {
    if (x === 1) {
      return 1;
    } else {
      return x * this.fac(x - 1);
    }
  }
};
var obj2 = {fac: obj1.fac};
obj1 = {};
obj2.fac(5); // ok
登入後複製

   


於是遞歸函數又不能正常工作了。


我們應該試著解決這個問題,還記得前面提到的一種函數宣告的方式嗎?

var a = function b(){};
登入後複製
登入後複製


这种声明方式叫做内联函数(inline function),虽然在函数外没有声明变量b,但是在函数内部,是可以使用b()来调用自己的,于是

var fn = function f(x) {
  // try if you write "var f = 0;" here
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
};
var fn2 = fn;
fn = null;
fn2(5); // OK
登入後複製

// here show the difference between "var f = function f() {}" and "function f() {}"
var f = function f(x) {
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
};
var fn2 = f;
f = null;
fn2(5); // OK
登入後複製

var obj1 = {
  num : 5,
  fac : function f(x) {
    if (x === 1) {
      return 1;
    } else {
      return x * f(x - 1);
    }
  }
};
var obj2 = {fac: obj1.fac};
obj1 = {};
obj2.fac(5); // ok
  
var obj3 = {};
obj1.fac.call(obj3, 5); // ok
登入後複製


就这样,我们有了一个可以在内部使用的名字,而不用担心递归函数被赋值给谁以及以何种方式被调用。

Javascript函数内部的arguments对象,有一个callee属性,指向的是函数本身。因此也可以使用arguments.callee在内部调用函数:

function f(x) {
  if (x === 1) {
    return 1;
  } else {
    return x * arguments.callee(x - 1);
  }
}
登入後複製

   


但arguments.callee是一个已经准备被弃用的属性,很可能会在未来的ECMAscript版本中消失,在ECMAscript 5中"use strict"时,不能使用arguments.callee。

最后一个建议是:如果要声明一个递归函数,请慎用new Function这种方式,Function构造函数创建的函数在每次被调用时,都会重新编译出一个函数,递归调用会引发性能问题——你会发现你的内存很快就被耗光了。


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何使用WebSocket和JavaScript實現線上語音辨識系統 如何使用WebSocket和JavaScript實現線上語音辨識系統 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統 如何利用JavaScript和WebSocket實現即時線上點餐系統 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統 如何使用WebSocket和JavaScript實現線上預約系統 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript與WebSocket:打造高效率的即時天氣預報系統 JavaScript與WebSocket:打造高效率的即時天氣預報系統 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

javascript如何使用insertBefore javascript如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用於在DOM樹中插入一個新的節點。這個方法需要兩個參數:要插入的新節點和參考節點(即新節點將要插入的位置的節點)。

JavaScript與WebSocket:打造高效率的即時影像處理系統 JavaScript與WebSocket:打造高效率的即時影像處理系統 Dec 17, 2023 am 08:41 AM

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數

See all articles