Understanding JavaScript Closures: A Practical Approach - Lee-hyuna/33-js-concepts-kr GitHub Wiki

Understanding JavaScript Closures: A Practical Approach

๋ฒˆ์—ญ : https://scotch.io/tutorials/understanding-javascript-closures-a-practical-approach

What is a JavaScript closure?

A JavaScript Closure๋Š” ์™ธ๋ถ€ ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„ ๋ฐ–์—์„œ ์‹คํ–‰ ํ–ˆ์„ ๋•Œ์—๋„ ๋‚ด๋ถ€ ํ•จ์ˆ˜๊ฐ€ ์™ธ๋ถ€ ํ•จ์ˆ˜์˜ ๋ฉค๋ฒ„๋ฅผ ์ ‘๊ทผ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ ์ž…๋‹ˆ๋‹ค. lexical scope

๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๋Š” ๊ธฐ๋Šฅ๊ณผ ๋ฒ”์œ„๋ฅผ ์ œ์™ธํ•˜๊ณ  ํด๋กœ์ €์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐ ํ•  ์—ฌ์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

Scope in JavaScript

์Šค์ฝ”ํ”„๋Š”๋Š” ํ”„๋กœ๊ทธ๋žจ์— ์ •์˜ ๋œ ๋ณ€์ˆ˜์˜ ๊ฐ€์‹œ์„ฑ ๋ฒ”์œ„๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. JavaScript์—์„œ ์Šค์ฝ”ํ”„๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ try-catch ๋ธ”๋ก, ํ•จ์ˆ˜, ์ค‘๊ด„ํ˜ธ๊ฐ€ ์žˆ๋Š” let ํ‚ค์›Œ๋“œ์ž…๋‹ˆ๋‹ค. ์ „์—ญ ๋ฒ”์œ„์™€ ๋กœ์ปฌ ๋ฒ”์œ„์˜ ๋‘ ๊ฐ€์ง€ ๋ฒ”์œ„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

var initialBalance = 0 // Global Scope

function deposit (amount) {
  /**
   * Local Scope
   * Code here has access to anything declared in the global scope
   */
  var newBalance = parseInt(initialBalance) + parseInt(amount)
  return newBalance
}

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ๊ฐ ํ•จ์ˆ˜๋Š” ์ž์‹ ๋งŒ์˜ local ์Šค์ฝ”ํ”„๋ฅผ ์„ ์–ธํ• ๋•Œ ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค.

์ด๋Š” ํ•จ์ˆ˜์˜ ๋กœ์ปฌ ๋ฒ”์œ„ ๋‚ด์—์„œ ์„ ์–ธ ๋œ ๊ฒƒ์€ ์™ธ๋ถ€์—์„œ ์•ก์„ธ์Šค ํ•  ์ˆ˜ ์—†๋‹ค๋Š”๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”

var initialBalance = 300 // Variable declared in the Global Scope

function withdraw (amount) {
  var balance // Variable declared in function scope

  balance = parseInt(initialBalance) - parseInt(amount)
  return balance
}
console.log(initialBalance) // Will output initialBalance value as it is declared in the global scope
console.log(balance) // ReferenceError: Can't find variable: balance

Lexical Scope

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ Lexical Scope ๋Š” ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค. ๋ณ€์ˆ˜์˜ ์Šค์ฝ”ํ”„๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋ณ€์ˆ˜๊ฐ€ ์ •์˜ ๋œ ์ฝ”๋“œ ๋ธ”๋ก ๋‚ด์—์„œ๋งŒ ํ˜ธ์ถœ/์ฐธ์กฐ ๋  ์ˆ˜ ์žˆ๋„๋กํ•ฉ๋‹ˆ๋‹ค.

์ฃผ๋ณ€ ํ•จ์ˆ˜ ๋ธ”๋ก ์•ˆ์— ์„ ์–ธ ๋œ ํ•จ์ˆ˜๋Š” ์ฃผ๋ณ€ ํ•จ์ˆ˜์˜ ์–ดํœ˜ ๋ฒ”์œ„์—์žˆ๋Š” ๋ณ€์ˆ˜์— ์•ก์„ธ์Šค ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

var initialBalance = 300 // Global Scope

function withdraw (amount) {
  /**
   * Local Scope
   * Code here has access to anything declared in the global scope
   */
  var balance = parseInt(initialBalance) - parseInt(amount)

  const actualBalance = (function () {
    const TRANSACTIONCOST = 35
    return balance - TRANSACTIONCOST /**
     * Accesses balance variable from the lexical scope
     */
  })() // Immediately Invoked Function expression. IIFE

  // console.log(TRANSACTIONCOST) // ReferenceError: Can't find variable: TRANSACTIONCOST
  return actualBalance
}

ํ•จ์ˆ˜ ์™ธ๋ถ€์—์„œ ๋‚ด๋ถ€ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋‘˜๋Ÿฌ์‹ธ์ธ ํ•จ์ˆ˜์˜ ๋ณ€์ˆ˜๋“ค์„ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์œ ์ง€๊ฐ€ ๋˜๋ฉด JavaScript ํด๋กœ์ €๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

function person () {
  var name = 'Paul'  // Local variable

  var actions = {
    speak: function () {
    //  new function scope
      console.log('My name is ', name) /**
      * Accessing the name variable from the outer function scope (lexical scope)
      */
    }
  } // actions object with a function

  return actions /**
  * We return the actions object
  * We then can invoke the speak function outside this scope
  */
}

person().speak() // Inner function invoked outside its lexical Scope

ํด๋กœ์ €๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์™ธ๋ถ€ ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ๋ฅผ ์ˆจ๊ธฐ๊ณ  ์œ ์ง€ํ•˜๋ฉด์„œ ๊ณต์šฉ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋…ธ์ถœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ผ๋ถ€ JavaScript ๋””์ž์ธ ํŒจํ„ด์€ ํด๋กœ์ €๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Module Pattern

์ด๋Ÿฌํ•œ ์ž˜ ๊ตฌํ˜„ ๋œ ํŒจํ„ด ์ค‘ ํ•˜๋‚˜๋Š” ๋ชจ๋“ˆ ํŒจํ„ด์ด๋ฉฐ,์ด ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐœ์ธ, ๊ณต์šฉ ๋ฐ ๊ถŒํ•œ์žˆ๋Š” ๋ฉค๋ฒ„๋ฅผ ์—๋ฎฌ๋ ˆ์ด์…˜ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

var Module = (function () {
  var foo = 'foo' // Private Property

  function addToFoo (bam) { // Private Method
    foo = bam
    return foo
  }

  var publicInterface = {
    bar: function () { // Public Method
      return 'bar'
    },
    bam: function () { // Public Method
      return addToFoo('bam') // Invoking the private method
    }
  }

  return publicInterface // Object will contain public methods
})()

Module.bar() // bar
Module.bam() // bam

์œ„์˜ ๋ชจ๋“ˆ ํŒจํ„ด ์ฝ”๋“œ์—์„œ ํด๋กœ์ €์˜ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ์™ธ๋ถ€์—์„œ ๋ฐ˜ํ™˜ ๊ฐ์ฒด์˜ ๊ณต์šฉ ๋ฉ”์„œ๋“œ ๋ฐ ์†์„ฑ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹คํ–‰ ์ปจํ…์ŠคํŠธ๊ฐ€ ์œ ์ง€๋˜์ง€๋งŒ ์™ธ๋ถ€ ๋ฒ”์œ„์—์„œ ์ˆจ๊ฒจ์ ธ ์žˆ์œผ๋ฏ€๋กœ ๋ชจ๋“  private ๋ฉค๋ฒ„๋“ค์€ ๊ณ„์† ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

More illustrations on Closures

ํ•จ์ˆ˜๋ฅผ setTimeout ๋˜๋Š” ๋ชจ๋“  ์ข…๋ฅ˜์˜ ์ฝœ๋ฐฑ์— ์ „๋‹ฌํ•  ๋•Œ ํ•จ์ˆ˜๋Š” ํด๋กœ์ €๋กœ ์ธํ•ด ์–ดํœ˜ ๋ฒ”์œ„๋ฅผ ์—ฌ์ „ํžˆ ๊ธฐ์–ตํ•ฉ๋‹ˆ๋‹ค.

function foo () {
  var bar = 'bar'
  setTimeout(function () {
    console.log(bar)
  }, 1000)
}

foo() // bar

ํด๋กœ์ €์™€ ๋ฃจํ”„์ผ๋•Œ

for (var i = 1; i <= 5; i++) {
  (function (i) {
    setTimeout(function () {
      console.log(i)
    }, i * 1000)
  })(i)
}
/**
* Prints 1 thorugh 5 after each second
* Closure enables us to remember the variable i
* An IIFE to pass in a new value of the variable i for each iteration
* IIFE (Immediately Invoked Function expression)
*/
for (let i = 1; i <= 5; i++) {
  (function (i) {
    setTimeout(function () {
      console.log(i)
    }, i * 1000)
  })(i)
}
/**
* Prints 1 through 5 after each second
* Closure enabling us to remember the variable i
* The let keyword rebinds the value of i for each iteration
*/

์ด์ œ ํด๋กœ์ €๋ฅผ ์ดํ•ดํ•˜๊ณ  ๋‹ค์Œ์„ ์ˆ˜ํ–‰ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋ณด์—ฌ ์ฃผ๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์ƒํ™ฉ์—์„œ ์‹๋ณ„
  • ์›ํ•˜๋Š”๋Œ€๋กœ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ์œ ์ง€
  • JavaScript ๋ชจ๋“ˆ ํŒจํ„ด์œผ๋กœ ์ฝ”๋“œ ๊ตฌํ˜„
  • ๋ช…ํ™•ํ•œ ์ดํ•ด์™€ ํ•จ๊ป˜ ์ฝ”๋“œ์—์„œ ํด๋กœ์ € ์‚ฌ์šฉ

๋‹ค์Œ ๋ฒˆ๊นŒ์ง€, ํ–‰๋ณตํ•œ ์ฝ”๋”ฉ.