함수
함수 (Functions)
함수는 JavaScript의 근본적인 구성 요소 중 하나다. JavaScript의 함수는 **프로시저(procedure)**와 유사하다 — 작업을 수행하거나 값을 계산하는 문장 집합이다. 하지만 프로시저가 함수로 인정받으려면 어떤 입력을 받고 입력과 출력 사이에 명백한 관계가 있는 출력을 반환해야 한다. 함수를 사용하려면 호출하려는 범위 어딘가에서 함수를 정의해야 한다.
자세한 내용은 JavaScript 함수에 대한 철저한 참조 장도 함께 보라.
본문
함수 정의하기 (Defining functions)
함수 선언 (Function declarations)
함수 정의(함수 선언 또는 함수 문장이라고도 함)는 function 키워드와 그 뒤에 오는 다음으로 구성된다.
- 함수의 이름.
- 괄호로 감싸고 쉼표로 구분된 함수의 매개변수 목록.
- 함수를 정의하는 JavaScript 문장들. 중괄호
{ /* … */ }로 감싼다.
예를 들어 다음 코드는 square라는 이름의 함수를 정의한다.
function square(number) {
return number * number;
}
square 함수는 number라는 매개변수 하나를 받는다. 함수는 매개변수(즉 number)를 자기 자신과 곱한 값을 반환하라는 하나의 문장으로 구성된다. return 문장은 함수가 반환하는 값, 즉 number * number를 지정한다.
매개변수는 본질적으로 값으로 함수에 전달된다. 그러므로 함수 본문 안의 코드가 전달된 매개변수에 완전히 새로운 값을 할당해도, 그 변경은 전역이나 그 함수를 호출한 코드에 반영되지 않는다.
객체를 매개변수로 전달하면, 함수가 객체의 속성을 변경하면 그 변경이 함수 밖에서도 보인다. 다음 예와 같다.
function myFunc(theObject) {
theObject.make = "Toyota";
}
const myCar = {
make: "Honda",
model: "Accord",
year: 1998,
};
console.log(myCar.make); // "Honda"
myFunc(myCar);
console.log(myCar.make); // "Toyota"
배열을 매개변수로 전달하면, 함수가 배열의 어떤 값을 변경하면 그 변경이 함수 밖에서도 보인다. 다음 예와 같다.
function myFunc(theArr) {
theArr[0] = 30;
}
const arr = [45];
console.log(arr[0]); // 45
myFunc(arr);
console.log(arr[0]); // 30
함수 선언과 표현식은 중첩될 수 있으며, 이는 범위 체인(scope chain)을 형성한다. 예:
function addSquares(a, b) {
function square(x) {
return x * x;
}
return square(a) + square(b);
}
자세한 내용은 함수 범위와 클로저를 보라.
함수 표현식 (Function expressions)
위의 함수 선언은 문법상 문장이지만, 함수는 **함수 표현식(function expression)**으로도 만들 수 있다.
그런 함수는 **익명(anonymous)**일 수 있다. 즉 이름을 가질 필요가 없다. 예를 들어 square 함수는 다음과 같이 정의될 수 있었다.
const square = function (number) {
return number * number;
};
console.log(square(4)); // 16
그러나 함수 표현식에 이름을 제공할 수도 있다. 이름을 제공하면 함수가 자기 자신을 참조할 수 있고, 디버거의 스택 트레이스에서 함수를 식별하기도 쉬워진다.
const factorial = function fac(n) {
return n < 2 ? 1 : n * fac(n - 1);
};
console.log(factorial(3)); // 6
함수 표현식은 함수를 다른 함수의 인자로 전달할 때 편리하다. 다음 예는 첫 인자로 함수를, 둘째 인자로 배열을 받아야 하는 map 함수를 정의한다. 그런 다음 함수 표현식으로 정의된 함수와 함께 호출된다.
function map(f, a) {
const result = new Array(a.length);
for (let i = 0; i < a.length; i++) {
result[i] = f(a[i]);
}
return result;
}
const numbers = [0, 1, 2, 5, 10];
const cubedNumbers = map(function (x) {
return x * x * x;
}, numbers);
console.log(cubedNumbers); // [0, 1, 8, 125, 1000]
JavaScript에서 함수는 조건에 기반해 정의될 수 있다. 예를 들어 다음 함수 정의는 num이 0과 같을 때만 myFunc를 정의한다.
let myFunc;
if (num === 0) {
myFunc = function (theObject) {
theObject.make = "Toyota";
};
}
여기서 설명한 대로 함수를 정의하는 것 외에도, eval()과 비슷하게 런타임에 문자열로부터 함수를 만들기 위해 Function 생성자를 사용할 수도 있다.
**메서드(method)**는 객체의 속성인 함수다. 객체와 메서드에 대해 더 읽으려면 Working with objects를 보라.
함수 호출하기 (Calling functions)
함수를 정의하는 것은 함수를 실행하는 것이 아니다. 정의는 함수에 이름을 붙이고 호출될 때 무엇을 할지 지정한다.
함수를 호출하는 것이 지정된 매개변수로 명시된 동작을 실제로 수행한다. 예를 들어 square 함수를 정의했다면 다음과 같이 호출할 수 있다.
square(5);
앞의 문장은 인수 5로 함수를 호출한다. 함수는 문장들을 실행하고 값 25를 반환한다.
함수는 호출될 때 범위 안에 있어야 하지만, 함수 선언은 **호이스팅(hoisted)**될 수 있다(호출 뒤의 코드에 나타날 수 있다). 함수 선언의 범위는 그것이 선언된 함수(또는 최상위에서 선언됐다면 전체 프로그램)다.
함수의 인수는 문자열과 숫자로 제한되지 않는다. 함수에 전체 객체를 전달할 수 있다. showProps() 함수(Working with objects에서 정의)는 객체를 인수로 받는 함수의 예다.
함수는 자기 자신을 호출할 수 있다. 예를 들어 다음은 팩토리얼을 재귀적으로 계산하는 함수다.
function factorial(n) {
if (n === 0 || n === 1) {
return 1;
}
return n * factorial(n - 1);
}
그러면 1부터 5까지의 팩토리얼을 다음과 같이 계산할 수 있다.
console.log(factorial(1)); // 1
console.log(factorial(2)); // 2
console.log(factorial(3)); // 6
console.log(factorial(4)); // 24
console.log(factorial(5)); // 120
함수를 호출하는 다른 방법들도 있다. 함수를 동적으로 호출해야 하거나, 함수의 인수 개수가 변하거나, 함수 호출의 컨텍스트를 런타임에 결정된 특정 객체로 설정해야 하는 경우가 종종 있다.
함수 자체는 객체이며, 그 객체들은 메서드를 가진다. (Function 객체 참조.) 이 목표를 달성하는 데 call()과 apply() 메서드를 사용할 수 있다.
함수 호이스팅 (Function hoisting)
아래 예를 보자.
console.log(square(5)); // 25
function square(n) {
return n * n;
}
이 코드는 square() 함수가 선언되기 전에 호출되는데도 오류 없이 실행된다. JavaScript 인터프리터가 함수 선언 전체를 현재 범위의 맨 위로 호이스팅하기 때문이다. 따라서 위 코드는 다음과 같다.
// 모든 함수 선언은 효과적으로 범위의 맨 위에 있다
function square(n) {
return n * n;
}
console.log(square(5)); // 25
함수 호이스팅은 함수 선언에서만 동작하며 함수 표현식에서는 동작하지 않는다. 다음 코드는 동작하지 않는다.
console.log(square(5)); // ReferenceError: Cannot access 'square' before initialization
const square = function (n) {
return n * n;
};
재귀 (Recursion)
함수는 자기 자신을 참조하고 호출할 수 있다. 함수 표현식이나 선언의 이름, 또는 함수 객체를 가리키는 범위 내의 어떤 변수를 통해서든 참조할 수 있다. 예를 들어 다음 함수 정의를 보자.
const foo = function bar() {
// 여기 문장
};
함수 본문 안에서 함수 자신을 bar 또는 foo로 참조할 수 있고, bar() 또는 foo()로 자기 자신을 호출할 수 있다.
자기 자신을 호출하는 함수를 **재귀 함수(recursive function)**라고 한다. 어떤 면에서 재귀는 루프와 유사하다. 둘 다 같은 코드를 여러 번 실행하고, 둘 다 조건이 필요하다(무한 루프, 또는 이 경우 무한 재귀를 피하기 위해).
예를 들어 다음 루프를 보자.
let x = 0;
// "x < 10"이 루프 조건
while (x < 10) {
// 작업 수행
x++;
}
이것은 재귀 함수 선언과 그 함수 호출로 변환될 수 있다.
function loop(x) {
// "x >= 10"이 종료 조건 ("!(x < 10)"와 동등)
if (x >= 10) {
return;
}
// 작업 수행
loop(x + 1); // 재귀 호출
}
loop(0);
그러나 일부 알고리즘은 단순한 반복 루프로는 풀 수 없다. 예를 들어 트리 구조(예: DOM)의 모든 노드를 얻는 것은 재귀로 더 쉽다.
function walkTree(node) {
if (node === null) {
return;
}
// node로 무언가 수행
for (const child of node.childNodes) {
walkTree(child);
}
}
loop 함수와 비교해, 여기서 각 재귀 호출은 그 자체로 많은 재귀 호출을 만든다.
재귀 알고리즘은 어떤 것이든 비재귀로 변환하는 것이 가능하지만, 논리가 종종 훨씬 더 복잡하고 그러려면 스택을 사용해야 한다.
사실 재귀 자체도 스택을 사용한다: 함수 스택(function stack). 스택 같은 동작은 다음 예에서 볼 수 있다.
function foo(i) {
if (i < 0) {
return;
}
console.log(`begin: ${i}`);
foo(i - 1);
console.log(`end: ${i}`);
}
foo(3);
// 기록:
// begin: 3
// begin: 2
// begin: 1
// begin: 0
// end: 0
// end: 1
// end: 2
// end: 3
즉시 실행 함수 표현식 (IIFE)
**즉시 실행 함수 표현식(Immediately Invoked Function Expression, IIFE)**은 표현식으로 정의된 함수를 직접 호출하는 코드 패턴이다. 다음과 같이 보인다.
(function () {
// 무언가 수행
})();
const value = (function () {
// 무언가 수행
return someValue;
})();
함수를 변수에 저장하는 대신 함수가 즉시 호출된다. 이는 거의 함수 본문만 쓰는 것과 동등하지만 몇 가지 독특한 이점이 있다.
- 변수의 추가 범위를 만들어, 변수를 유용한 곳에 국한시키는 데 도움이 된다.
- 이제 문장들의 시퀀스가 아니라 표현식이다. 이는 변수를 초기화할 때 복잡한 계산 논리를 쓸 수 있게 해준다.
자세한 내용은 IIFE 용어집 항목을 보라.
함수 범위와 클로저 (Function scopes and closures)
함수는 변수의 범위를 형성한다. 즉 함수 안에서 정의된 변수는 함수 밖 어디에서도 접근할 수 없다. 함수 범위는 모든 상위 범위로부터 상속받는다. 예를 들어 전역 범위에서 정의된 함수는 전역 범위에서 정의된 모든 변수에 접근할 수 있다. 다른 함수 안에 정의된 함수는 부모 함수에 정의된 모든 변수와, 부모 함수가 접근할 수 있는 다른 어떤 변수에도 접근할 수 있다. 반대로 부모 함수(그리고 다른 부모 범위)는 내부 함수 안에 정의된 변수와 함수에는 접근할 수 없다. 이는 내부 함수의 변수에 일종의 **캡슐화(encapsulation)**를 제공한다.
// 다음 변수들은 전역 범위에서 정의됨
const num1 = 20;
const num2 = 3;
const name = "Chamakh";
// 이 함수는 전역 범위에서 정의됨
function multiply() {
return num1 * num2;
}
console.log(multiply()); // 60
// 중첩 함수 예
function getScore() {
const num1 = 2;
const num2 = 3;
function add() {
return `${name} scored ${num1 + num2}`;
}
return add();
}
console.log(getScore()); // "Chamakh scored 5"
클로저 (Closures)
함수 본문을 **클로저(closure)**라고도 부른다. 클로저는 어떤 변수를 참조하는 소스 코드의 조각(가장 흔히 함수)이며, 그 변수들이 선언된 범위가 종료된 후에도 클로저는 그 변수들을 "기억한다".
클로저는 보통 중첩 함수로 설명된다. 그것들이 부모 범위의 수명을 넘어 변수를 기억한다는 것을 보여주기 위해서다. 그러나 사실 중첩 함수는 필수가 아니다. 기술적으로 말하면 JavaScript의 모든 함수는 클로저를 형성한다 — 일부는 아무것도 포착하지 않을 뿐이며, 클로저가 반드시 함수일 필요도 없다. 유용한 클로저의 핵심 요소는 다음과 같다.
- 어떤 변수나 함수를 정의하는 부모 범위. 명확한 수명이 있어야 한다. 즉 어떤 시점에 실행을 마쳐야 한다. 전역 범위가 아닌 모든 범위가 이 요구를 충족한다. 여기에는 블록, 함수, 모듈 등이 포함된다.
- 부모 범위 안에 정의된 내부 범위로, 부모 범위에 정의된 어떤 변수나 함수를 참조한다.
- 내부 범위가 부모 범위의 수명을 넘어 생존한다. 예를 들어 부모 범위 밖에 정의된 변수에 저장되거나, 부모 범위(함수라면)에서 반환된다.
그러면 부모 범위 밖에서 함수를 호출할 때, 부모 범위가 이미 실행을 마쳤더라도 부모 범위에 정의된 변수나 함수에 여전히 접근할 수 있다.
다음은 클로저의 전형적인 예다.
// 외부 함수가 "name"이라는 변수를 정의
const pet = function (name) {
const getName = function () {
// 내부 함수는 외부 함수의 "name" 변수에 접근할 수 있다
return name;
};
return getName; // 내부 함수를 반환해 외부 범위에 노출
};
const myPet = pet("Vivie");
console.log(myPet()); // "Vivie"
위 코드보다 훨씬 더 복잡할 수 있다. 외부 함수의 내부 변수를 조작하는 메서드를 포함한 객체를 반환할 수 있다.
const createPet = function (name) {
let sex;
const pet = {
// setName(newName)은 이 문맥에서 setName: function (newName)과 동등
setName(newName) {
name = newName;
},
getName() {
return name;
},
getSex() {
return sex;
},
setSex(newSex) {
if (
typeof newSex === "string" &&
(newSex.toLowerCase() === "male" || newSex.toLowerCase() === "female")
) {
sex = newSex;
}
},
};
return pet;
};
const pet = createPet("Vivie");
console.log(pet.getName()); // Vivie
pet.setName("Oliver");
pet.setSex("male");
console.log(pet.getSex()); // male
console.log(pet.getName()); // Oliver
위 코드에서 외부 함수의 name 변수는 내부 함수에 접근 가능하고, 내부 함수를 통하지 않고는 내부 변수에 접근할 다른 방법이 없다. 내부 함수의 내부 변수는 외부 인수와 변수의 안전한 저장소 역할을 한다. 그것들은 내부 함수가 작업할 "지속적(persistent)"이고 "캡슐화된(encapsulated)" 데이터를 보유한다. 함수는 변수에 할당되거나 이름을 가질 필요조차 없다.
const getCode = (function () {
const apiCode = "0]Eal(eh&2"; // 외부인이 수정하지 못하게 하고 싶은 코드…
return function () {
return apiCode;
};
})();
console.log(getCode()); // "0]Eal(eh&2"
위 코드에서 IIFE 패턴을 사용한다. 이 IIFE 범위 안에는 두 값이 존재한다: 변수 apiCode와 반환되어 변수 getCode에 할당되는 이름 없는 함수. apiCode는 반환된 이름 없는 함수의 범위에 있지만 프로그램의 다른 어떤 부분의 범위에도 없으므로, getCode 함수를 통하지 않고는 apiCode의 값을 읽을 방법이 없다.
다중 중첩 함수 (Multiply-nested functions)
함수는 여러 번 중첩될 수 있다. 예를 들어:
- 함수 (A)가 함수 (B)를 포함하고, 그 (B)는 함수 (C)를 포함한다.
- 함수 B와 C는 둘 다 여기서 클로저를 형성한다. 그래서 B는 A에, C는 B에 접근할 수 있다.
- 추가로, C는 B에 접근할 수 있고 B는 A에 접근할 수 있으므로, C도 A에 접근할 수 있다.
따라서 클로저는 여러 범위를 포함할 수 있다. 그것들은 자신을 포함하는 함수의 범위를 재귀적으로 포함한다. 이를 **범위 체이닝(scope chaining)**이라고 한다. 다음 예를 보자.
function A(x) {
function B(y) {
function C(z) {
console.log(x + y + z);
}
C(3);
}
B(2);
}
A(1); // 6을 기록 (1 + 2 + 3)
이 예에서 C는 B의 y와 A의 x에 접근한다. 가능한 이유는:
- B는 A를 포함하는 클로저를 형성한다(즉 B는 A의 인수와 변수에 접근할 수 있다).
- C는 B를 포함하는 클로저를 형성한다.
- C의 클로저가 B를 포함하고 B의 클로저가 A를 포함하므로, C의 클로저도 A를 포함한다. 이는 C가 B와 A의 인수와 변수 모두에 접근할 수 있다는 뜻이다. 다시 말해 C는 B와 A의 범위를 그 순서대로 체이닝한다.
그러나 그 반대는 성립하지 않는다. A는 C에 접근할 수 없다. A는 B의 어떤 인수나 변수에도 접근할 수 없는데, C는 B의 변수이기 때문이다. 따라서 C는 B에게만 사적인 채로 남는다.
이름 충돌 (Name conflicts)
클로저의 범위에서 두 인수나 변수가 같은 이름을 가지면 **이름 충돌(name conflict)**이 발생한다. 더 중첩된 범위가 우선한다. 그래서 가장 안쪽 범위가 가장 높은 우선순위를, 가장 바깥쪽 범위가 가장 낮은 우선순위를 가진다. 이것이 범위 체인이다. 체인의 첫 번째가 가장 안쪽 범위, 마지막이 가장 바깥쪽 범위다. 다음을 보자.
function outside() {
const x = 5;
function inside(x) {
return x * 2;
}
return inside;
}
console.log(outside()(10)); // 20 (10이 아니라)
이름 충돌은 return x * 2 문장에서 inside의 매개변수 x와 outside의 변수 x 사이에서 발생한다. 여기 범위 체인은 inside => outside => global object다. 따라서 inside의 x가 outside의 x보다 우선하고, 10(outside의 x)이 아니라 20(inside의 x)이 반환된다.
arguments 객체 사용하기 (Using the arguments object)
함수의 인수는 **배열과 유사한 객체(array-like object)**에 유지된다. 함수 안에서 전달된 인수를 다음과 같이 주소 지정할 수 있다.
arguments[i];
여기서 i는 0에서 시작하는 인수의 서수 번호다. 따라서 함수에 전달된 첫 인자는 arguments[0]이다. 총 인수 개수는 arguments.length로 나타낸다.
arguments 객체를 사용하면, 함수가 공식적으로 선언한 것보다 더 많은 인수로 함수를 호출할 수 있다. 이는 함수에 전달될 인수가 몇 개인지 미리 모를 때 종종 유용하다. arguments.length로 실제로 전달된 인수 개수를 알아낸 다음, arguments 객체로 각 인수에 접근할 수 있다.
예를 들어 여러 문자열을 연결하는 함수를 생각해보자. 함수의 유일한 공식 인수는 연결할 항목들을 구분하는 문자를 지정하는 문자열이다. 함수는 다음과 같이 정의된다.
function myConcat(separator) {
let result = ""; // 목록 초기화
// arguments를 순회
for (let i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}
이 함수에 얼마든지 많은 인수를 전달할 수 있고, 함수는 각 인수를 문자열 "목록"으로 연결한다.
console.log(myConcat(", ", "red", "orange", "blue"));
// "red, orange, blue, "
console.log(myConcat("; ", "elephant", "giraffe", "lion", "cheetah"));
// "elephant; giraffe; lion; cheetah; "
console.log(myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley"));
// "sage. basil. oregano. pepper. parsley. "
참고:
arguments변수는 "배열과 유사(array-like)"하지만 배열은 아니다. 번호가 매겨진 인덱스와length속성을 가진다는 점에서 배열과 유사하다. 그러나 배열 조작 메서드를 모두 가지지는 않는다. 자세한 내용은 JavaScript 참조의Function객체를 보라.
함수 매개변수 (Function parameters)
두 가지 특별한 종류의 매개변수 문법이 있다: **기본 매개변수(default parameters)**와 나머지 매개변수(rest parameters).
기본 매개변수 (Default parameters)
JavaScript에서 함수의 매개변수는 기본적으로 undefined다. 그러나 어떤 상황에서는 다른 기본값을 설정하는 것이 유용할 수 있다. 이것이 바로 기본 매개변수가 하는 일이다.
과거에는 기본값을 설정하는 일반적인 전략이 함수 본문에서 매개변수 값을 검사하고 undefined이면 값을 할당하는 것이었다.
다음 예에서 b에 대해 값이 제공되지 않으면 a*b를 평가할 때 그 값은 undefined가 되고, multiply 호출은 보통 NaN을 반환했을 것이다. 그러나 이 예의 두 번째 줄이 이를 방지한다.
function multiply(a, b) {
b = typeof b !== "undefined" ? b : 1;
return a * b;
}
console.log(multiply(5)); // 5
기본 매개변수를 사용하면 함수 본문의 수동 검사가 더 이상 필요 없다. 함수 머리에서 b의 기본값으로 1을 넣을 수 있다.
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5)); // 5
자세한 내용은 참조의 default parameters를 보라.
나머지 매개변수 (Rest parameters)
나머지 매개변수 문법은 무한한 수의 인수를 배열로 나타낼 수 있게 해준다.
다음 예에서 multiply 함수는 나머지 매개변수로 두 번째 인수부터 끝까지의 인수들을 수집한다. 그런 다음 이들을 첫 인수로 곱한다.
function multiply(multiplier, ...theArgs) {
return theArgs.map((x) => multiplier * x);
}
const arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
화살표 함수 (Arrow functions)
화살표 함수 표현식(arrow function expression)(가상의 미래 -> 문법과 구분하기 위해 fat arrow라고도 불림)은 함수 표현식에 비해 더 짧은 문법을 가지며, 자신만의 this, arguments, super, new.target을 가지지 않는다. 화살표 함수는 항상 익명이다.
화살표 함수의 도입에는 두 가지 요소가 영향을 미쳤다: 더 짧은 함수와 this의 비바인딩(non-binding).
더 짧은 함수 (Shorter functions)
일부 함수형 패턴에서 더 짧은 함수는 환영받는다. 비교해보자.
const a = ["Hydrogen", "Helium", "Lithium", "Beryllium"];
const a2 = a.map(function (s) {
return s.length;
});
console.log(a2); // [8, 6, 7, 9]
const a3 = a.map((s) => s.length);
console.log(a3); // [8, 6, 7, 9]
별도의 this 없음 (No separate this)
화살표 함수 이전에는 모든 새 함수가 자신만의 this 값을 정의했다(생성자의 경우 새 객체, strict mode 함수 호출의 경우 undefined, 함수가 "객체 메서드"로 호출되면 기본 객체 등). 이것은 객체 지향적 프로그래밍 스타일에 이상적이지 않다는 것이 증명됐다.
function Person() {
// Person() 생성자는 `this`를 자기 자신으로 정의한다.
this.age = 0;
setInterval(function growUp() {
// 비strict 모드에서 growUp() 함수는 `this`를
// 전역 객체로 정의하는데, 이는 Person() 생성자가
// 정의한 `this`와 다르다.
this.age++;
}, 1000);
}
const p = new Person();
ECMAScript 3/5에서 이 문제는 this의 값을 클로저로 감쌀 수 있는 변수에 할당함으로써 해결됐다.
function Person() {
// 일부는 `self` 대신 `that`을 선택한다.
// 하나를 선택하고 일관되게 사용하라.
const self = this;
self.age = 0;
setInterval(function growUp() {
// 콜백은 값이 예상된 객체인 `self` 변수를 참조한다.
self.age++;
}, 1000);
}
대안으로, 올바른 this 값이 growUp() 함수에 전달되도록 바인딩된 함수(bound function)를 만들 수도 있다.
화살표 함수는 자신만의 this를 가지지 않는다. 둘러싼 실행 컨텍스트의 this 값이 사용된다. 따라서 다음 코드에서 setInterval에 전달된 함수 안의 this는 둘러싸는 함수의 this와 같은 값을 가진다.
function Person() {
this.age = 0;
setInterval(() => {
this.age++; // `this`는 person 객체를 올바르게 가리킨다
}, 1000);
}
const p = new Person();
더 알아보기
- JavaScript 가이드: 객체 다루기(Working with objects)
- 표현식과 연산자 가이드
- Function 객체 참조
- 클로저(Closures) MDN 문서
- 키워드: 함수, 함수 선언, 함수 표현식, 호이스팅, 재귀, IIFE, 클로저, arguments, 기본 매개변수, 나머지 매개변수, 화살표 함수, call, apply, this