<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 三、函數 > 原文:[Functions](https://eloquentjavascript.net/03_functions.html) > > 譯者:[飛龍](https://github.com/wizardforcel) > > 協議:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) > > 自豪地采用[谷歌翻譯](https://translate.google.cn/) > > 部分參考了[《JavaScript 編程精解(第 2 版)》](https://book.douban.com/subject/26707144/) > 人們認為計算機科學是天才的藝術,但是實際情況相反,只是許多人在其它人基礎上做一些東西,就像一面由石子壘成的墻。 > > 高德納 ![](https://img.kancloud.cn/b1/84/b1841eb3414836e023a9967de97bec5a_490x310.jpg) 函數是 JavaScript 編程的面包和黃油。 將一段程序包裝成值的概念有很多用途。 它為我們提供了方法,用于構建更大程序,減少重復,將名稱和子程序關聯,以及將這些子程序相互隔離。 函數最明顯的應用是定義新詞匯。 用散文創造新詞匯通常是不好的風格。 但在編程中,它是不可或缺的。 以英語為母語的典型成年人,大約有 2 萬字的詞匯量。 很少有編程語言內置了 2 萬個命令。而且,可用的詞匯的定義往往比人類語言更精確,因此靈活性更低。 因此,我們通常會引入新的概念,來避免過多重復。 ## 定義函數 函數定義是一個常規綁定,其中綁定的值是一個函數。 例如,這段代碼定義了`square`,來引用一個函數,它產生給定數字的平方: ```js const square = function(x) { return x * x; }; console.log(square(12)); // → 144 ``` 函數使用以關鍵字`function`起始的表達式創建。 函數有一組參數(在本例中只有`x`)和一個主體,它包含調用該函數時要執行的語句。 以這種方式創建的函數的函數體,必須始終包在花括號中,即使它僅包含一個語句。 一個函數可以包含多個參數,也可以不含參數。在下面的例子中,`makeNoise`函數中沒有包含任何參數,而`power`則使用了兩個參數: ```js var makeNoise = function() { console.log("Pling!"); }; makeNoise(); // → Pling! const power = function(base, exponent) { let result = 1; for (let count = 0; count < exponent; count++) { result *= base; } return result; }; console.log(power(2, 10)); // → 1024 ``` 有些函數會產生一個值,比如`power`和`square`,有些函數不會,比如`makeNoise`,它的唯一結果是副作用。 `return`語句決定函數返回的值。 當控制流遇到這樣的語句時,它立即跳出當前函數并將返回的值賦給調用該函數的代碼。 不帶表達式的`return`關鍵字,會導致函數返回`undefined`。 沒有`return`語句的函數,比如`makeNoise`,同樣返回`undefined`。 函數的參數行為與常規綁定相似,但它們的初始值由函數的調用者提供,而不是函數本身的代碼。 ## 綁定和作用域 每個綁定都有一個作用域,它是程序的一部分,其中綁定是可見的。 對于在任何函數或塊之外定義的綁定,作用域是整個程序 - 您可以在任何地方引用這種綁定。它們被稱為全局的。 但是為函數參數創建的,或在函數內部聲明的綁定,只能在該函數中引用,所以它們被稱為局部綁定。 每次調用該函數時,都會創建這些綁定的新實例。 這提供了函數之間的一些隔離 - 每個函數調用,都在它自己的小世界(它的局部環境)中運行,并且通常可以在不知道全局環境中發生的事情的情況下理解。 用`let`和`const`聲明的綁定,實際上是它們的聲明所在的塊的局部對象,所以如果你在循環中創建了一個,那么循環之前和之后的代碼就不能“看見”它。JavaScript 2015 之前,只有函數創建新的作用域,因此,使用`var`關鍵字創建的舊式綁定,在它們出現的整個函數中內都可見,或者如果它們不在函數中,在全局作用域可見。 ```js let x = 10; if (true) { let y = 20; var z = 30; console.log(x + y + z); // → 60 } // y is not visible here console.log(x + z); // → 40 ``` 每個作用域都可以“向外查看”它周圍的作用域,所以示例中的塊內可以看到`x`。 當多個綁定具有相同名稱時例外 - 在這種情況下,代碼只能看到最內層的那個。 例如,當`halve`函數中的代碼引用`n`時,它看到它自己的`n`,而不是全局的`n`。 ```js const halve = function(n) { return n / 2; } let n = 10; console.log(halve(100)); // → 50 console.log(n); // → 10 ``` ## 嵌套作用域 JavaScript 不僅區分全局和局部綁定。 塊和函數可以在其他塊和函數內部創建,產生多層局部環境。 例如,這個函數(輸出制作一批鷹嘴豆泥所需的配料)的內部有另一個函數: ```js const hummus = function(factor) { const ingredient = function(amount, unit, name) { let ingredientAmount = amount * factor; if (ingredientAmount > 1) { unit += "s"; } console.log(`${ingredientAmount} ${unit} ${name}`); }; ingredient(1, "can", "chickpeas"); ingredient(0.25, "cup", "tahini"); ingredient(0.25, "cup", "lemon juice"); ingredient(1, "clove", "garlic"); ingredient(2, "tablespoon", "olive oil"); ingredient(0.5, "teaspoon", "cumin"); }; ``` `ingredient`函數中的代碼,可以從外部函數中看到`factor`綁定。 但是它的局部綁定,比如`unit`或`ingredientAmount`,在外層函數中是不可見的。 簡而言之,每個局部作用域也可以看到所有包含它的局部作用域。 塊內可見的綁定集,由這個塊在程序文本中的位置決定。 每個局部作用域也可以看到包含它的所有局部作用域,并且所有作用域都可以看到全局作用域。 這種綁定可見性方法稱為詞法作用域。 ## 作為值的函數 函數綁定通常只充當程序特定部分的名稱。 這樣的綁定被定義一次,永遠不會改變。 這使得容易混淆函數和名稱。 ```js let launchMissiles = function(value) { missileSystem.launch("now"); }; if (safeMode) { launchMissiles = function() {/* do nothing */}; } ``` 在第 5 章中,我們將會討論一些高級功能:將函數類型的值傳遞給其他函數。 ## 符號聲明 創建函數綁定的方法稍短。 當在語句開頭使用`function`關鍵字時,它的工作方式不同。 ```js function square(x) { return x * x; } ``` 這是函數聲明。 該語句定義了綁定`square`并將其指向給定的函數。 寫起來稍微容易一些,并且在函數之后不需要分號。 這種形式的函數定義有一個微妙之處。 ```js console.log("The future says:", future()); function future() { return "You'll never have flying cars"; } ``` 前面的代碼可以執行,即使在函數定義在使用它的代碼下面。 函數聲明不是常規的從上到下的控制流的一部分。 在概念上,它們移到了其作用域的頂部,并可被該作用域內的所有代碼使用。 這有時是有用的,因為它以一種看似有意義的方式,提供了對代碼進行排序的自由,而無需擔心在使用之前必須定義所有函數。 ## 箭頭函數 函數的第三個符號與其他函數看起來有很大不同。 它不使用`function`關鍵字,而是使用由等號和大于號組成的箭頭(`=>`)(不要與大于等于運算符混淆,該運算符寫做`>=`)。 ```js const power = (base, exponent) => { let result = 1; for (let count = 0; count < exponent; count++) { result *= base; } return result; }; ``` 箭頭出現在參數列表后面,然后是函數的主體。 它表達了一些東西,類似“這個輸入(參數)產生這個結果(主體)”。 如果只有一個參數名稱,則可以省略參數列表周圍的括號。 如果主體是單個表達式,而不是大括號中的塊,則表達式將從函數返回。 所以這兩個`square`的定義是一樣的: ```js const square1 = (x) => { return x * x; }; const square2 = x => x * x; ``` 當一個箭頭函數沒有參數時,它的參數列表只是一組空括號。 ```js const horn = () => { console.log("Toot"); }; ``` 在語言中沒有很好的理由,同時擁有箭頭函數和函數表達式。 除了我們將在第 6 章中討論的一個小細節外,他們實現相同的東西。 在 2015 年增加了箭頭函數,主要是為了能夠以簡短的方式編寫小函數表達式。 我們將在第 5 章中使用它們。 ## 調用棧 控制流經過函數的方式有點復雜。 讓我們仔細看看它。 這是一個簡單的程序,它執行了一些函數調用: ```js function greet(who) { console.log("Hello " + who); } greet("Harry"); console.log("Bye"); ``` 這個程序的執行大致是這樣的:對`greet`的調用使控制流跳轉到該函數的開始(第 2 行)。 該函數調用控制臺的`console.log`來完成它的工作,然后將控制流返回到第 2 行。 它到達`greet`函數的末尾,所以它返回到調用它的地方,這是第 4 行。 之后的一行再次調用`console.log`。 之后,程序結束。 我們可以使用下圖表示出控制流: ``` not in function in greet in console.log in greet not in function in console.log not in function ``` 由于函數在返回時必須跳回調用它的地方,因此計算機必須記住調用發生處上下文。 在一種情況下,`console.log`完成后必須返回`greet`函數。 在另一種情況下,它返回到程序的結尾。 計算機存儲此上下文的地方是調用棧。 每次調用函數時,當前上下文都存儲在此棧的頂部。 當函數返回時,它會從棧中刪除頂部上下文,并使用該上下文繼續執行。 存儲這個棧需要計算機內存中的空間。 當棧變得太大時,計算機將失敗,并顯示“棧空間不足”或“遞歸太多”等消息。 下面的代碼通過向計算機提出一個非常困難的問題來說明這一點,這個問題會導致兩個函數之間的無限的來回調用。 相反,如果計算機有無限的棧,它將會是無限的。 事實上,我們將耗盡空間,或者“把棧頂破”。 ```js function chicken() { return egg(); } function egg() { return chicken(); } console.log(chicken() + " came first."); // → ?? ``` ## 可選參數 下面的代碼可以正常執行: ```js function square(x) { return x * x; } console.log(square(4, true, "hedgehog")); // → 16 ``` 我們定義了`square`,只帶有一個參數。 然而,當我們使用三個參數調用它時,語言并不會報錯。 它會忽略額外的參數并計算第一個參數的平方。 JavaScript 對傳入函數的參數數量幾乎不做任何限制。如果你傳遞了過多參數,多余的參數就會被忽略掉,而如果你傳遞的參數過少,遺漏的參數將會被賦值成`undefined`。 該特性的缺點是你可能恰好向函數傳遞了錯誤數量的參數,但沒有人會告訴你這個錯誤。 優點是這種行為可以用于使用不同數量的參數調用一個函數。 例如,這個`minus`函數試圖通過作用于一個或兩個參數,來模仿`-`運算符: ```js function minus(a, b) { if (b === undefined) return -a; else return a - b; } console.log(minus(10)); // → -10 console.log(minus(10, 5)); // → 5 ``` 如果你在一個參數后面寫了一個`=`運算符,然后是一個表達式,那么當沒有提供它時,該表達式的值將會替換該參數。 例如,這個版本的`power`使其第二個參數是可選的。 如果你沒有提供或傳遞`undefined`,它將默認為 2,函數的行為就像`square`。 ```js function power(base, exponent = 2) { let result = 1; for (let count = 0; count < exponent; count++) { result *= base; } return result; } console.log(power(4)); // → 16 console.log(power(2, 6)); // → 64 ``` 在下一章當中,我們將會了解如何獲取傳遞給函數的整個參數列表。我們可以借助于這種特性來實現函數接收任意數量的參數。比如`console.log`就利用了這種特性,它可以用來輸出所有傳遞給它的值。 ```js console.log("C", "O", 2); // → C O 2 ``` ## 閉包 函數可以作為值使用,而且其局部綁定會在每次函數調用時重新創建,由此引出一個值得我們探討的問題:如果函數已經執行結束,那么這些由函數創建的局部綁定會如何處理呢? 下面的示例代碼展示了這種情況。代碼中定義了函數`wrapValue`,該函數創建了一個局部綁定`localVariable`,并返回一個函數,用于訪問并返回局部綁定`localVariable`。 ```js function wrapValue(n) { let local = n; return () => local; } let wrap1 = wrapValue(1); let wrap2 = wrapValue(2); console.log(wrap1()); // → 1 console.log(wrap2()); // → 2 ``` 這是允許的并且按照您的希望運行 - 綁定的兩個實例仍然可以訪問。 這種情況很好地證明了一個事實,每次調用都會重新創建局部綁定,而且不同的調用不能覆蓋彼此的局部綁定。 這種特性(可以引用封閉作用域中的局部綁定的特定實例)稱為閉包。 引用來自周圍的局部作用域的綁定的函數稱為(一個)閉包。 這種行為不僅可以讓您免于擔心綁定的生命周期,而且還可以以創造性的方式使用函數值。 我們對上面那個例子稍加修改,就可以創建一個可以乘以任意數字的函數。 ```js function multiplier(factor) { return number => number * factor; } let twice = multiplier(2); console.log(twice(5)); // → 10 ``` 由于參數本身就是一個局部綁定,所以`wrapValue`示例中顯式的`local`綁定并不是真的需要。 考慮這樣的程序需要一些實踐。 一個好的心智模型是,將函數值看作值,包含他們主體中的代碼和它們的創建環境。 被調用時,函數體會看到它的創建環境,而不是它的調用環境。 這個例子調用`multiplier`并創建一個環境,其中`factor`參數綁定了 2。 它返回的函數值,存儲在`twice`中,會記住這個環境。 所以當它被調用時,它將它的參數乘以 2。 ## 遞歸 一個函數調用自己是完全可以的,只要它沒有經常這樣做以致溢出棧。 調用自己的函數被稱為遞歸函數。 遞歸允許一些函數以不同的風格編寫。 舉個例子,這是`power`的替代實現: ```js function power(base, exponent) { if (exponent == 0) { return 1; } else { return base * power(base, exponent - 1); } } console.log(power(2, 3)); // → 8 ``` 這與數學家定義冪運算的方式非常接近,并且可以比循環變體將該概念描述得更清楚。 該函數以更小的指數多次調用自己以實現重復的乘法。 但是這個實現有一個問題:在典型的 JavaScript 實現中,它大約比循環版本慢三倍。 通過簡單循環來運行,通常比多次調用函數開銷低。 速度與優雅的困境是一個有趣的問題。 您可以將其視為人性化和機器友好性之間的權衡。 幾乎所有的程序都可以通過更大更復雜的方式加速。 程序員必須達到適當的平衡。 在`power`函數的情況下,不雅的(循環)版本仍然非常簡單易讀。 用遞歸版本替換它沒有什么意義。 然而,通常情況下,一個程序處理相當復雜的概念,為了讓程序更直接,放棄一些效率是有幫助的。 擔心效率可能會令人分心。 這又是另一個讓程序設計變復雜的因素,當你做了一件已經很困難的事情時,擔心的額外事情可能會癱瘓。 因此,總是先寫一些正確且容易理解的東西。 如果您擔心速度太慢 - 通常不是這樣,因為大多數代碼的執行不足以花費大量時間 - 您可以事后進行測量并在必要時進行改進。 遞歸并不總是循環的低效率替代方法。 遞歸比循環更容易解決解決一些問題。 這些問題通常是需要探索或處理幾個“分支”的問題,每個“分支”可能再次派生為更多的分支。 考慮這個難題:從數字 1 開始,反復加 5 或乘 3,就可以產生無限數量的新數字。 你會如何編寫一個函數,給定一個數字,它試圖找出產生這個數字的,這種加法和乘法的序列? 例如,數字 13 可以通過先乘 3 然后再加 5 兩次來到達,而數字 15 根本無法到達。 使用遞歸編碼的解決方案如下所示: ```js function findSolution(target) { function find(current, history) { if (current == target) { return history; } else if (current > target) { return null; } else { return find(current + 5, `(${history} + 5)`) || find(current * 3, `(${history} * 3)`); } } return find(1, "1"); } console.log(findSolution(24)); // → (((1 * 3) + 5) * 3) ``` 需要注意的是該程序并不需要找出最短運算序列,只需要找出任何一個滿足要求的序列即可。 如果你沒有看到它的工作原理,那也沒關系。 讓我們瀏覽它,因為它是遞歸思維的很好的練習。 內層函數`find`進行實際的遞歸。 它有兩個參數:當前數字和記錄我們如何到達這個數字的字符串。 如果找到解決方案,它會返回一個字符串,顯示如何到達目標。 如果從這個數字開始找不到解決方案,則返回`null`。 為此,該函數執行三個操作之一。 如果當前數字是目標數字,則當前歷史記錄是到達目標的一種方式,因此將其返回。 如果當前的數字大于目標,則進一步探索該分支是沒有意義的,因為加法和乘法只會使數字變大,所以它返回`null`。 最后,如果我們仍然低于目標數字,函數會嘗試從當前數字開始的兩個可能路徑,通過調用它自己兩次,一次是加法,一次是乘法。 如果第一次調用返回非`null`的東西,則返回它。 否則,返回第二個調用,無論它產生字符串還是`null`。 為了更好地理解函數執行過程,讓我們來看一下搜索數字 13 時,`find`函數的調用情況: ``` find(1, "1") find(6, "(1 + 5)") find(11, "((1 + 5) + 5)") find(16, "(((1 + 5) + 5) + 5)") too big find(33, "(((1 + 5) + 5) * 3)") too big find(18, "((1 + 5) * 3)") too big find(3, "(1 * 3)") find(8, "((1 * 3) + 5)") find(13, "(((1 * 3) + 5) + 5)") found! ``` 縮進表示調用棧的深度。 第一次調用`find`時,它首先調用自己來探索以`(1 + 5)`開始的解決方案。 這一調用將進一步遞歸,來探索每個后續的解,它產生小于或等于目標數字。 由于它沒有找到一個命中目標的解,所以它向第一個調用返回`null`。 那里的`||`操作符會使探索`(1 * 3)`的調用發生。 這個搜索的運氣更好 - 它的第一次遞歸調用,通過另一個遞歸調用,命中了目標數字。 最內層的調用返回一個字符串,并且中間調用中的每個“||”運算符都會傳遞該字符串,最終返回解決方案。 ## 添加新函數 這里有兩種常用的方法,將函數引入到程序中。 首先是你發現自己寫了很多次非常相似的代碼。 我們最好不要這樣做。 擁有更多的代碼,意味著更多的錯誤空間,并且想要了解程序的人閱讀更多資料。 所以我們選取重復的功能,為它找到一個好名字,并把它放到一個函數中。 第二種方法是,你發現你需要一些你還沒有寫的功能,這聽起來像是它應該有自己的函數。 您將首先命名該函數,然后您將編寫它的主體。 在實際定義函數本身之前,您甚至可能會開始編寫使用該函數的代碼。 給函數起名的難易程度取決于我們封裝的函數的用途是否明確。對此,我們一起來看一個例子。 我們想編寫一個打印兩個數字的程序,第一個數字是農場中牛的數量,第二個數字是農場中雞的數量,并在數字后面跟上`Cows`和`Chickens`用以說明,并且在兩個數字前填充 0,以使得每個數字總是由三位數字組成。 ``` 007 Cows 011 Chickens ``` 這需要兩個參數的函數 - 牛的數量和雞的數量。 讓我們來編程。 ```js function printFarmInventory(cows, chickens) { let cowString = String(cows); while (cowString.length < 3) { cowString = "0" + cowString; } console.log(`${cowString} Cows`); let chickenString = String(chickens); while (chickenString.length < 3) { chickenString = "0" + chickenString; } console.log(`${chickenString} Chickens`); } printFarmInventory(7, 11); ``` 在字符串表達式后面寫`.length`會給我們這個字符串的長度。 因此,`while`循環在數字字符串前面加上零,直到它們至少有三個字符的長度。 任務完成! 但就在我們即將向農民發送代碼(連同大量發票)時,她打電話告訴我們,她也開始飼養豬,我們是否可以擴展軟件來打印豬的數量? 當然沒有問題。但是當再次復制粘貼這四行代碼的時候,我們停了下來并重新思考。一定還有更好的方案來解決我們的問題。以下是第一種嘗試: ```js function printZeroPaddedWithLabel(number, label) { let numberString = String(number); while (numberString.length < 3) { numberString = "0" + numberString; } console.log(`${numberString} ${label}`); } function printFarmInventory(cows, chickens, pigs) { printZeroPaddedWithLabel(cows, "Cows"); printZeroPaddedWithLabel(chickens, "Chickens"); printZeroPaddedWithLabel(pigs, "Pigs"); } printFarmInventory(7, 11, 3); ``` 這種方法解決了我們的問題!但是`printZeroPaddedWithLabel`這個函數并不十分恰當。它把三個操作,即打印信息、數字補零和添加標簽放到了一個函數中處理。 這一次,我們不再將程序當中重復的代碼提取成一個函數,而只是提取其中一項操作。 ```js function zeroPad(number, width) { let string = String(number); while (string.length < width) { string = "0" + string; } return string; } function printFarmInventory(cows, chickens, pigs) { console.log(`${zeroPad(cows, 3)} Cows`); console.log(`${zeroPad(chickens, 3)} Chickens`); console.log(`${zeroPad(pigs, 3)} Pigs`); } printFarmInventory(7, 16, 3); ``` 名為`zeroPad`的函數具有很好的名稱,使讀取代碼的人更容易弄清它的功能。 而且這樣的函數在更多的情況下是有用的,不僅僅是這個特定程序。 例如,您可以使用它來幫助打印精確對齊的數字表格。 我們的函數應該包括多少功能呢?我們可以編寫一個非常簡單的函數,只支持將數字擴展成 3 字符寬。也可以編寫一個復雜通用的數字格式化系統,可以處理分數、負數、小數點對齊和使用不同字符填充等。 一個實用原則是不要故作聰明,除非你確定你會需要它。 為你遇到的每一個功能編寫通用“框架”是很誘人的。 控制住那種沖動。 你不會完成任何真正的工作 - 你只會編寫你永遠不會使用的代碼。 ## 函數及其副作用 我們可以將函數分成兩類:一類調用后產生副作用,而另一類則產生返回值(當然我們也可以定義同時產生副作用和返回值的函數)。 在農場案例當中,我們調用第一個輔助函數`printZeroPaddedWithLabel`來產生副作用,打印一行文本信息。而在第二個版本中有一個`zeroPad`函數,我們調用它來產生返回值。第二個函數比第一個函數的應用場景更加廣泛,這并非偶然。相比于直接產生副作用的函數,產生返回值的函數則更容易集成到新的環境當中使用。 純函數是一種特定類型的,生成值的函數,它不僅沒有副作用,而且也不依賴其他代碼的副作用,例如,它不讀取值可能會改變的全局綁定。 純函數具有令人愉快的屬性,當用相同的參數調用它時,它總是產生相同的值(并且不會做任何其他操作)。 這種函數的調用,可以由它的返回值代替而不改變代碼的含義。 當你不確定純函數是否正常工作時,你可以通過簡單地調用它來測試它,并且知道如果它在當前上下文中工作,它將在任何上下文中工作。 非純函數往往需要更多的腳手架來測試。 盡管如此,我們也沒有必要覺得非純函數就不好,然后將這類函數從代碼中刪除。副作用常常是非常有用的。比如說,我們不可能去編寫一個純函數版本的`console.log`,但`console.log`依然十分實用。而在副作用的幫助下,有些操作則更易、更快實現,因此考慮到運算速度,有時候純函數并不可取。 ## 本章小結 本章教你如何編寫自己的函數。 當用作表達式時,`function`關鍵字可以創建一個函數值。 當作為一個語句使用時,它可以用來聲明一個綁定,并給它一個函數作為它的值。 箭頭函數是另一種創建函數的方式。 ```js // Define f to hold a function value const f = function(a) { console.log(a + 2); }; // Declare g to be a function function g(a, b) { return a * b * 3.5; } // A less verbose function value let h = a => a % 3; ``` 理解函數的一個關鍵方面是理解作用域。 每個塊創建一個新的作用域。 在給定作用域內聲明的參數和綁定是局部的,并且從外部看不到。 用`var`聲明的綁定行為不同 - 它們最終在最近的函數作用域或全局作用域內。 將程序執行的任務分成不同的功能是有幫助的。 你不必重復自己,函數可以通過將代碼分組成一些具體事物,來組織程序。 ## 習題 ### 最小值 前一章介紹了標準函數`Math.min`,它可以返回參數中的最小值。我們現在可以構建相似的東西。編寫一個函數`min`,接受兩個參數,并返回其最小值。 ```js // Your code here. console.log(min(0, 10)); // → 0 console.log(min(0, -10)); // → -10 ``` ### 遞歸 我們已經看到,`%`(取余運算符)可以用于判斷一個數是否是偶數,通過使用`% 2`來檢查它是否被 2 整除。這里有另一種方法來判斷一個數字是偶數還是奇數: + 0是偶數 + 1是奇數 + 對于其他任何數字N,其奇偶性與N–2相同。 定義對應此描述的遞歸函數`isEven`。 該函數應該接受一個參數(一個正整數)并返回一個布爾值。 使用 50 與 75 測試該函數。想想如果參數為 –1 會發生什么以及產生相應結果的原因。請你想一個方法來修正該問題。 ```js // Your code here. console.log(isEven(50)); // → true console.log(isEven(75)); // → false console.log(isEven(-1)); // → ?? ``` ### 字符計數 你可以通過編寫`"string"[N]`,來從字符串中得到第`N`個字符或字母。 返回的值將是只包含一個字符的字符串(例如`"b"`)。 第一個字符的位置為零,這會使最后一個字符在`string.length - 1`。 換句話說,含有兩個字符的字符串的長度為2,其字符的位置為 0 和 1。 編寫一個函數`countBs`,接受一個字符串參數,并返回一個數字,表示該字符串中有多少個大寫字母`"B"`。 接著編寫一個函數`countChar`,和`countBs`作用一樣,唯一區別是接受第二個參數,指定需要統計的字符(而不僅僅能統計大寫字母`"B"`)。并使用這個新函數重寫函數`countBs`。 ```js // Your code here. console.log(countBs("BBC")); // → 2 console.log(countChar("kakkerlak", "k")); // → 4 ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看