跳至主要内容

Javascript 的函式與閉包

Function(函式)

function 的宣告

在 JavaScript 中有兩種宣告函式的方式,分別是使用傳統的 function 和 ES6 中新增的 Arrow function(箭頭函數),語法範例如下:

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

// arrow function
const add = (a, b) => {
return a + b;
};

Arrow Function

Arrow function 相較於傳統的函式宣告方式,在某些情境下具有簡化的優點:

  1. 簡潔的語法: 像前面出現的 add(),如果箭頭函數只包含一個表達式,可以省略 {}return,這樣的寫法稱之為 implicit return(隱式返回)。

    // explicit return
    const add = (a, b) => {
    return a + b;
    };

    // implicit return
    const add = (a, b) => a + b;
  2. 返回 object literal: 使用 Arrow Function 除了可以更簡潔地返回變數或字串,有時候在處理物件時也需要返回 object literal 的形式。

    傳統的寫法會是

    function createPerson(name, age) {
    return {
    name: name,
    age: age
    };
    }

    const person = createPerson("John", 30);
    console.log(person); // { name: 'John', age: 30 }

    使用箭頭函數的話可以寫成

    // explicit return
    const createPerson = (name, age) => ({
    name: name,
    age: age
    });

    // implicit return
    const createPerson = (name, age) => ({ name, age });
    • 注意:返回 object literal 時,由於 {} 會是函數主體,所以需要將 object literal 包在 () 中。

Closure(閉包)

閉包是 JavaScript 中的一個重要概念,像前面的例子有提到使用函式時可以返回變數、字串、物件,那當然也可以返回函式,而當函式返回另一個函式時,就會涉及到 Closure 的概念。 (以下範例皆使用 ES6 的 Arrow Function 語法)

  • 以下是 Closure 的簡單範例,可以看到 createHelloWorld() 這個函式會返回另一個函式。
  • 可以把返回的函式 assign 給 f,接著使用 f() 就可以 print 出字串。
const createHelloWorld = () => {
return () => {
return "Hello World"
}
}
// 以上可以進一步簡化成
// const createHelloWorld = () => () => "Hello World";

const f = createHelloWorld();
console.log(f()); // "Hello World"
  • Closure 有個特性是內部函數可以使用外部函數的變數,這個特性稱為 Capturing(捕獲)
  • 以下範例中內部函數能捕獲外部函數中的 counterValue 變數。
const createCounter = (n) => {
let counterValue = n;

return () => {
const currentValue = counterValue;
counterValue += 1;
return currentValue;
};
};

const counter = createCounter(10)
console.log(counter()) // 10
console.log(counter()) // 11
console.log(counter()) // 12

LeetCode 中的 Closure 題目

以下都是 Easy 難度的 JavaScript Closure 題目

2704. To Be Or Not To Be

Write a function expect that helps developers test their code. It should take in any value val and return an object with the following two functions.

  • toBe(val) accepts another value and returns true if the two values === each other. If they are not equal, it should throw an error "Not Equal".
  • notToBe(val) accepts another value and returns true if the two values !== each other. If they are equal, it should throw an error "Equal".

解題:

根據前面有提過的宣告函式時回傳的方式,與基礎的 Closure 回傳函式的概念,可以先寫出程式的架構大致如下:

const expect = (val) => ({
toBe: (compareVal) => {
// if ...
},
notToBe: (compareVal) => {
// if ...
},
});

接著依據題目的敘述, toBe 和 notToBe 可以看作是用於檢查條件是否符合預期的斷言(assertions)。

const expect = (val) => ({
toBe: (compareVal) => {
if (val === compareVal) {
return true;
} else {
throw new Error("Not Equal");
}
},
notToBe: (compareVal) => {
if (val === compareVal) {
throw new Error("Equal");
} else {
return true;
}
}
})

console.log(expect(5).toBe(5)) // true
console.log(expect(5).notToBe(5)) // throws "Equal"

2665. Counter II

Write a function createCounter. It should accept an initial integer init. It should return an object with three functions.

The three functions are:

  • increment() increases the current value by 1 and then returns it.
  • decrement() reduces the current value by 1 and then returns it.
  • reset() sets the current value to init and then returns it.

一樣可以先寫出程式的架構大致如下:

const createCounter = (init) => {
let currentValue = init;

return ({
increment: () => {
// increases the current value by 1 and then returns it
},
decrement: () => {
// reduces the current value by 1 and then returns it
},
reset: () => {
// sets the current value to init and then returns it
},
})
};

接著依據題目的敘述來補完

const createCounter = (init) => {
let currentValue = init;

return ({
increment: () => {
currentValue += 1
return currentValue
},
decrement: () => {
currentValue -= 1
return currentValue
},
reset: () => {
currentValue = init;
return currentValue
},
})
};

const counter = createCounter(5)
console.log(counter.increment()) // 6
console.log(counter.reset()) // 5
console.log(counter.decrement()) // 4