【技術積累】JavaScript中的函數【一】

2023-06-13 15:01:17

什麼是函數?如何宣告函數?

JavaScript中的函數是一段可重複使用的程式碼塊,它可以接受輸入並返回輸出。 在JavaScript中,函數是一種特殊的物件,因此可以將其儲存在變數中,將其作為引數傳遞給其他函數,並從其他函數中返回。

在JavaScript中,宣告函數有兩種方式:函數宣告和函數表示式。

1. 函數宣告

函數宣告是指用關鍵字function定義函數的方法,在函數名後跟一對圓括號和一對花括號,並在花括號中編寫函數體。

範例:宣告一個函數,它將兩個數位相加並返回其總和。

function addNumbers(num1, num2) {
  return num1 + num2;
}

2. 函數表示式

函數表示式是指將函數賦值給變數的方法。它通常包含一個匿名函數,因此可以使用變數名呼叫該函數。

範例:用函數表示式宣告一個函數,它列印出兩個數位的總和。

let addNumbers = function(num1, num2) {
  console.log(num1 + num2);
};

函數還可以在呼叫時接受引數。這些引數通常是函數需要的輸入,可以在函數中使用。

例如,下面的函數將兩個引數相加並返回結果:

function addNumbers(num1, num2) {
  return num1 + num2;
}

let result = addNumbers(3, 5);
console.log(result); // 輸出結果為 8

在這個例子中,我們呼叫了addNumbers函數,並將3和5傳遞給它作為引數。計算完成後,該函數返回結果8。 最後,我們將結果輸出到控制檯。

總之,JavaScript中的函數是一種強大的工具,可以幫助我們將程式碼重用和管理程式碼流程。無論是函數宣告還是函數表示式,我們都可以使用它們來建立程式碼塊,以便在程式中多次使用。

函數如何返回值

JavaScript函數可以返回值。一個函數可以返回一個值,這個值可以是任意型別的資料,包括數位、字串、物件等等。

要返回一個值,可以使用關鍵字「return」後面跟著要返回的值。範例如下:

function add(a, b) {
  return a + b;
}

var sum = add(2, 3);
console.log(sum); // 輸出 5

在這個例子中,函數「add」返回了引數「a」和「b」的和。當使用這個函數時,返回值會被儲存在變數「sum」中,並且在控制檯中輸出。

在函數內部,如果沒有明確的使用「return」語句,則預設返回「undefined」。例如:

function sayHello() {
  console.log("Hello");
}

var result = sayHello();
console.log(result); // 輸出 undefined

在這個例子中,函數「sayHello」沒有返回任何值,因此預設返回了「undefined」。

在實際程式設計中,函數的返回值通常用於向呼叫者傳遞處理結果或者狀態。例如,在某些情況下,可能需要根據函數返回的值採取不同的操作,像這樣:

function checkAge(age) {
  if (age >= 18) {
    return "成年人";
  } else {
    return "未成年人";
  }
}

var result = checkAge(20);

if (result === "成年人") {
  console.log("你可以去看電影");
} else {
  console.log("你還不能去看電影");
}

在這個例子中,函數「checkAge」接收一個引數「age」,根據年齡判斷是否成年,並返回「成年人」或「未成年人」。當呼叫函數時,返回值被儲存在「result」變數中。在後面的程式碼中,使用「if」語句根據返回值來判斷是否可以去看電影。

什麼是函數引數?函數引數傳遞的方式有哪些?

函數引數是函數定義時用來接收外部傳遞進來的值或物件的預留位置。在呼叫函數時,我們需要傳遞實際的值或物件作為函數引數,以便在函數內部使用。

函數引數傳遞的方式有以下幾種:

  1. 傳遞單個值:直接把值傳遞給函數。

  2. 傳遞多個值:使用逗號分隔多個值,按照參數列的順序傳遞給函數。

  3. 傳遞物件:將一個物件作為引數傳遞給函數,函數可以通過物件存取其屬性。

  4. 傳遞陣列:將一個陣列作為引數傳遞給函數,函數可以使用陣列下標存取陣列元素。

  5. 省略引數:可以省略某些引數,未傳遞的引數將被設定為undefined。

  6. 回撥函數:將一個函數作為另一個函數的引數傳遞,以便在另一個函數執行完成後呼叫該函數。

  7. 使用arguments物件:函數內部可以使用arguments物件存取所有傳遞進來的引數,無需事先定義引數的名稱和數量。

  8. ES6中的展開運運算元:可以使用展開運運算元將陣列或物件中的值展開為函數的引數。

在函數中使用全域性變數可以直接使用全域性變數的名稱。在JavaScript中,全域性變數可以通過在函數外部定義變數來建立。例如:

var globalVar = "Hello World!";

function myFunction() {
  console.log(globalVar);
}

myFunction(); // 輸出結果:Hello World!

在函數內部建立區域性變數,可以使用var、let和const關鍵字定義一個新的變數。這些變數的作用域僅限於函數內部,函數外部無法存取。例如:

function myFunction() {
  var localVar = "This is a local variable.";
  console.log(localVar);
}

myFunction(); // 輸出結果:This is a local variable.
console.log(localVar); // 丟擲ReferenceError異常:localVar is not defined

在ES6中,使用let和const關鍵字建立的變數具有塊級作用域,可以在定義變數的塊中存取該變數,包括在函數內部。例如:

function myFunction() {
  let localVar = "This is a local variable.";
  console.log(localVar);
}

myFunction(); // 輸出結果:This is a local variable.
console.log(localVar); // 丟擲ReferenceError異常:localVar is not defined

什麼是函數作用域

JavaScript的函數作用域是指變數可以在函數內宣告並在函數內使用,而在函數外無法存取該變數。這是因為JavaScript中的函數都有它們自己的作用域。

例如,在以下範例中,變數x在函數foo的作用域中宣告,因此只能在該函數內部使用:

function foo() {
  var x = 10; // 變數x 只在函數foo中可見
  console.log(x);
}

console.log(x); // 報錯,變數x無法存取

函數作用域可以幫助程式設計師避免變數命名衝突和全域性變數的濫用。它還允許在函數內宣告和使用私有變數,這些變數不會洩露到全域性作用域中。

JavaScript中的函數作用域實現是通過詞法作用域(也稱靜態作用域)實現的。在詞法作用域中,函數的作用域是在函數定義時確定的,而不是在函數呼叫時確定的。這意味著函數可以在它被定義的地方存取其外部作用域中的變數,而不必等到函數被呼叫時。

什麼是閉包?如何使用閉包?

閉包是指在一個函數內部定義另一個函數,並使得這個內部函數可以存取到外部函數的變數和引數。閉包可以將這些變數和引數的值儲存下來,以供後續使用。

在JavaScript中,可以使用閉包來實現許多功能,比如:

1. 封裝私有變數

通過使用閉包,可以建立一個物件,該物件包含一些私有變數和一些公共方法,但這些私有變數無法從外部存取。這樣做可以保證程式碼的安全性和可維護性。

例如:

function createCounter() {
  var count = 0;
  return {
    increment: function() {
      count++;
    },
    getValue: function() {
      return count;
    }
  };
}

var counter = createCounter();

counter.increment();
console.log(counter.getValue()); // 1

2. 延遲執行

通過使用閉包,可以在需要時延遲執行某些程式碼,這對於一些需要耗費時間的操作(比如網路請求)很有用。

例如:

function delayedAlert(message, time) {
  setTimeout(function() {
    alert(message);
  }, time);
}

delayedAlert('Hello World!', 2000);

3. 記憶化

通過使用閉包,可以在函數執行時將其結果快取下來,以便下次執行時直接返回快取的結果,從而提高效能。

例如:

function memoize(func) {
  var cache = {};
  return function() {
    var key = JSON.stringify(arguments);
    if (cache[key]) {
      return cache[key];
    } else {
      var result = func.apply(this, arguments);
      cache[key] = result;
      return result;
    }
  };
}

var fib = memoize(function(n) {
  if (n <= 1) {
    return n;
  } else {
    return fib(n - 1) + fib(n - 2);
  }
});

console.log(fib(10)); // 55

4. 模組化

通過使用閉包,可以建立一個獨立的模組,該模組包含一些私有變數和方法,同時仍然可以對外暴露一些公共方法。

例如:

var calculator = (function() {
  var result = 0;

  function add(x) {
    result += x;
  }

  function subtract(x) {
    result -= x;
  }

  function reset() {
    result = 0;
  }

  function getResult() {
    return result;
  }

  return {
    add: add,
    subtract: subtract,
    reset: reset,
    getResult: getResult
  };
})();

calculator.add(5);
calculator.subtract(2);
console.log(calculator.getResult()); // 3
calculator.reset();
console.log(calculator.getResult()); // 0

總之,閉包是一種非常強大的功能,在JavaScript中廣泛使用。它可以用於封裝私有變數、延遲執行、記憶化、模組化等多種場合。

閉包的優點和缺點是什麼?

JavaScript閉包的優點:

  1. 可以通過閉包來建立私有變數和方法,防止變數和方法被外部程式碼存取和修改,從而保護程式的安全性。
  2. 可以幫助我們實現高階函數,從而提高程式碼的可複用性,並且可以使程式碼更加簡潔。
  3. 閉包可以實現資料快取,從而提高程式的效能,避免重複計算。

JavaScript閉包的缺點:

  1. 閉包可能導致記憶體漏失,因為閉包中捕獲的變數不會被垃圾回收機制自動釋放。
  2. 閉包的執行速度可能比常規函數慢,因為它需要同時儲存外部函數的作用域和內部函數的作用域。
  3. 閉包可能會導致變數被意外修改,因為閉包中的變數是共用的,如果在外部函數中修改了閉包中的變數,那麼所有使用該閉包的程式碼都會受到影響。

什麼是高階函數

在JavaScript中,高階函數是指能夠接收一個或多個函數作為引數,並且能夠返回一個新函數的函數。這種函數的靈活性讓它們非常強大,並可以用於編寫更加簡潔和靈活的程式碼。

高階函數的特點:

  1. 接收一個或多個函數作為引數。
  2. 返回一個新函數。
  3. 可以對引數函數進行處理,比如運算、組合、封裝等操作。

高階函數的使用場景:

  1. 回撥函數:在非同步程式碼中,高階函數常常被用作回撥函數,用於處理非同步操作的結果。
  2. 遍歷:高階函數可以用於對陣列、物件或其他資料結構進行遍歷和篩選。
  3. 函陣列合:高階函數可以將多個處理邏輯組合在一起,實現更加複雜的功能。
  4. 封裝:高階函數可以用於對一些重複的操作進行封裝,提高程式碼重用性和可維護性。

例如,如果我們想在一個陣列中找到所有大於3的數位,可以使用高階函數`filter`:

const arr = [1, 2, 3, 4, 5];
const result = arr.filter(function(item) {
  return item > 3;
});
console.log(result); // [4, 5]

在上述程式碼中,`filter`函數就是一個高階函數,它接收一個匿名函數作為引數,用於處理陣列元素,並返回一個新的陣列,其中僅包含滿足條件的元素。

什麼是回撥函數?如何使用回撥函數?

回撥函數是一個在某個事件發生時執行的函數,它是JavaScript中一種常見的非同步程式設計方式。回撥函數通常作為引數傳遞給另一個函數,並在另一個函數執行完畢後被呼叫。

使用回撥函數時,需要注意以下幾點:

  1. 回撥函數必須是一個函數,可以是內建函數或自定義函數。
  2. 回撥函數必須作為引數傳遞給另一個函數,並在另一個函數執行完畢後被呼叫。
  3. 回撥函數的引數和返回值必須符合另一個函數的要求。

範例:

function add(a, b, callback) {
  var result = a + b;
  callback(result);
}

add(1, 2, function(result) {
  console.log(result);
});

在上面的例子中,add函數有三個引數,其中callback函數是回撥函數,當add函數執行完畢後,會呼叫這個回撥函數並傳遞計算結果。

在呼叫add函數時,傳入了兩個數位和一個回撥函數。回撥函數在add函數執行完畢後被呼叫,並列印出計算結果。

什麼是箭頭函數?

JavaScript中的箭頭函數是ES6(ECMAScript 2015)中新增的一種新函數型別。箭頭函數是一種更簡潔的函數宣告方式,同時也有不同於傳統函數的一些特性。

箭頭函數的語法形式為:(引數)=> {函數體}

其中,引數和函數體之間由箭頭符號(=>)連線。

箭頭函數與傳統函數的一些不同點如下:

  1. 箭頭函數沒有自己的this物件,它的this繼承自父作用域。這意味著在箭頭函數中使用this時,它實際上是指向父作用域中的this,而不是箭頭函數自己的this。

  2. 箭頭函數沒有arguments物件,但可以使用rest引數(...args)取代。

  3. 如果箭頭函數只有一個引數,可以省略小括號。

  4. 如果函數體只有一條語句,可以省略大括號和return語句。

箭頭函數的應用場景:

  1. 作為回撥函數。箭頭函數的簡潔語法和繼承父作用域的this物件使它非常適合作為回撥函數。

  2. 與陣列方法結合使用。在陣列方法中,箭頭函數比傳統函數更具可讀性和簡潔性。

  3. 與PromiseAPI結合使用。在PromiseAPI中,箭頭函數可以幫助我們更方便地處理非同步程式碼。

總之,箭頭函數是一種更簡潔、更易讀、更方便的函數型別,它的使用可以提高程式碼的可讀性和開發效率。

// 傳統函數
function greeting(name) {
  console.log("Hello, " + name);
}

// 箭頭函數
const greetingArrow = name => {
  console.log("Hello, " + name);
}

greeting("Tom"); // 輸出 "Hello, Tom"
greetingArrow("Jerry"); // 輸出 "Hello, Jerry"
const shoppingCart = [
   { id: 1, name: "Shirt", price: 25 },
   { id: 2, name: "Pants", price: 50 },
   { id: 3, name: "Shoes", price: 100 }
];

const VAT_RATE = 0.2; // 稅率

const calculateTotalPrice = shoppingCart => {
  // 使用 reduce 方法計算購物車中所有商品的總價
  const totalPrice = shoppingCart.reduce((acc, product) => acc + product.price, 0);
  
  // 計算總價和稅費
  const total = totalPrice + (totalPrice * VAT_RATE);
  
  // 四捨五入並保留兩位小數
  return Math.round(total * 100) / 100;
}

const total = calculateTotalPrice(shoppingCart);
console.log("Total Price: $" + total); // 輸出 "Total Price: $210"

什麼是Promise?如何使用Promise?

Promise是JavaScript中一種非同步程式設計的解決方案,它表示一個尚未完成但預計將來會完成的操作,並提供一種處理該操作結果的方式。

在Promise中,我們可以定義需要執行的操作,以及當操作完成後需要執行的一系列回撥函數。Promise既可以表示操作失敗的情況,也可以表示操作成功的情況。

Promise具有以下特點:

1. Promise物件的狀態不受外界影響。一旦狀態改變,就不會再變,任何時候都可以得到這個結果。

2. Promise物件的狀態一旦改變,就不能再改變。如果不設定回撥函數,Promise內部的錯誤不會影響到外部。

3. Promise 物件提供了鏈式操作,可以使其程式碼更加簡潔易讀。

const promise = new Promise((resolve, reject) => {
  // 非同步操作
  setTimeout(function(){
    const num = Math.random();
    if(num < 0.5){
      resolve(num)
    } else {
      reject(num)
    }
  }, 1000)
})

promise.then((res)=>{
  console.log("操作成功,結果為:", res);
}).catch((err)=>{
  console.log("操作失敗,結果為:", err);
})

上述程式碼中,我們通過Promise建構函式建立了一個Promise物件,在非同步操作完成後通過resolve方法表示操作成功並返回一個結果,通過reject方法表示操作失敗並返回一個錯誤資訊。在then和catch方法中,我們分別定義了操作成功和操作失敗後需要執行的回撥函數。

 

Promise有三種狀態:pending、fulfilled和rejected。當非同步操作執行時,它最初處於pending狀態。如果非同步操作成功,則狀態變為fulfilled,並返回一個值,如果發生錯誤,則狀態變為rejected,並返回一個錯誤物件。

下面是使用Promise的簡單範例:

function fetchData() {
  return new Promise((resolve, reject) => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(response => response.json())
      .then(data => resolve(data))
      .catch(error => reject(error))
  })
}

fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error))

上面的程式碼中,fetchData函數返回一個Promise。如果非同步操作成功,它會呼叫resolve回撥函數,並傳遞資料物件。如果發生錯誤,它會呼叫reject回撥函數,並傳遞一個錯誤物件。

在這個範例中,我們使用了fetch API來獲取資料。通過呼叫response.json(),我們將響應物件轉換為JSON格式的資料。最後,我們處理資料物件或錯誤物件,您可以在.then()和.catch()方法中進行操作。

在處理非同步操作時,Promise非常強大且易於使用。它可以簡化程式碼並提高可讀性。不要猶豫,嘗試使用Promise來處理非同步操作。

什麼是async/await? 如何使用async/await?

async/await 是一種用於簡化非同步操作的語法特性。它基於 Promise 物件,利用 async 和 await 關鍵字簡化 Promise 的鏈式呼叫和錯誤處理,使非同步程式碼看起來像同步程式碼。

async 表示一個函數是非同步的,並返回一個 Promise 物件。await 表示等待 Promise 物件的解決,並返回 Promise 物件的解決值。使用 async/await 可以避免回撥地獄,提高程式碼的可讀性和可維護性。

使用 async/await 的基本流程如下:

1.定義一個非同步函數,使用 async 關鍵字標記。

2.在函數中使用 await 關鍵字等待 Promise 物件處理完成,返回 Promise 物件的解決值。

3.處理 Promise 物件的錯誤,使用 try...catch 捕獲非同步操作的錯誤。

例如:

 

async function getData() {
  try {
    let response = await fetch('https://jsonplaceholder.typicode.com/users');
    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.log(error);
  }
}

getData();

在上面的例子中,我們定義一個非同步函數`getData`,使用 await 關鍵字等待 fetch() 方法返回的 Promise 物件處理完成,然後再使用 await 關鍵字等待 response 物件的 json() 方法返回的 Promise 物件處理完成,最後將返回的 JSON 資料輸出。同時使用 try...catch 捕獲非同步操作的錯誤,便於進行錯誤處理。

使用 async/await 可以大大簡化 Promise 的鏈式呼叫和錯誤處理,提高程式碼的可讀性和可維護性。但需要注意的是,await 只能在 async 函數內部使用,否則會報語法錯誤。