JavaScript Scope and Closures - Lee-hyuna/33-js-concepts-kr GitHub Wiki
Hello@
์ค์ฝํ์ ํด๋ก์ ๋ ์๋ฐ์คํฌ๋ฆฝํธ์์ ์~์ฃผ ์ค์ํ ์ญํ ์ ํ์ฃ ? ๊ทธ๋ฐ๋ฐ ์ฒ์ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์ฒ์ ์์ํ ๋ ์ง์ง ์ดํดํ๊ธฐ๊ฐ ์ด๋ ค์ด ์์ ์ ๋๋ค! ์์, ์ด์ ์ค์ฝํ์ ํด๋ก์ ๊ฐ ๋ญ์ง ์ง๊ธ๋ถํฐ ๋นก์ธ๊ฒ ์ค๋ช ์ ํด๋๋ฆฌ๊ฒ ์ต๋๋ค
#Scope
์๋ฐ์คํฌ๋ฆฝํธ์ ์ค์ฝํ๋ ๋ณ์์ ์ ๊ทผํ ์ ์๋๋ฐ '๊ธ๋ก๋ฒ ์ค์ฝํ'์ '๋ก์ปฌ ์ค์ฝํ' ๋ฒ์๊ฐ ์๋ค.
#Global Scope
๋ชจ๋ ๋ณ์๊ฐ function ์ด๋ ์ค๊ดํธ {}
๋ก ๊ตฌ์ฑ๋ ๊ฐ์ฒด ์ธ๋ถ์ ์ ์ธ๋์์ ๋ global scope๋ผ๊ณ ํ๋ค.
์ด๊ฑด ์๋ฐ์คํฌ๋ฆฝํธ์์๋ง ํด๋นํ๋ค. Node.js์์๋ ๊ธ๋ก๋ฒ ์ค์ฝํ๊ฐ ๋ค๋ฅด๊ฒ ์ ์ธ์ด ๋์ง๋ง, ์ด ์ฑํฐ์์ Node.js๋ฅผ ํ์ง ์์ผ๋๊น ํจ์ฐ~ ๐
const globalVariable = 'some value'
๊ธ๋ก๋ฒ ๋ณ์๋ก ์ ์ธ์ด ๋์์ผ๋ฉด ์ธ์ , ์ด๋์๋ ์ฌ์ฉํ ์ ์๋ค.
const hello = 'Hello CSS-Tricks Reader!'
function sayHello () {
console.log(hello)
}
console.log(hello) // 'Hello CSS-Tricks Reader!'
sayHello() // 'Hello CSS-Tricks Reader!'
์ ์ญ ๋ฒ์์์ ๋ณ์๋ฅผ ์ ์ธํ ์ ์์ง๋ง, ๊ทธ๋ฌ์ง ์๋ ๊ฒ์ด ์ข๋ค. ๋ ๊ฐ ์ด์์ ๋ณ์๋ฅผ ๋์ผํ ์ด๋ฆ์ผ๋ก ๋ช
๋ช
ํ๋ ์ถฉ๋์ ๊ฐ๋ฅ์ฑ์ด ์๊ธฐ ๋๋ฌธ์ด๋ค.
const
, let
์ผ๋ก ์ ์ธํ๋ค๋ฉด ์ด๋ฆ ์ถฉ๋์ด ์ผ์ด๋ ๋๋ง๋ค ์ค๋ฅ๋ฅผ ๋ฐ์ ๊ฒ์ด๋ค. ์ด๋ ๊ฒ ํ์ง ๋ง์ธ์~!
// Don't do this!
let thing = 'something'
let thing = 'something else' // Error, thing has already been declared
var
๋ฅผ ์ ์ธํ๊ณ ๋ ๋ฒ์งธ ๋ณ์๋ ์ ์ธ๋ ํ์ ์ฒซ๋ฒ์งธ ๋ณ์๋ฅผ ๋ฎ์ด์ด๋ค. ์ด๊ฒ์ ๋ํ ์ฝ๋๋ฅผ ๋๋ฒ๊น
ํ๊ธฐ ์ด๋ ต๊ฒ ๋ง๋ค๊ธฐ ๋๋ฌธ์ ๋ฐ๋์งํ์ง ์๋ค.
// Don't do this!
var thing = 'something'
var thing = 'something else' // perhaps somewhere totally different in your code
console.log(thing) // 'something else'
๋ฐ๋ผ์ ํญ์ ๊ธ๋ก๋ฒ ๋ณ์๊ฐ ์๋๋ผ ๋ก์ปฌ ๋ณ์๋ฅผ ์ ์ธํด์ผ ํ๋ค.
#Local Scope
์ฝ๋์ ํน์ ๋ถ๋ถ์์๋ง ์ฌ์ฉํ ์ ์๋ ๋ณ์๋ ๋ก์ปฌ ๋ฒ์์ ์๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผํ๋ค. ์ด๋ฌํ ๋ณ์๋ฅผ local scope๋ผ๊ณ ๋ ํ๋ค.
์๋ฐ์คํฌ๋ฆฝํธ์๋ function scope์ block scope๋ผ๋ ๋ ์ข ๋ฅ์ local scope๊ฐ ์๋ค. ๋จผ์ function scope๋ถํฐ ์ด์ผ๊ธฐํด ๋ด ์๋ค.
Function scope
ํจ์์ ๋ณ์๋ฅผ ์ ์ธํ ๋๋ ํจ์ ๋ด์์๋ง ์ด ๋ณ์์ ์ก์ธ์คํ ์ ์๋ค. ํจ์ ๋ฐ์์๋ ์ ๊ทผ์ ํ ์ ์๋ค.
function sayHello () {
const hello = 'Hello CSS-Tricks Reader!'
console.log(hello)
}
sayHello() // 'Hello CSS-Tricks Reader!'
console.log(hello) // Error, hello is not defined
Block scope
const
์ let
์ ์ค๊ดํธ{}
์์์๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
์๋์ ์์์๋ hello
๊ฐ ์ค๊ดํธ ์์์์ ์ค์ฝํ๋ฅผ ๊ฐ์ง๊ณ ์๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
{
const hello = 'Hello CSS-Tricks Reader!'
console.log(hello) // 'Hello CSS-Tricks Reader!'
}
console.log(hello) // Error, hello is not defined
(์๋ฌต์ ๋ฆฌํด์ด ์๋ ํ์ดํ ํจ์๋ฅผ ์ฌ์ฉํ์ง ์๋ ํ) ์ค๊ดํธ๋ฅผ ์ฌ์ฉํ์ฌ ํจ์๋ฅผ ์ ์ธํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ธ๋ก ์ค์ฝํ๋ ํจ์ ์ค์ฝํ์ ํ์ ์งํฉ์ด๋ค.
#Function hoisting and scopes
ํจ์๋ ํจ์ ์ ์ธ๊ณผ ํจ๊ป ์ ์ธ๋ ๋ ํญ์ ํ์ฌ ์ค์ฝํ์ ์ต์๋จ์ผ๋ก ์ฌ๋ผ๊ฐ๋ค. ๋ฐ๋ผ์ ์ด ๋ ๊ฐ์ง๋ ๋๋ฑํ๋ค.
// This is the same as the one below
sayHello()
function sayHello () {
console.log('Hello CSS-Tricks Reader!')
}
// This is the same as the code above
function sayHello () {
console.log('Hello CSS-Tricks Reader!')
}
sayHello()
ํจ์ ํํ์์ ์ฌ์ฉํ์ฌ ์ ์ธํ ๋ ํจ์๋ ํ์ฌ ์ค์ฝํ ์๋จ์ ์ฌ๋ฆฌ์ง ์๋๋ค.
sayHello() // Error, sayHello is not defined
const sayHello = function () {
console.log(aFunction)
}
์ด๋ฌํ ๋ ๊ฐ์ง ๋ณํ ๋๋ฌธ์ function ํธ์ด์คํ ์ ์ ์ฌ์ ์ผ๋ก ํผ๋์ ์ผ์ผํฌ ์ ์์ผ๋ฏ๋ก ์ฌ์ฉํด์๋ ์ ๋๋ค. function์ ์ฌ์ฉํ๊ธฐ ์ ์ ํญ์ function์ ์ ์ธํ์ญ์์ค.
#Functions do not have access to each other's scopes
ํจ์๋ ํ ํจ์๊ฐ ๋ค๋ฅธ ํจ์์ ์ฌ์ฉ๋ ์ ์์์๋ ๋ถ๊ตฌํ๊ณ ๊ทธ๊ฒ๋ค์ ๋ณ๋๋ก ์ ์ํ ๋ ์๋ก์ ๋ฒ์์ ์ ๊ทผํ ์ ์๋ค.
๋ค์ ์๋์ ์์ ๊ฐ์ด second
์์ firstFunctionVariable
๋ฅผ ์ฌ์ฉํ ์ ์๋ ๋ถ๋ถ์ ๋ณผ ์ ์๋ค.
function first () {
const firstFunctionVariable = `I'm part of first`
}
function second () {
first()
console.log(firstFunctionVariable) // Error, firstFunctionVariable is not defined
}
<br>
### ##Nested scopes
ํจ์๊ฐ ๋ค๋ฅธ ํจ์์ ์ ์๋ ๋, ๋ด๋ถ ํจ์๋ ์ธ๋ถ ํจ์์ ๋ณ์์ ์ ๊ทผํ ์ ์๋ค. ์ด๋ฌํ ํ๋์ ์ดํ์ ** lexical scoping**์ด๋ผ๊ณ ํ๋ค.<br>
๊ทธ๋ฌ๋ ์ธ์ธกํจ์๋ ๋ด์ธกํจ์์ ๋ณ์์ ์ ๊ทผํ ์ ์๋ค.
```js
function outerFunction () {
const outer = `I'm the outer function!`
function innerFunction() {
const inner = `I'm the inner function!`
console.log(outer) // I'm the outer function!
}
console.log(inner) // Error, inner is not defined
}
์ค์ฝํ๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง๋ฅผ ์๊ฐํํ๊ธฐ ์ํด ์๋ฅผ ๋ค์ด๋ณด์ ์ผ๋ฐฉ ์ ๋ฆฌ๋ฅผ ์๋ฅผ ๋ค์๋ฉด, ๊ฒ์ ๋ณผ ์ ์์ง๋ง ๊ฒ์ ์ฌ๋์ด ๋ณผ ์ ์๋ค.
๋ฒ์ ๋ด์ ์ค์ฝํ๊ฐ ์๋ ๊ฒฝ์ฐ ์ฌ๋ฌ๊ฐ์ง ์ธต์ ์ผ๋ฐฉ์ ๋ฆฌ๋ฅผ ์์ํ ์ ์๋ค.
์ง๊ธ๊น์ง ์ค์ฝํ์ ๋ํ ๋ชจ๋ ๊ฒ์ ์ดํดํ๋ค๋ฉด ํด๋ก์ ๊ฐ ๋ฌด์์ธ์ง ์์๋ผ ์ค๋น๊ฐ ์ถฉ๋ถํ ๋์๋ค.
Closures
ํจ์ ๋ด์์ ๋ด๋ถ ํจ์๋ฅผ ์์ฑํ ๋๋ง๋ค ํด๋ก์ ๋ฅผ ์์ฑํ๋ค. ๋ด๋ถํจ์๋ ํด๋ก์ ๋ค. ํด๋ก์ ๋ ๋ณดํต ๋ฐํ๋๋ฏ๋ก ์ธ๋ถํจ์์ ๋ณ์๋ฅผ ์ฌ์ฉํ ์ ์๋ค. ํด๋ก์ ๋ฅผ ๊ณต๋ถํ๋ฉด ํญ์ ๋ณผ ์ ์๋ ์์์ด๋ค.
function outerFunction () {
const outer = `I see the outer variable!`
function innerFunction() {
console.log(outer)
}
return innerFunction
}
outerFunction()() // I see the outer variable!
์๋์์์ฒ๋ผ ๋ด๋ถํจ์๋ ๋ฆฌํด๋๋ฏ๋ก ๊ธฐ๋ฅ์ ์ ์ธํ๋ฉด์ ๋ฆฌํด ์์ฑํ์ฌ ์ฝ๋๋ฅผ ์ฝ๊ฐ ๋จ์ถํ ์๋ ์๋ค.
function outerFunction () {
const outer = `I see the outer variable!`
return function innerFunction() {
console.log(outer)
}
}
outerFunction()() // I see the outer variable!
ํด๋ก์ ๋ ์ธ๋ถํจ์์ ์๋ ๋ณ์์ ์ ๊ทผํ ์ ์๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ผ๋ก ๋ ๊ฐ์ง ์ฉ๋๋ก ์ฌ์ฉ๋๋ค.
- ์ฌ์ด๋ ์ดํํธ ์ปจํธ๋กค
- ๋ณ์๋ฅผ ์ํธํ ํ๋ ค๊ณ ํ ๋
#Controlling side effects with closures
์ฌ์ด๋ ์ดํํธ๋ ํจ์์ ๊ฐ์ ๋ฐํํ๋ ๊ฒ ์ธ์ ๋ค๋ฅธ ๊ฒ์ ํ ๋ ๋ฐ์ํ๋ค.
Ajax ์์ฒญ, ์๊ฐ ์ด๊ณผ ๋๋ console.log
์ ๊ฐ์ ๋ง์ ๊ฒ๋ค์ด ๋ถ์์ฉ์ผ ์ ์๋ค.
function (x) {
console.log('A console.log is a side effect!')
}
๋ณดํต ์ฌ์ด๋ ์ดํํธ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ํด๋ก์ ๋ฅผ ์ฌ์ฉํ ๋ Ajax๋ timeout์ฒ๋ผ ์ฝ๋ํ๋ฆ์ ํด์น ๊น๋ด ๊ฑฑ์ ์ ๋ง์ดํ๋ค.
์ํฉ์ ๋ ๋ช ํํ๊ฒ ํ๊ธฐ ์ํด ์ด ์ผ์ ์์๋ก ์ดํด๋ณด์.
์น๊ตฌ์ ์์ผ์ ์ํด ์ผ์ดํฌ๋ฅผ ๋ง๋ค๊ณ ์ถ๋ค๊ณ ํ์. ์ด ์ผ์ดํฌ๋ ๋ง๋๋ ๋ฐ 1์ด๊ฐ ๊ฑธ๋ฆฌ๋๊น 1์ด ํ์ ๋ก๊ทธ๋ฅผ Made a cake๋ผ๊ณ ์ฐ๋๋ค.
ES6 ํ์ดํ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ์๋ฅผ ๋ ์งง๊ณ ์ดํดํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค๊ณ ์๋ค.
function makeCake() {
setTimeout(_ => console.log(`Made a cake`), 1000)
}
์ผ์ดํฌ ๋ง๋ค๊ธฐ ๊ธฐ๋ฅ์ ์๊ฐ ์ด๊ณผ๋ผ๋ ๋ถ์์ฉ์ ๊ฐ์ง๊ณ ์๋ค.
๋ ๋์๊ฐ ๋น์ ์ด ๋น์ ์ ์น๊ตฌ๊ฐ ์ผ์ดํฌ์ ๋ง์ ์ ํํ๊ธฐ๋ฅผ ์ํ๋ค๊ณ ํ์. ๊ทธ๋ฌ๊ธฐ ์ํด์๋ makeCake
์ ํ๋ฏธ๋ฅผ ๋ํ ์ ์๋ค.
function makeCake(flavor) {
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000)
}
function์ ์คํํ ๋ ์ผ์ดํฌ์ ์ฌ๋ฃ๋ฅผ ๋ฃ์ ์ ์๋ค
makeCake('banana')
// Made a banana cake!
์ฌ๊ธฐ์ ๋ฌธ์ ๋ ๊ทธ ๋ง์ ์๊ณ ๋์ ๋ฐ๋ก ์ผ์ดํฌ๋ฅผ ๋ง๋ค๊ณ ์ถ์ง ์๋ค๋ ๊ฒ์ด๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, ์ผ์ดํฌ์ ๋ง์ ์ ์ฅํ๋ prepareCake
function์ ๋ง๋ค์๋ค. prepareCake
๋ด์ makeCake
๋ฅผ ํด๋ก์ ๋ก ๋ฆฌํดํ๋ค.
์ด๋๋ถํฐ ์ธ์ ๋ ์ง ์ํ๋ฉด ๋ฆฌํด๋๋ ๊ธฐ๋ฅ์ ํธ์ถํ ์ ์์ผ๋ฉฐ, ์ผ์ดํฌ๋ 1์ด ์์ ๋ง๋ค์ด์ง ๊ฒ์ด๋ค.
function prepareCake (flavor) {
return function () {
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000)
}
}
const makeCakeLater = prepareCake('banana')
// And later in your code...
makeCakeLater()
// Made a banana cake!
#Private variables with closures
์ง๊ธ๊น์ง ์๊ณ ์๋ฏ์ด ํจ์์ ์์ฑ๋๋ ๋ณ์๋ ํจ์ ๋ฐ์์ ์ ์ํ ์ ์๋ค. ์ ์์ด ์ ๋๊ธฐ ๋๋ฌธ์ private๋ณ์ ๋ผ๊ณ ๋ ๋ถ๋ฅธ๋ค. ๊ทธ๋ฐ๋ฐ ํน๋ณํ ๊ฒฝ์ฐ์ private๋ณ์์ ์ ๊ทผํด์ผํ ๋๊ฐ ์๋ค. ํด๋ก์ ธ์์ ์ด ๋ถ๋ถ์ ์์ฉํ ์ ์๋ค.
function secret (secretCode) {
return {
saySecretCode () {
console.log(secretCode)
}
}
}
const theSecret = secret('CSS Tricks is amazing')
theSecret.saySecretCode()
// 'CSS Tricks is amazing'
saySecretCode
๋ ์์ ์์์ ์๋์ ๋น๋ฐํจ์ ์ธ๋ถ์ ๋น๋ฐ์ฝ๋๋ฅผ ๋
ธ์ถ์ํค๋ ์ ์ผํ ๊ธฐ๋ฅ(ํด๋ก์ )์ด๋ค. ์ด๋ฐ ๊ธฐ๋ฅ์ privileged function๋ผ๊ณ ๋ ๋ถ๋ฆฐ๋ค.
#Debugging scopes with DevTools
์ต๊ทผ ์ฌ์ฉํ ์ค์ฝํ์ ๋ํด์ ํฌ๋กฌ์ด๋ ํ์ด์ดํญ์ค์ ๊ฐ๋ฐ์๋๊ตฌ์์ ๊ฐ๋จํ๊ฒ ๋๋ฒ๊น
์ ํ ์ ์๋ค.
์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์๋ ๋ ๊ฐ์ง๊ฐ ์๋ค.
์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ์ฝ๋์ debugger
ํค์๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด๋ค. ์ด๋ก ์ธํด ๋ธ๋ผ์ฐ์ ์์ JavaScript ์คํ์ด ์ผ์ ์ค์ง๋์ด ๋๋ฒ๊น
์ด ๊ฐ๋ฅํ๋ค.
function prepareCake (flavor) {
// Adding debugger
debugger
return function () {
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000)
}
}
const makeCakeLater = prepareCake('banana')
DevTools๋ฅผ ์ด๊ณ Chrome(๋๋ Firefox์ Debugger ํญ)์ Source(์์ค) ํญ์ผ๋ก ์ด๋ํ๋ฉด ์ฌ์ฉํ ์ ์๋ ๋ณ์๊ฐ ๋ํ๋๋ค.
debugger
ํค์๋๋ฅผ ํตํด ํด๋ก์ ๋ก ๋ฐํํ ์ ์๋ค. ์ด๋ฒ์๋ ์ค์ฝํ ๋ณ์๊ฐ ์ด๋ป๊ฒ ๋ณ๊ฒฝ๋๋์ง ํ์ธํด๋ณด์!
function prepareCake (flavor) {
return function () {
// Adding debugger
debugger
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000)
}
}
const makeCakeLater = prepareCake('banana')
์ด ๋๋ฒ๊น ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๋ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ๋ผ์ธ ๋ฒํธ๋ฅผ ํด๋ฆญํ์ฌ ์์ค(๋๋ ๋๋ฒ๊ฑฐ) ํญ์ ์ง์ ์ฝ๋์ ์ค๋จ์ ์ ์ถ๊ฐํ๋ ๊ฒ์ด๋ค.