함수 내부에 중첩 호출이 있을 때는 다음과 같은 절차가 수행된다.
재귀적으로 정의된 자료구조는 자기 자신의 일부를 복제하는 형태의 자료구조
function showName(firstName, lastName, ...titles) {
alert( firstName + ' ' + lastName ); // Julius Caesar
// 나머지 인수들은 배열 titles의 요소가 됩니다.
// titles = ["Consul", "Imperator"]
alert( titles[0] ); // Consul
alert( titles[1] ); // Imperator
alert( titles.length ); // 2
}
showName("Julius", "Caesar", "Consul", "Imperator");
위처럼 나머지 매개변수를 ...
을 이용해서 배열로 받을 수 있다.
나머지 매개변수는 항상 인수의 마지막에 있어야한다.
나머지 매개변수는 비교적 최신 문법으로 이전에는 arguments 변수가 있었다.
arguments는 유사 배열이면서 iterable 객체이다. 하지만 배열은 아니다.
arguement는 모든 인수를 담는다.
화살표 함수에는 arguments 변수가 없다.
spread 문법은 배열의 요소를 전개해서 넣어주기 위해서 만들어졌다.
spread 문법은 배열이 아니라 iterable 객체이면 모두 사용할 수 있다.
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
let merged = [0, ...arr, 2, ...arr2];
alert(merged); // 0,3,5,1,2,8,9,15 (0, arr, 2, arr2 순서로 합쳐집니다.)
...arr
은 표현식이 아니라서 단독으로 사용할 수 없다.
let str = "Hello";
const a = ...str; // 에러 발생
alert( a );
[...str]
과 Array.from(str)
은 동일한 결과가 출력이 되지만 차이가 있다.
Array.from(str)
은 유사배열에도 사용이 가능하다.
spread 문법을 사용해서 객체를 복사할 수 있다.
{...}
코드 블록 안에서 선언한 변수는 코드 블록 안에서만 사용할 수 있다.
var
로 선언된 변수는 코드 블록이 아니라 함수 안으로 제한이 된다.
{
// 지역 변수에 몇 가지 조작을 하면, 그 결과를 밖에선 볼 수 없습니다.
let message = "Hello"; // 블록 내에서만 변숫값을 얻을 수 있습니다.
alert(message); // Hello
}
alert(message); // ReferenceError: message is not defined
{
// 지역 변수에 몇 가지 조작을 하면, 그 결과를 밖에선 볼 수 없습니다.
var message = "Hello"; // 블록 내에서만 변숫값을 얻을 수 있습니다.
console.log(message); // Hello
}
console.log(message); // Hello
자바스크립트에서 실행중인 함수, 코드블록, 스크립트 전체는 렉시컬 환경
이라고 불리는 내부 숨긴 연관 객체를 갖는다.
렉시컬 환경 객체
는 두 부분으로 구성이 된다.
변수는 특수 내부 객체인 환경 레코드의 프로퍼티일 뿐이다. 변수를 가져오거나 변경하는 것은 환경 레코드의 프로퍼티를 가져오거나 변경하는 것이다.
let
을 만나기 전까진 이 변수를 참조할 수 없습니다.let phrase
가 나타났네요. 아직 값을 할당하기 전이기 때문에 프로퍼티 값은 undefined
입니다. phrase
는 이 시점 이후부터 사용할 수 있습니다.phrase
에 값이 할당되었습니다.phrase
의 값이 변경되었습니다.function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
위와 같은 코드가 있을 때 makeCounter()
를 호출하면 호출할 때마다 새로운 렉시컬 환경 객체가 만들어진다. 이 렉시컬 환경 객체에는 makeCounter를 실행하는데 필요한 변수들이 저장된다.
그런데 makeCounter()
안에서 만들어진 중첩 함수가 생성된다. 그런데 함수는 함수가 만들어진 곳의 렉시컬 환경을 [[Environment]]
라고 하는 숨김 프로퍼티 안에 저장을 한다.
이후에 counter()
를 호출하면 각 호출마다 새로운 렉시컬 환경이 만들어진다. 그리고 이 렉시컬 환경은 counter.[[Environment]]
에 저장된 렉시컬 환경을 외부 렉시컬 환경으로 참조한다.
클로저는 외부 변수를 기억하고 외부 변수에 접근할 수 있는 함수를 의미한다.
자바스크립트에서 함수는 숨김 프로퍼티인 [[Environment]]
를 이용해 자신이 만들어진 외부함수의 렉시컬 환경을 기억하고 이것을 이용해서 외부 변수에 접근한다.
name 프로퍼티에는 함수 이름이 담겨있다.
length에는 매개변수의 갯수가 담겨있다.
매개변수의 갯수에 따라서 다른 기능을 수행하도록 만들 때 사용 가능.
직접 커스텀 프로퍼티를 만들 수 있다.
function makeCounter() {
// let count = 0 대신 아래 메서드(프로퍼티)를 사용함
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
다음 처럼 함수 표현식으로 들어가는 함수에 이름을 적어주는 것이다.
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
sayHi("John"); // Hello, John
이러면 이름을 사용해서 함수 표현식 내부에서 자기 자신을 참조할 수 있다.
또한 표현식 외부에서는 그 이름을 사용할 수 없다.
즉, 자기 자신을 사용하기 위해서 만들어진 것.
이것을 사용하는 이유는 재귀로 사용을 하는데 이 함수를 다른 변수에 대입하고 원래 있던 변수를 지워버리면 생기는 문제를 해결하기 위한 것
다음과 같이 함수를 만들 수 있다.
let func = new Function ([arg1, arg2, ...argN], functionBody);
이것의 장점은 문자열을 사용해 함수를 만들 수 있다는 점이다.
new Function
을 사용해서 함수를 만들면 [[Environment]]
가 함수의 렉시컬 환경이 아니라 전역 렉시컬 환경을 참조한다. 따라서 new Function
을 이요해 만든 함수는 외부 변수에 접근할 수 없고 전역 변수에만 접근할 수 있다.
아주 느린 함수 slow가 있을 때 이것을 캐싱하고 싶으면 다음과 같이 할 수 있다.
function slow(x) {
// CPU 집약적인 작업이 여기에 올 수 있습니다.
alert(`slow(${x})을/를 호출함`);
return x;
}
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) { // cache에 해당 키가 있으면
return cache.get(x); // 대응하는 값을 cache에서 읽어옵니다.
}
let result = func(x); // 그렇지 않은 경우엔 func를 호출하고,
cache.set(x, result); // 그 결과를 캐싱(저장)합니다.
return result;
};
}
slow = cachingDecorator(slow);
alert( slow(1) ); // slow(1)이 저장되었습니다.
alert( "다시 호출: " + slow(1) ); // 동일한 결과
alert( slow(2) ); // slow(2)가 저장되었습니다.
alert( "다시 호출: " + slow(2) ); // 윗줄과 동일한 결과
cachingDecorator
처럼 인수로 받은 함수의 행동을 변경시켜주는 함수를 데코레이터라고 부른다.
slow
함수를 직접 수정하지 않고 캐싱을 할 수 있다.