[4장] 콜백 함수
![[4장] 콜백 함수](https://cdn.hashnode.com/res/hashnode/image/upload/v1709695881990/7544e5be-17fa-4bb8-b72e-a23ef3dfc972.png)
1. 콜백함수란?
콜백 함수는 다른 코드의 인자로 넘겨주는 함수이다. 콜백 함수를 넘겨받은 코드는 이 콜백 함수를 필요에 따라 적절한 시점에 실행할 것이다.
예를 들면, 어떤 함수 X를 호출하면서 '특정 조건일 때 함수 Y를 실행해서 나에게 알려달라'는 요청을 함께 보내는 것이다. 이 요청을 받은 함수 X의 입장에서는 해당 조건이 갖춰졌는지 여부를 스스로 판단하고 Y를 직접 호출한다.
이처럼 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다. 콜백 함수를 위임받은 코드는 자체적인 내부 로직에 의해 이 콜백 함수를 적절한 시점에 실행할 것이다.
2. 제어권
2-1 호출 시점
콜백 함수 예제 (1-1) setInterval
var count = 0;
var timer = setInterval(function () {
console.log(count);
if (++count > 4) clearInterval(timer);
}, 300);
setInterval을 호출할 때 두 개의 매개변수를 전달했는데, 그중 첫 번째는 익명 함수이고 두 번째는 300이라는 숫자이다.
setInterval의 구조는 다음과 같다.
var intervalID = scope.setInterval(func, delay[, param1, param2, ...]);
func: 함수delay: 밀리초(ms) 단위의 숫자param1, param2, ...:func함수를 실행할 때 매개변수로 전달할 인자
scope에는 Window객체 또는 Worker의 인스턴스가 들어올 수 있다. 두 객체 모두 setInterval메서드를 제공하기 때문이다. 일반적인 브라우저 환경에서는 window를 생략해서 함수처럼 사용이 가능하다.
func에 넘겨준 함수는 매 delay마다 실행되며, 그 결과 어떠한 값도 리턴하지 않는다. setInterval을 실행하면 반복적으로 실행되는 내용 자체를 특정할 수 있는 고유한 ID값이 반환된다. 이를 변수에 담는 이유는 반복 실행되는 중간에 종료(clearInterval)할 수 있게 하기 위해서이다.
콜백 함수 예제 (1-2) setInterval
var count = 0;
var cbFunc = function () {
console.log(count);
if (++count > 4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);
// -- 실행 결과 --
// 0 (0.3초)
// 1 (0.6초)
// 2 (0.9초)
// 3 (1.2초)
// 4 (1.5초)
timer변수에는setInterval의 ID값이 담긴다.setInterval에 첫 번째 인자로 전달한 콜백 함수cbFunc는 0.3초마다 자동으로 실행된다. 이 때, 내부에서는count값을 출력하고,count를 1만큼 증가시킨 다음, 그 값이 4보다 크면 반복 실행을 종료하라고 한다.
cbFunc의 제어권을 넘겨받은 setInterval이 스스로의 판단에 따라 적절한 시점에(0.3초마다) 이 익명 함수를 실행한 것이다. 이처럼 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다.
이미지 출처: "코어 자바스크립트", 정재남, 위키북스, 2019, p. 2.
2-2 인자
우선 예제를 보기전에 map의 구조를 먼저 알아보자
Array.prototype.map(callback[, thisArg])
callback: function(currentValue, index, array)
currentValue: 배열의 요소 중의 현재값index: 현재값의 인덱스array:map메서드의 대상이 되는 배열 자체
map메서드는 첫 번째 인자로 callback함수를 받고, 생략 가능한 두 번째 인자로 콜백 함수 내부에서 this로 인식할 대상을 특정할 수 있다. thisArg를 생략할 경우에는 일반적인 함수와 마찬가지로 전역객체가 바인딩 된다.
콜백 함수 예제 (2-1) Array.prototype.map
var newArr = [10, 20, 30].map(function (currentValue, index) {
console.log(currentValue, index);
return currentValue + 5;
});
console.log(newArr);
// -- 실행 결과 --
// 10 0
// 20 1
// 30 2
// [15, 25, 35]
[10, 20, 30]에map메서드를 호출하고, 그 첫 번째 매개변수로는 익명함수를 전달했다. 이제 배열[10, 20, 30]의 각 요소를 처음부터 하나씩 꺼내어 콜백 함수를 실행한다.- 첫 번째 요소에 대한 콜백 함수는
currentValue에 10,index에 0이 담긴 채 실행되고, 각 값을 출력한 다음15(10 + 5)를 반환한다. - 두 번째 요소에 대한 콜백 함수는
currentValue에 20,index에 1이 담긴 채 실행되고, 각 값을 출력한 다음25(20 + 5)를 반환한다. - 같은 방식으로 세 번째에 대한 콜백 함수까지 실행을 마치고 나면
[15, 25, 35]라는 새로운 배열이 만들어져 변수newArr에 담기고, 이 값이 출력된다.
제이쿼리의 메서드들은 기본적으로 첫 번째 인자에 index가, 두 번째 인자에 currentValue가 온다. 만약 map메서드를 제이쿼리의 방식처럼 순서를 바꾸어 사용해보면 어떨까?
콜백 함수 예제 (2-2) Array.prototype.map - 인자의 순서를 임의로 바꾸어 사용한 경우
var newArr2 = [10, 20, 30].map(function (index, currentValue) {
console.log(index, currentValue);
return currentValue + 5;
});
console.log(newArr2);
// -- 실행 결과 --
// 10 0
// 20 1
// 30 2
// [5, 6, 7]
컴퓨터는 순서에 의해서만 각각을 구분하고 인식한다. 우리가 첫번째 인자의 이름을 index로 명명한 것과는 관계없이 순회 중인 배열 중 현재 요소의 값을 배정한다. 두번째 요소또한 currentValue라고 명명했지만, 순서상으로 두 번째이기 때문에 현재값의 인덱스가 배정된다. 그렇기때문에 [5, 6, 7]이라는 결과가 나오게 되는 것이다.
map메서드를 호출해서 원하는 배열을 얻으려면 map메서드에 정의된 규칙에 따라 함수를 작성해야 하며, 그 규칙에는 콜백 함수의 인자로 넘어올 값들 및 그 순서도 포함되어 있다. 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지는 전적으로 map메서드에 달린 것이다.
이처럼 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수를 호출할 때 어떤 값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가진다.
2-3 this
이전 챕터인 this에서 콜백 함수도 함수이기 때문에 기본적으로는 this가 전역객체를 참조하지만, 제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 되는 것을 알아보았다.
해당 내용을 인지하고 다음 예제를 살펴보자.
콜백 함수 예제 (2-3) Array.prototype.map - 구현
01 Array.prototype.map = function(callback, thisArg) {
02 var mappedArr = [];
03 for (var i = 0; i < this.length; i++) {
04 var mappedValue = callback.call(thisArg || window, this[i], i, this);
05 mappedArr[i] = mappedValue;
06 }
07 return mappedArr;
08 };
- 4번째 줄에서
callback.call을 호출하고, 그 반환값은mappedValue에 담긴다. this에는thisArg값이 있을 경우 그 값을, 없을 경우에는 전역객체를 지정한다.
- 첫 번째 인자에는 메서드의
this가 배열을 가리킬 것이므로 배열의i번째 요소 값을,
- 첫 번째 인자에는 메서드의
- 두 번째 인자에는
i의 값을,
- 두 번째 인자에는
- 세 번째 인자에는 배열 자체를 지정한다.
- 5번째 줄에서
mappedValue가 변수mappedArr의i번째 인자에 할당된다.
제어권을 넘겨받을 코드에서 call / apply메서드의 첫 번째 인자에 콜백 함수 내부에서 this가 될 대상을 명시적으로 바인딩하기 때문에 this에 다른 값이 담기는 것을 알 수 있다.
콜백 함수 내부에서의 this
setTimeout(function() {
console.log(this);
}, 300); // (1) Window { ... }
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this); // (2) Window { ... }
});
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener(
'click',
function(e) {
console.log(this, e); // (3) <button id="a">클릭</button>
} // MouseEvent { isTrusted: true, ... }
);
- (1) :
setTimeout에서 콜백 함수는 일반 함수로서 호출되기 때문에this는 전역 객체를 가리키게 된다. - (2) :
forEach는 기본적으로 콜백 함수의this값을 전역 객체로 설정하며, 선택적인 두 번째 인자로 콜백 함수 내에서this로 사용될 값을 지정할 수 있다. 예제에서는 별도로this를 지정해주지 않았기 때문에 전역 객체를 가리키게 된다. - (3) :
addEventListener는 이벤트 처리할 때 사용되는 콜백 함수 내의this값을 이벤트 리스너가 부착된 DOM 요소로 자동 설정한다. 이는addEventListener기본 동작이며, 결과적으로 콜백 함수 내에서this는 이벤트가 발생한 특정 DOM 요소를 가리키게 된다.
3. 콜백 함수는 함수다
콜백 함수는 함수다. 콜백 함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 함수로서 호출된다.
메서드를 콜백 함수로 전달한 경우
01 var obj = {
02 vals: [1, 2, 3],
03 logValues: function(v, i) {
04 console.log(this, v, i);
05 },
06 };
07 obj.logValues(1, 2); // { vals: [1, 2, 3], logValues: f } 1 2
08 [4, 5, 6].forEach(obj.logValues); // Window { ... } 4 0
09 // Window { ... } 5 1
10 // Window { ... } 6 2
- 7번째에서는
logValues를 메서드로서 호출했기 때문에this는obj를 가리키고, 인자로 넘어온 1, 2가 출력된다. - 8번째에서는
logValues를forEach함수의 콜백 함수로서 전달했다. - 이때
obj를this로 하는 메서드를 그대로 전달한 것이 아니라,obj.logValues가 가리키는 함수만 전달한 것이다.
- 이때
- 따라서
forEach에 의해 함수로서 호출되고, 별도로this를 지정하는 인자가 ㅇ벗으므로 함수 내부에서의this는 전역객체를 바라보게 된다.
- 따라서
즉, 어떤 함수의 인자에 객체의 메서드를 전달하더라도 이는 결국 메서드가 아닌 함수일 뿐인 것이다.
4. 콜백 함수 내부의 this에 다른 값 바인딩하기
콜백 함수 내부에서 this가 객체를 바라보게 하려면 어떻게 해야 할까?
별도의 인자로 this를 받는 함수는 원하는 값을 넘겨주면 되지만, 그렇지 않은 경우에는 this의 제어권도 넘겨주게 되므로 사용자가 임의로 값을 바꿀 수 없다.
그래서 전통적으로는 this를 다른 변수에 담아 콜백 함수로 활용할 함수에서는 this대신 그 변수를 사용하게 하고, 이를 클로저로 만드는 방식이 많이 쓰였다.
콜백 함수 내부의 this에 다른 값을 바인딩하는 방법(1) - 전통적인 방식
var obj1 = {
name: 'obj1',
func: function() {
var self = this;
return function() {
console.log(self.name);
};
},
};
var callback = obj1.func();
setTimeout(callback, 1000); // 1초 뒤 'obj1' 출력
obj1.func메서드 내부에서self변수에this를 담고, 익명 함수를 선언과 동시에 반환한다.obj1.func를 호출하면 앞서 선언한 내부함수가 반환되어callback변수에 담긴다.- 이
callback을setTimeout함수에 인자로 전달하면 1초 뒤callback이 실행되면서obj1을 출력한다.
이 방식은 실제로 this를 사용하지도 않을뿐더러 번거로워 보인다.
그렇다면, 차라리 아래와 같이 this를 아예 안 쓰는 편이 더 낫지 않을까?
콜백 함수 내부에서 this를 사용하지 않은 경우
var obj1 = {
name: 'obj1',
func: function() {
console.log(obj1.name);
},
};
setTimeout(obj1.func, 1000);
물론, 이코드는 훨씬 간결하고 직관적이지만 this를 이용해 다양한 상황에 재활용하는 것은 불가능하다.
다음과 같이 다른 객체에 재활용하는 상황을 살펴보자.
앞선 예제(전통적인방식)의 func함수 재활용
var obj1 = {
name: 'obj1',
func: function() {
var self = this;
return function() {
console.log(self.name);
};
},
};
var obj2 = {
name: 'obj2',
func: obj1.func,
};
var callback2 = obj2.func();
setTimeout(callback2, 1500); // 1.5초 후 'obj2' 출력
var obj3 = { name: 'obj3' };
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000); // 2초 후 'obj3' 출력
이처럼 앞선 예제(전통적인 방식)는 번거롭긴 하지만 this를 우회적으로나마 활용함으로써 다양한 상황에서 원하는 객체를 바라보는 콜백 함수를 만들 수 있는 방법이다.
반면 콜백 함수 내부에서 this를 사용하지 않는 예제의 경우는 처음부터 바라볼 객체를 명시적으로 obj1으로 지정했기 때문에 다른 객체를 바라보게 할 수 없다. 그렇기때문에 메모리를 낭비하는 단점이 있음에도 전통적인 방식이 널리 통용될 수 밖에 없었던 것이다.
이러한 전통적인 방식의 단점은 이후 ES5에서 나온 bind메서드로 보완할 수 있다.
콜백 함수 내부의 this에 다른 값을 바인딩하는 방법(2) - bind 메서드 활용
var obj1 = {
name: 'obj1',
func: function() {
console.log(this.name);
},
};
setTimeout(obj1.func.bind(obj1), 1000); // 1초 후 'obj1' 출력
var obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 1500); // 1.5초 후 'obj2' 출력
5. 콜백 지옥과 비동기 제어
콜백 지옥은 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 과도하게 깊어지는 현상을 의미한다.
비동기는 동기의 반대개념이다.
동기적인 코드는 현재 실행 중인 코드가 완료된 후 다음 코드를 실행하는 방식이고, 비동기는 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어간다.
즉, CPU의 계산에 의해 즉시처리가 가능한 대부분의 코드는 동기적이고, setTimeout, addEventListener, XMLHttpRequest와 같이 별도의 요청, 실행 대기, 보류에 관련된 코드는 비동기적이라고 할 수 있다.
현대의 자바스크립트는 웹의 복잡도가 높아진 만큼 비동기적인 코드의 비중이 훨씬 높아진 상황이다. 그리고 그만큼 콜백 지옥에 빠지기도 훨씬 쉬워졌다.
콜백 지옥 예시 (1-1)
setTimeout(
function(name) {
var coffeeList = name;
console.log(coffeeList);
setTimeout(
function(name) {
coffeeList += ', ' + name;
console.log(coffeeList);
setTimeout(
function(name) {
coffeeList += ', ' + name;
console.log(coffeeList);
setTimeout(
function(name) {
coffeeList += ', ' + name;
console.log(coffeeList);
},
500,
'카페라떼'
);
},
500,
'카페모카'
);
},
500,
'아메리카노'
);
},
500,
'에스프레소'
);
// 0.5초 간격으로 출력된 결과
// 에스프레소
// 에스프레소, 아메리카노
// 에스프레소, 아메리카노, 카페모카
// 에스프레소, 아메리카노, 카페모카, 카페라떼
- 0.5초 주기마다 커피 목록을 수집하고 출력한다.
- 각 콜백은 커피 이름을 전달하고 목록에 이름을 추가한다.
목적 달성에는 지장이 없지만 들여쓰기 수준이 과도하게 깊어지고 값이 전달되는 순서가 '아래에서 위로' 향하고 있어 어색하게 느껴진다.
익명의 콜백 함수를 기명함수로 전환하면 위와 같은 문제를 해결할 수 있다.
콜백 지옥 해결 - 기명함수로 전환
var coffeeList = '';
var addEspresso = function(name) {
coffeeList = name;
console.log(coffeeList);
setTimeout(addAmericano, 500, '아메리카노');
};
var addAmericano = function(name) {
coffeeList += ', ' + name;
console.log(coffeeList);
setTimeout(addMocha, 500, '카페모카');
};
var addMocha = function(name) {
coffeeList += ', ' + name;
console.log(coffeeList);
setTimeout(addLatte, 500, '카페라떼');
};
var addLatte = function(name) {
coffeeList += ', ' + name;
console.log(coffeeList);
};
setTimeout(addEspresso, 500, '에스프레소');
이제 코드의 가독성이 향상되어 '위에서 아래로' 읽는것이 쉬워졌다.
하지만 이렇게 일회성 함수를 모두변수에 할당하면, 코드명을 일일이 따라다녀야 하므로 오히려 혼란을 초래할 수 있다.
이러한 비동기 작업의 혼란을 줄이고자 자바스크립트 진영은 ES6에서 Promise, Generator를, ES2017에서 async/await를 도입했다.
Promise
new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백 함수는 호출할 때 바로 실행되지만, 그 내부에 resolve 또는 reject함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 then또는 catch로 넘어가지 않는다.
따라서 비동기 작업이 완료될 때 비로소 resolve 또는 reject를 호출하는 방법으로 비동기 작업의 동기적 표현이 가능하다.
비동기 작업의 동기적 표현(1) - Promise(1)
new Promise(function(resolve) {
setTimeout(function() {
var name = '에스프레소';
console.log(name);
resolve(name);
}, 500);
})
.then(function(prevName) {
return new Promise(function(resolve) {
setTimeout(function() {
var name = prevName + ', 아메리카노';
console.log(name);
resolve(name);
}, 500);
});
})
.then(function(prevName) {
return new Promise(function(resolve) {
setTimeout(function() {
var name = prevName + ', 카페모카';
console.log(name);
resolve(name);
}, 500);
});
})
.then(function(prevName) {
return new Promise(function(resolve) {
setTimeout(function() {
var name = prevName + ', 카페라떼';
console.log(name);
resolve(name);
}, 500);
});
});
위 예제의 반복적인 내용을 함수화하면 더 간결하게 표현할 수 있다.
비동기 작업의 동기적 표현(2) - Promise(2)
var addCoffee = function(name) {
return function(prevName) {
return new Promise(function(resolve) {
setTimeout(function() {
var newName = prevName ? prevName + ', ' + name : name;
console.log(newName);
resolve(newName);
}, 500);
});
};
};
addCoffee('에스프레소')()
.then(addCoffee('아메리카노'))
.then(addCoffee('카페모카'))
.then(addCoffee('카페라떼'));
addCoffee함수name을 인자로 받으며, 또 다른 함수를 반환한다.- 반환된 함수는
prevName을 인자로 받는다. - 반환된 함수 내부에서는
new Promise를 사용하여 비동기 작업을 구현하며, 비동기 작업은setTimeout으로 지연되어 실행된다.
setTimeout과 비동기 작업- 내부의 익명 함수는
prevName이 존재하는지 확인하고 있다면prevName와name을 결합하여 이를newName에 저장, 없다면name만newName에 저장한다. - 이렇게 생성된 새 이름 목록을
newName변수에 저장하고, 콘솔에 출력한다.
- 내부의 익명 함수는
- Promise의
resolve함수setTimeout내부의 작업이 완료되면,resolve(newName)에 의해,.then메소드에 제공된 다음 함수에newName이 인자로 전달된다.
Generator
Generator를 실행하면 Iterator가 반환되는데, Iterator는 next라는 메서드를 가지고 있다.
이 next메서드를 호출하면 Generator함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다. 이후 다시 next메서드를 호출하면 앞서 멈췄던 부분에서 시작해서 그 다음에 등장하는 yield에서 함수의 실행을 멈춘다.
비동기 작업이 완료되는 시점마다 next메서드를 호출하면 Generator함수 내부의 소스가 위에서부터 아래로 순차적으로 진행되는 것이다.
비동기 작업의 동기적 표현 (3) - Generator
01 var addCoffee = function(prevName, name) {
02 setTimeout(function() {
03 coffeeMaker.next(prevName ? prevName + ', ' + name : name);
04 }, 500);
05 };
06 var coffeeGenerator = function*() {
07 var espresso = yield addCoffee('', '에스프레소');
08 console.log(espresso);
09 var americano = yield addCoffee(espresso, '아메리카노');
10 console.log(americano);
11 var mocha = yield addCoffee(americano, '카페모카');
12 console.log(mocha);
13 var latte = yield addCoffee(mocha, '카페라떼');
14 console.log(latte);
15 };
16 var coffeeMaker = coffeeGenerator();
17 coffeeMaker.next();
- 6번째 줄의
*이 붙은 함수가Generator함수이다. coffeeGenerator를 실행하면next라는 메서드를 가지고 있는coffeeMaker가 반환된다.coffeeMaker.next()를 호출하면Generator함수 내부에서 가장 먼저 등장하는yield에서 함수의 실행을 멈춘다.- 각
addCoffee함수는setTimeout을 수행하고, 이 작업이 완료될 때마다coffeMaker.next()를 호출한다. 그리고Generator함수가 다시 실행되어 다음yield키워드까지 이동한다. - 순차적 실행 과정
- 첫
coffeeMaker.next()호출로 '에스프레소'가 추가되고,setTimeout이 완료된 후 다음next()호출로 '아메리카노'가 추가된다. - 이 패턴이 '카페모카', '카페라떼'에 대해서도 반복된다.
- 결과적으로,
에스프레소,에스프레소, 아메리카노,에스프레소, 아메리카노, 카페모카,에스프레소, 아메리카노, 카페모카, 카페라떼순서로 커피 목록이 만들어지고 출력된다.
- 첫
async/await
비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 표기하면 뒤의 내용이 Promise로 자동 전환되고, 해당 내용이 resolve된 이후에야 다음으로 진행한다.
즉 Promise의 then과 흡사한 효과를 얻을 수 있다.
비동기 작업의 동기적 표현 (4) - Promise + async/await
var addCoffee = function(name) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(name);
}, 500);
});
};
var coffeeMaker = async function() {
var coffeeList = '';
var _addCoffee = async function(name) {
coffeeList += (coffeeList ? ',' : '') + (await addCoffee(name));
};
await _addCoffee('에스프레소');
console.log(coffeeList);
await _addCoffee('아메리카노');
console.log(coffeeList);
await _addCoffee('카페모카');
console.log(coffeeList);
await _addCoffee('카페라떼');
console.log(coffeeList);
};
coffeeMaker();
6. 정리
콜백 함수는 다른 코드에 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.
제어권을 넘겨받은 코드는 다음과 같은 제어권을 가진다.
- 콜백 함수를 호출하는 시점을 스스로 판단해서 실행한다.
- 콜백 함수를 호출할 때 인자로 넘겨줄 값들 및 그 순서가 정해져 있다.
이 순서를 따르지 않고 코드를 작성하면 엉뚱한 결과를 얻게 된다. - 콜백 함수의
this가 무엇을 바라보도록 할지가 정해져 있는 경우도 있다.
정하지 않은 경우에는 전역객체를 바라본다.
사용자 임의로this를 바꾸고 싶을 경우bind메서드를 활용하면 된다.
어떤 함수에 인자로 메서드를 전달하더라도 이는 결국 함수로서 실행된다.
비동기 제어를 위해 콜백 함수를 사용하다 보면 콜백 지옥에 빠지기 쉽다.
- 최근의 ECMAScript에는
Promise,Generator,async/await등 콜백 지옥에서 벗어날 수 있는 방법들이 등장하고 있다.
- 최근의 ECMAScript에는

