首頁 >web前端 >js教程 >定義和重寫自己的JavaScript函數

定義和重寫自己的JavaScript函數

Christopher Nolan
Christopher Nolan原創
2025-02-16 08:41:09479瀏覽

JavaScript 函數的動態特性:自我定義與重寫

JavaScript Functions That Define and Rewrite Themselves

關鍵要點:

  • JavaScript 的動態特性允許函數自我定義甚至重寫自身。這通過將匿名函數賦值給與函數同名的變量來實現。這個概念被稱為惰性定義模式 (Lazy Definition Pattern),可用於僅在第一次函數調用時需要的初始化代碼。
  • 如果在函數第一次調用和後續重定義之前將其賦值給另一個變量,則新變量將保留原始函數定義,不會被重寫。但是,在函數重定義之前設置的任何屬性都將丟失。
  • 函數重寫技術可以與特性檢測相結合,以創建更高效的函數,這個概念稱為初始化時分支 (init-time branching)。這允許函數針對正在使用的瀏覽器進行優化,僅在第一次函數調用期間檢查特性支持。

JavaScript Functions That Define and Rewrite Themselves

JavaScript 的動態特性意味著函數不僅可以調用自身,還可以定義自身,甚至重寫自身。這是通過將匿名函數賦值給與函數同名的變量來實現的。

考慮以下函數:

<code class="language-javascript">function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}</code>

此函數首先向控制台輸出一條消息,然後重寫自身以向控制台輸出不同的消息。當函數被調用一次後,它就像這樣定義:

<code class="language-javascript">function party() {
  console.log('Been there, got the T-Shirt');
}</code>

第一次調用之後,每次調用該函數都會輸出消息“Been there, got the T-Shirt”:

<code class="language-javascript">party();
party();
party();</code>

如果函數也賦值給另一個變量,則此變量將保留原始函數定義,不會被重寫。這是因為原始函數被賦值給一個變量,然後在函數內部,與函數同名的變量被賦值給另一個函數。如果我們在第一次調用和重定義 之前 創建一個名為 beachParty 的變量並將其賦值給 party() 函數,則可以看到此示例:

<code class="language-javascript">function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}

const beachParty = party; // 注意,party 函数尚未被调用

beachParty(); // party() 函数现在已被重定义,即使它没有被显式调用

party();

beachParty(); // 但此函数尚未被重定义

beachParty(); // 无论调用多少次,它都将保持不变</code>

屬性丟失

小心:如果之前在函數上設置了任何屬性,則當函數重寫自身時,這些屬性將丟失。在前面的示例中,我們可以設置一個 music 屬性,並看到在函數被調用和重定義後它不再存在:

<code class="language-javascript">function party() {
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}

party.music = 'Classical Jazz'; // 设置函数的属性

party();

party.music; // 函数现在已被重定义,因此属性不存在</code>

這被稱為惰性定義模式 (Lazy Definition Pattern),通常在第一次調用時需要一些初始化代碼時使用。這意味著可以在第一次調用時完成初始化,然後可以將函數重定義為您希望在每次後續調用時使用的函數。

初始化時分支 (Init-Time Branching)

此技術可以與我們在上一章中討論的特性檢測一起使用,以創建重寫自身的函數,稱為初始化時分支 (init-time branching)。這使函數能夠在瀏覽器中更有效地工作,並避免每次調用時都檢查特性。

讓我們以我們虛構的獨角獸對象為例,該對象尚未在所有瀏覽器中獲得完全支持。在上一章中,我們研究瞭如何使用特性檢測來檢查是否支持此功能。現在我們可以更進一步:我們可以根據是否支持某些方法來定義函數。這意味著我們只需要在第一次調用函數時檢查支持情況:

<code class="language-javascript">function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}</code>

在檢查了 window.unicorn 對像是否存在(通過檢查它是否為真值)之後,我們根據結果重寫了 ride() 函數。在函數的最後,我們再次調用它,以便現在調用重寫的函數,並返回相關值。需要注意的是,函數第一次調用時會調用兩次,儘管它在每次後續調用時都會變得更高效。讓我們看看它是如何工作的:

<code class="language-javascript">function party() {
  console.log('Been there, got the T-Shirt');
}</code>

一旦函數被調用,它就會根據瀏覽器的功能進行重寫。我們可以通過檢查函數而不調用它來檢查這一點:

這可以是一個有用的模式,用於在第一次調用函數時初始化它們,並針對正在使用的瀏覽器對其進行優化。

遞歸函數

遞歸函數是指調用自身直到滿足某個條件的函數。當涉及迭代過程時,它是一個有用的工具。一個常見的例子是計算數字階乘的函數:

<code class="language-javascript">party();
party();
party();</code>

如果提供 0 作為參數(0 的階乘為 1),則此函數將返回 1,否則它將把參數乘以使用比它少一個的參數調用自身的結果。該函數將繼續調用自身,直到最終參數為 0 並返回 1。這將導致 1、2、3 和所有直到原始參數的所有數字相乘。

來自數學領域的另一個例子是 Collatz 猜想。這是一個陳述起來很簡單的問題,但到目前為止尚未解決。它涉及取任何正整數並遵循以下規則:

  • 如果數字是偶數,則將其除以二
  • 如果數字是奇數,則將其乘以三再加一

例如,如果我們從數字 18 開始,我們將得到以下序列:

18, 9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, 4, 2, 1 , …

如您所見,序列最終陷入循環,循環遍歷“4,2,1”。 Collatz 猜想指出,每個正整數都會創建一個最終以該循環結束的序列。這已針對高達 5 × 2⁶⁰ 的所有數字進行了驗證,但沒有證據表明它將繼續對高於此的所有整數都成立。為了測試該猜想,我們可以編寫一個使用遞歸來不斷調用函數直到達到值 1 的函數(因為我們希望我們的函數避免最終陷入遞歸循環!):

<code class="language-javascript">function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}</code>

此函數將一個數字作為參數,以及另一個名為 sequence 的參數,其默認值為包含第一個參數的數組。第二個參數僅在函數遞歸調用自身時使用。

函數首先做的是測試 n 的值是否為 1。如果是,則函數返回一條消息來說明它花了多少步。如果它沒有達到 1,它會檢查 n 的值是偶數(在這種情況下,它將其除以 2),還是奇數,在這種情況下,它乘以 3 再加 1。然後函數調用自身,提供新的 n 值和新的序列作為參數。新序列是通過將舊序列和 n 的值放在一個新數組中並將展開運算符應用於舊序列來構建的。

讓我們看看數字 18 會發生什麼:

<code class="language-javascript">function party() {
  console.log('Been there, got the T-Shirt');
}</code>

如您所見,它需要 21 步,但最終它會結束於 1。

嘗試使用該函數,看看您能否找到一個大於 5 × 2⁶⁰ 的值不會結束於 1——如果您這樣做,您將成名!

關於自我定義和重寫 JavaScript 函數的常見問題解答 (FAQ)

如何在 JavaScript 中動態創建函數?

在 JavaScript 中,您可以使用 Function 構造函數動態創建函數。此構造函數採用兩個參數:一個包含逗號分隔的參數名稱列表的字符串,以及一個包含函數體的字符串。例如,您可以創建一個添加兩個數字的函數,如下所示:

<code class="language-javascript">party();
party();
party();</code>

此方法允許您動態定義函數,但通常不推薦這樣做,因為它不如正常聲明函數那樣高效且不易出錯。

JavaScript 函數自我定義是什麼意思?

在 JavaScript 中,函數可以自我定義,這意味著它可以在運行時修改自己的代碼。這是可能的,因為 JavaScript 中的函數是一等對象,這意味著它們可以傳遞、從其他函數返回,甚至可以修改。這是一個函數重寫自身的示例:

<code class="language-javascript">function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}

const beachParty = party; // 注意,party 函数尚未被调用

beachParty(); // party() 函数现在已被重定义,即使它没有被显式调用

party();

beachParty(); // 但此函数尚未被重定义

beachParty(); // 无论调用多少次,它都将保持不变</code>

第一次調用 foo() 時,它會重寫自身。下次調用 foo() 時,它將執行新代碼。

如何重寫 JavaScript 函數?

在 JavaScript 中,您可以通過簡單地將新函數賦值給同一個變量來重寫函數。這是一個例子:

<code class="language-javascript">function party() {
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}

party.music = 'Classical Jazz'; // 设置函数的属性

party();

party.music; // 函数现在已被重定义,因此属性不存在</code>

當您調用 foo() 時,它將執行新函數,而不是原始函數。這是因為變量 foo 現在指向新函數。

動態定義 JavaScript 函數的優缺點是什麼?

動態定義 JavaScript 函數可以提供靈活性,因為您可以根據程序的需求動態創建和修改函數。但是,它也有一些缺點。它不如正常聲明函數那樣高效,因為 JavaScript 引擎無法提前優化函數。它也更容易出錯,因為函數體字符串中的任何錯誤都不會在函數執行之前被捕獲。

我可以使用箭頭函數來定義和重寫 JavaScript 函數嗎?

是的,您可以使用箭頭函數來定義和重寫 JavaScript 函數。箭頭函數提供了更簡潔的語法,並且在處理 this 和其他特殊關鍵字方面有一些區別。這是一個定義和重寫箭頭函數的示例:

<code class="language-javascript">function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}</code>

當您調用 foo() 時,它將執行新函數,而不是原始函數。

以上是定義和重寫自己的JavaScript函數的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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