JavaScript 編程精解 中文第三版 3、函數

來源: ApacheCN『JavaScript 編程精解 中文第三版』翻譯項目

原文:Functionsjavascript

譯者:飛龍html

協議:CC BY-NC-SA 4.0java

自豪地採用谷歌翻譯git

部分參考了《JavaScript 編程精解(第 2 版)》程序員

人們認爲計算機科學是天才的藝術,可是實際狀況相反,只是許多人在其它人基礎上作一些東西,就像一面由石子壘成的牆。github

高德納apache

函數是 JavaScript 編程的麪包和黃油。 將一段程序包裝成值的概念有不少用途。 它爲咱們提供了方法,用於構建更大程序,減小重複,將名稱和子程序關聯,以及將這些子程序相互隔離。編程

函數最明顯的應用是定義新詞彙。 用散文創造新詞彙一般是很差的風格。 但在編程中,它是不可或缺的。閉包

以英語爲母語的典型成年人,大約有 2 萬字的詞彙量。 不多有編程語言內置了 2 萬個命令。並且,可用的詞彙的定義每每比人類語言更精確,所以靈活性更低。 所以,咱們一般會引入新的概念,來避免過多重複。框架

定義函數

函數定義是一個常規綁定,其中綁定的值是一個函數。 例如,這段代碼定義了square,來引用一個函數,它產生給定數字的平方:

const square = function(x) {
  return x * x;
};

console.log(square(12));
// → 144

函數使用以關鍵字function起始的表達式建立。 函數有一組參數(在本例中只有x)和一個主體,它包含調用該函數時要執行的語句。 以這種方式建立的函數的函數體,必須始終包在花括號中,即便它僅包含一個語句。

一個函數能夠包含多個參數,也能夠不含參數。在下面的例子中,makeNoise函數中沒有包含任何參數,而power則使用了兩個參數:

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

有些函數會產生一個值,好比powersquare,有些函數不會,好比makeNoise,它的惟一結果是反作用。 return語句決定函數返回的值。 當控制流遇到這樣的語句時,它當即跳出當前函數並將返回的值賦給調用該函數的代碼。 不帶表達式的return關鍵字,會致使函數返回undefined。 沒有return語句的函數,好比makeNoise,一樣返回undefined

函數的參數行爲與常規綁定類似,但它們的初始值由函數的調用者提供,而不是函數自己的代碼。

綁定和做用域

每一個綁定都有一個做用域,它是程序的一部分,其中綁定是可見的。 對於在任何函數或塊以外定義的綁定,做用域是整個程序 - 您能夠在任何地方引用這種綁定。它們被稱爲全局的。

可是爲函數參數建立的,或在函數內部聲明的綁定,只能在該函數中引用,因此它們被稱爲局部綁定。 每次調用該函數時,都會建立這些綁定的新實例。 這提供了函數之間的一些隔離 - 每一個函數調用,都在它本身的小世界(它的局部環境)中運行,而且一般能夠在不知道全局環境中發生的事情的狀況下理解。

letconst聲明的綁定,其實是它們的聲明所在的塊的局部對象,因此若是你在循環中建立了一個,那麼循環以前和以後的代碼就不能「看見」它。JavaScript 2015 以前,只有函數建立新的做用域,所以,使用var關鍵字建立的舊式綁定,在它們出現的整個函數中內均可見,或者若是它們不在函數中,在全局做用域可見。

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

const halve = function(n) {
  return n / 2;
}
let n = 10;
console.log(halve(100));
// → 50
console.log(n);
// → 10

嵌套做用域

JavaScript 不只區分全局和局部綁定。 塊和函數能夠在其餘塊和函數內部建立,產生多層局部環境。

例如,這個函數(輸出製做一批鷹嘴豆泥所需的配料)的內部有另外一個函數:

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綁定。 可是它的局部綁定,好比unitingredientAmount,在外層函數中是不可見的。

簡而言之,每一個局部做用域也能夠看到全部包含它的局部做用域。 塊內可見的綁定集,由這個塊在程序文本中的位置決定。 每一個局部做用域也能夠看到包含它的全部局部做用域,而且全部做用域均可以看到全局做用域。 這種綁定可見性方法稱爲詞法做用域。

做爲值的函數

函數綁定一般只充當程序特定部分的名稱。 這樣的綁定被定義一次,永遠不會改變。 這使得容易混淆函數和名稱。

let launchMissiles = function(value) {
  missileSystem.launch("now");
};
if (safeMode) {
  launchMissiles = function() {/* do nothing */};
}

在第 5 章中,咱們將會討論一些高級功能:將函數類型的值傳遞給其餘函數。

符號聲明

建立函數綁定的方法稍短。 當在語句開頭使用function關鍵字時,它的工做方式不一樣。

function square(x) {
  return x * x;
}

這是函數聲明。 該語句定義了綁定square並將其指向給定的函數。 寫起來稍微容易一些,而且在函數以後不須要分號。

這種形式的函數定義有一個微妙之處。

console.log("The future says:", future());

function future() {
  return "You'll never have flying cars";
}

前面的代碼能夠執行,即便在函數定義在使用它的代碼下面。 函數聲明不是常規的從上到下的控制流的一部分。 在概念上,它們移到了其做用域的頂部,並可被該做用域內的全部代碼使用。 這有時是有用的,由於它以一種看似有意義的方式,提供了對代碼進行排序的自由,而無需擔憂在使用以前必須定義全部函數。

箭頭函數

函數的第三個符號與其餘函數看起來有很大不一樣。 它不使用function關鍵字,而是使用由等號和大於號組成的箭頭(=>)(不要與大於等於運算符混淆,該運算符寫作>=)。

const power = (base, exponent) => {
  let result = 1;
  for (let count = 0; count < exponent; count++) {
    result *= base;
  }
  return result;
};

箭頭出如今參數列表後面,而後是函數的主體。 它表達了一些東西,相似「這個輸入(參數)產生這個結果(主體)」。

若是隻有一個參數名稱,則能夠省略參數列表周圍的括號。 若是主體是單個表達式,而不是大括號中的塊,則表達式將從函數返回。 因此這兩個square的定義是同樣的:

const square1 = (x) => { return x * x; };
const square2 = x => x * x;

當一個箭頭函數沒有參數時,它的參數列表只是一組空括號。

const horn = () => {
  console.log("Toot");
};

在語言中沒有很好的理由,同時擁有箭頭函數和函數表達式。 除了咱們將在第 6 章中討論的一個小細節外,他們實現相同的東西。 在 2015 年增長了箭頭函數,主要是爲了可以以簡短的方式編寫小函數表達式。 咱們將在第 5 章中使用它們。

調用棧

控制流通過函數的方式有點複雜。 讓咱們仔細看看它。 這是一個簡單的程序,它執行了一些函數調用:

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函數。 在另外一種狀況下,它返回到程序的結尾。

計算機存儲此上下文的地方是調用棧。 每次調用函數時,當前上下文都存儲在此棧的頂部。 當函數返回時,它會從棧中刪除頂部上下文,並使用該上下文繼續執行。

存儲這個棧須要計算機內存中的空間。 當棧變得太大時,計算機將失敗,並顯示「棧空間不足」或「遞歸太多」等消息。 下面的代碼經過向計算機提出一個很是困難的問題來講明這一點,這個問題會致使兩個函數之間的無限的來回調用。 相反,若是計算機有無限的棧,它將會是無限的。 事實上,咱們將耗盡空間,或者「把棧頂破」。

function chicken() {
  return egg();
}
function egg() {
  return chicken();
}
console.log(chicken() + " came first.");
// → ??

可選參數

下面的代碼能夠正常執行:

function square(x) { return x * x; }
console.log(square(4, true, "hedgehog"));
// → 16

咱們定義了square,只帶有一個參數。 然而,當咱們使用三個參數調用它時,語言並不會報錯。 它會忽略額外的參數並計算第一個參數的平方。

JavaScript 對傳入函數的參數數量幾乎不作任何限制。若是你傳遞了過多參數,多餘的參數就會被忽略掉,而若是你傳遞的參數過少,遺漏的參數將會被賦值成undefined

該特性的缺點是你可能剛好向函數傳遞了錯誤數量的參數,但沒有人會告訴你這個錯誤。

優勢是這種行爲能夠用於使用不一樣數量的參數調用一個函數。 例如,這個minus函數試圖經過做用於一個或兩個參數,來模仿-運算符:

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

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就利用了這種特性,它能夠用來輸出全部傳遞給它的值。

console.log("C", "O", 2);
// → C O 2

閉包

函數能夠做爲值使用,並且其局部綁定會在每次函數調用時從新建立,由此引出一個值得咱們探討的問題:若是函數已經執行結束,那麼這些由函數建立的局部綁定會如何處理呢?

下面的示例代碼展現了這種狀況。代碼中定義了函數wrapValue,該函數建立了一個局部綁定localVariable,並返回一個函數,用於訪問並返回局部綁定localVariable

function wrapValue(n) {
  let local = n;
  return () => local;
}

let wrap1 = wrapValue(1);
let wrap2 = wrapValue(2);
console.log(wrap1());
// → 1
console.log(wrap2());
// → 2

這是容許的而且按照您的但願運行 - 綁定的兩個實例仍然能夠訪問。 這種狀況很好地證實了一個事實,每次調用都會從新建立局部綁定,並且不一樣的調用不能覆蓋彼此的局部綁定。

這種特性(能夠引用封閉做用域中的局部綁定的特定實例)稱爲閉包。 引用來自周圍的局部做用域的綁定的函數稱爲(一個)閉包。 這種行爲不只可讓您免於擔憂綁定的生命週期,並且還能夠以創造性的方式使用函數值。

咱們對上面那個例子稍加修改,就能夠建立一個能夠乘以任意數字的函數。

function multiplier(factor) {
 return number => number * factor;
}

let twice = multiplier(2);
console.log(twice(5));
// → 10

因爲參數自己就是一個局部綁定,因此wrapValue示例中顯式的local綁定並非真的須要。

考慮這樣的程序須要一些實踐。 一個好的心智模型是,將函數值看做值,包含他們主體中的代碼和它們的建立環境。 被調用時,函數體會看到它的建立環境,而不是它的調用環境。

這個例子調用multiplier並建立一個環境,其中factor參數綁定了 2。 它返回的函數值,存儲在twice中,會記住這個環境。 因此當它被調用時,它將它的參數乘以 2。

遞歸

一個函數調用本身是徹底能夠的,只要它沒有常常這樣作以至溢出棧。 調用本身的函數被稱爲遞歸函數。 遞歸容許一些函數以不一樣的風格編寫。 舉個例子,這是power的替代實現:

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 根本沒法到達。

使用遞歸編碼的解決方案以下所示:

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)的調用發生。 這個搜索的運氣更好 - 它的第一次遞歸調用,經過另外一個遞歸調用,命中了目標數字。 最內層的調用返回一個字符串,而且中間調用中的每一個「||」運算符都會傳遞該字符串,最終返回解決方案。

添加新函數

這裏有兩種經常使用的方法,將函數引入到程序中。

首先是你發現本身寫了不少次很是類似的代碼。 咱們最好不要這樣作。 擁有更多的代碼,意味着更多的錯誤空間,而且想要了解程序的人閱讀更多資料。 因此咱們選取重複的功能,爲它找到一個好名字,並把它放到一個函數中。

第二種方法是,你發現你須要一些你尚未寫的功能,這聽起來像是它應該有本身的函數。 您將首先命名該函數,而後您將編寫它的主體。 在實際定義函數自己以前,您甚至可能會開始編寫使用該函數的代碼。

給函數起名的難易程度取決於咱們封裝的函數的用途是否明確。對此,咱們一塊兒來看一個例子。

咱們想編寫一個打印兩個數字的程序,第一個數字是農場中牛的數量,第二個數字是農場中雞的數量,並在數字後面跟上CowsChickens用以說明,而且在兩個數字前填充 0,以使得每一個數字老是由三位數字組成。

007 Cows
011 Chickens

這須要兩個參數的函數 - 牛的數量和雞的數量。 讓咱們來編程。

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循環在數字字符串前面加上零,直到它們至少有三個字符的長度。

任務完成! 但就在咱們即將向農民發送代碼(連同大量發票)時,她打電話告訴咱們,她也開始飼養豬,咱們是否能夠擴展軟件來打印豬的數量?

固然沒有問題。可是當再次複製粘貼這四行代碼的時候,咱們停了下來並從新思考。必定還有更好的方案來解決咱們的問題。如下是第一種嘗試:

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這個函數並不十分恰當。它把三個操做,即打印信息、數字補零和添加標籤放到了一個函數中處理。

這一次,咱們再也不將程序當中重複的代碼提取成一個函數,而只是提取其中一項操做。

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關鍵字能夠建立一個函數值。 看成爲一個語句使用時,它能夠用來聲明一個綁定,並給它一個函數做爲它的值。 箭頭函數是另外一種建立函數的方式。

// 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,接受兩個參數,並返回其最小值。

// 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 會發生什麼以及產生相應結果的緣由。請你想一個方法來修正該問題。

// 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

// Your code here.

console.log(countBs("BBC"));
// → 2
console.log(countChar("kakkerlak", "k"));
// → 4
相關文章
相關標籤/搜索