JS-함수 심화학습
| topics | 200-프론트개발 |
| types | 이론 학습 |
| tags |
함수 심화학습
재귀
자기 자신을 호출하는 함수
재귀 호출의 최대 개수(재귀 깊이)를 제한하고 있다.
function pow(x, n) {
let result = 1;
for (let i = 0; i < n; i++) {
result *= x;
}
return result;
}
function pow(x, n) {
return (n == 1) ? x : (x * pow(x, n - 1));
}
반복문으로도 표현 가능하지만 ,
재귀를 이용하면 훨신 더 간결하고 유지보수가 쉬운 코드를 만들 수 있을 때가 있다.
하지만 반복문은 메모리를 더 적게 사용하고 재귀는 메모리를 더 많이 잡아먹는다
재귀의 동작
실행 컨텍스트 : 해당 함수의 실행 절차, 변수의 현재값, this의 값등 의 함수 실행에대한 세부 정보를 담고 있는 내부 데이터 구조
1 함수 호출 = 1 실행 컨텍스트 생성
- 스택 최상단에 현재 컨텍스트가 '기록’됩니다.
- 서브 호출을 위한 새로운 컨텍스트가 만들어집니다.
- 서브 호출이 완료되면. 기존 컨텍스트를 스택에서 꺼내(pop) 실행을 이어나갑니다.
이후 간단히 표기할 실행 컨텍스트
{ 인수 , 함수의 실행 위치}
(1) function pow(x, n) {
if (n == 1) {
(2) return x;
} else {
(3) return x * pow(x, n - 1);
}
}
alert( pow(2, 3) );
- pow(2,3)이 호출 된 순간
** 실행 컨텍스트 **
| { x : 2 , n : 3 , ( 1 )} | - (3) 위치에서 pow(2,2)가 호출 된 순간
** 실행 컨텍스트 **
| { x : 2 , n : 2 , ( 1 )} |
| { x : 2 , n : 3 , ( 3 )} | - (3) 위치에서 pow(2,1)가 호출 된 순간
** 실행 컨텍스트 **
| { x : 2 , n : 1 , ( 1 )} |
| { x : 2 , n : 2 , ( 3 )} |
| { x : 2 , n : 3 , ( 3 )} | - n==1 을 만족 시킴으로 x가 리턴되면서 함수 종료 됨
** 실행 컨텍스트 **
| { x : 2 , n : 1 , ( 1 )} | ⇒ 2 반환 → pop
| { x : 2 , n : 2 , ( 3 )} |
| { x : 2 , n : 3 , ( 3 )} |
———————————————
| { x : 2 , n : 2 , ( 3 )} | 실행 → pow(2,1)결과 알고잇음으로 4 반환 → pop
| { x : 2 , n : 3 , ( 3 )} |
———————————————
| { x : 2 , n : 3 , ( 3 )} | 실행 → pow(2,2)결과 알고잇음으로 8 반환 → pop
재귀의 이용
깊이 탐색하는 알고리즘을 구현할 때 사용가능 dfs 같은..
연결리스트
queue 나 deque의 자료구조를 이용할 때
shift() 나 unshift() 를 이용하면 시간 복잡도가 크다. O(n)
형태
let list = {
value : 값,
next : 다음 객체 참조
}

let list1 = {
value : 1,
next : null,
}
let list2 = {
value : 2,
next : list3,
}
let list3 = {
value : 3,
next : null,
}
/**
원래 형태
list1
list2 -> list3
**/
// 추가
list3.next = {value : 4}
list1.next = list2
/**
추가 후 형태
list1 -> list2 -> list3 -> {value:4}
**/
‘ … ’나머지 매개변수와 스프레드 문법
나머지 매개변수 ‘ … ’
남아 있는 매개변수들을 배열에 넣어서 반환
function test(a,b,...c){
console.log(a);// 1
console.log(b);// 2
console.log(c);// [3,4,5,6,7]
}
test(1,2,3,4,5,6,7);
남아있는 매개변수를 담기에 중간에 있으면 안됨.
a , …b , c 불가능.
arguments객체
나머지 매개변수 전에 쓰였던 것
이터러블 객체지만 배열은 아님
화살표 함수는 arguments가 없음 , 호출 시 외부 일반 함수의 arguments 반환
function showName() {
alert( arguments.length );
alert( arguments[0] );
alert( arguments[1] );
}
// 2, Bora, Lee가 출력됨
showName("Bora", "Lee");
// 1, Bora, undefined가 출력됨(두 번째 인수는 없음)
showName("Bora");
스프레드 문법 ‘ … ’
배열을 통체로 매개변수로 넘겨 주는것
- 배열을 인수로 전달 할 때
let arr1 = [1, -2];
let arr2 = [8, 3];
Math.max(1, ...arr1, 2, ...arr2, 25) //25
//이 두개는 같다.
Math.max(1, 1, -2, 2, 8, 3, 25) //25
- 배열을 합칠 때
let arr1 = [1, -2];
let arr2 = [8, 3];
let arr3 = [...arr1, ...arr2] //[1,-2,8,3]
- 배열을 복사 할 때
Object.assign({}, obj) 도 이용할 수 있긴 하지만 이 편이 더 간단하다.
let arr1 = [1, -2];
let copyarr1 = [...arr1];
console.log(arr1 ===copyarr1); //false
변수의 유효범위와 클로저
여기서의 변수는 let과 const만 다룬다.
코드 블록
코드블록 { } 안에서 선언한 변수는 블록 안에서만 사용 가능하다.
{
// 지역 변수를 선언하고 몇 가지 조작을 했지만 그 결과를 밖에서 볼 수 없습니다.
let message = "안녕하세요."; // 블록 내에서만 변숫값을 얻을 수 있습니다.
alert(message); // 안녕하세요.
}
alert(message); // ReferenceError: message is not defined
렉시컬 환경
스크립트 전체 , 코드블록 , 호출된 함수는 렉시컬환경이라는 내부수김 연관 객체를 갖는다.
(실제로 접근은 불가.)
생성된 기준으로 렉시컬환경을 갖는다.
- 환경 레코드
모든 지역 변수와 함수를 프로퍼티로 저장하고 있는 객체
this값과 같은 정보도 여기에서 저장 - 외부 렉시컬 환경에 대한 참조
외부 코드와 연관됨
변수
변수는 환경 레코드의 프로퍼티. 변수 변경시 환경레코드의 프로퍼티가 변경됨

(1) 스크립트 시작시 변수가 렉시컬 환경에 올라간다. 변수상태는 uninitialized가 된다. 변수상태를 인지는 하고 있으나 변수를 참조할 수 없다.
(2) 변수가 선언됨. 값을 할당하지 않았으면 undefined의 값을 가지게 된다.
(3,4) 값이 할당
함수 선언문
바로 초기화 됨. 렉시컬환경이 만들어 지는 즉시 사용가능.
내부와 외부 렉시컬 환경
함수를 호출해 실행하면 새로운 렉시컬환경이 자동으로 만들어진다.
이 새로운 렉시컬환경에서는 매개 변수와 해당 함수의 지역 변수들을 저장하고 외부 렉시컬을 참조한다.
변수에 접근 시 내부 렉시컬 → 참조하는 외부 렉시컬 순으로 확장하며 검색.

클로저
반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
console.log(counter());//0
console.log(counter());//1
console.log(counter());//2

makeCounter를 호출할때마다 새로운 렉시컬이 생성됨.
makeCounte는 실행되고 반환되었지만 익명함수는 생성만되고 실행되지 않음.
즉, 익명함수의 렉시컬환경은 기억되고 있음.
이 익명함수는 count라는 makeCounter이라는 내부변수를 참조하고 있는데 때문에 count의 값은 계속 기억되고 있음.
클로저의 장단점
- 정보 은닉
count변수는 외부에서 접근이 불가능하다. 때문에 의도적인 변경을 피할수 있어 안정적이다. - 메모리 많이 차지
해당 환경의 변수를 사용하고 있어서 지워지않는 거기 때문에 메모리를 많이 잡아 먹는다.
전역 객체
전역 객체를 이용하여 어디서나 사용 가능한 변수나 함수를 만들 수 있음
브라우저 환경 - window
node.js - global
현재는 globalThis로 표준화
var은 전역 객체의 프로퍼티가 되지만 let은 그렇지 않다.
window.alert("hello")
var gVar = 5;
alert(window.gVar); //5
let gLet = 5;
alert(window.gLet); // undefine
모든 곳에서 사용가능한 변수 만들기
// 모든 스크립트에서 현재 사용자(current user)에 접근할 수 있게 이를 전역 객체에 추가함
window.currentUser = {
name: "John"
};
// 아래와 같은 방법으로 모든 스크립트에서 currentUser에 접근할 수 있음
alert(currentUser.name); // John
// 지역 변수 'currentUser'가 있다면
// 지역 변수와 충돌 없이 전역 객체 window에서 이를 명시적으로 가져올 수 있음
alert(window.currentUser.name); // John
전역객체를 이용하여 해당 기능 지원하는지 확인
if (!window.Promise) {
alert("구식 브라우저를 사용 중이시군요!");
}
if (!window.Promise) {
window.Promise = ... // 모던 자바스크립트에서 지원하는 기능을 직접 구현함
}
객체로서의 함수와 기명 함수 표현식
javascript에서의 함수는 객체이다.
따라서 함수에 프로퍼티를 추가,제거,참조할 수 있다.
함수의 프로퍼티
‘name’
함수의 이름을 가져올 수 있다. 메서드에서도 이를 사용이 가능하며 익명함수도 자동으로 이름이 할당된다.
let sayHi = function() {
alert("Hi");
};
alert(sayHi.name); // sayHi (익명 함수이지만 이름이 있네요!)
‘length**’**
매개변수의 개수를 반환
나머지 매개변수는 이에 포함이 되지않음
함수의 타입검사를 위해서도 종종 사용됨
커스텀 프로퍼티
자체적으로 프로퍼티를 추가할 수도 있다.
function sayHi() {
alert("Hi");
// 함수를 몇 번 호출했는지 세봅시다.
sayHi.counter++;
}
sayHi.counter = 0; // 초깃값
sayHi(); // Hi
sayHi(); // Hi
alert( `호출 횟수: ${sayHi.counter}회` ); // 호출 횟수: 2회
프로퍼티는 변수가 아니라 여기에 let counter을 선언하고 실행하여도 아무 상관이 없다
클로저를 함수 프로퍼티로 대체 가능하다.
function makeCounter() { //함수 프로퍼티
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
function makeCounter() {//클로저
let count = 0
return function counter() {
return count++;
};
}
클로저로 생성시 외부에서 count에 접근이 불가능하지만 함수프로퍼티를 사용시 외부에서 값을 수정 가능하다.
new Function 문법
문자열을 함수로 변환함.
let func = new Function ([arg1, arg2, ...argN], functionBody);
- 일반적인 함수와 다르게 현재 렉시컬환경을 참조하는 것이 아닌 전역 렉시컬을 참조하게 된다.
//make function with 'new Function'
function getFunc() {
let value = "test";
let func = new Function('alert(value)');
return func;
}
getFunc()(); // ReferenceError: value is not defined
//general function
function getFunc() {
let value = "test";
let func = function() { alert(value); };
return func;
}
getFunc()(); // getFunc의 렉시컬 환경에 있는 값 "test"가 출력됩니다.
호출 스케줄링 (setTimeout, setInterval)
일정 시간이 지난 후에 원하는 함수를 호출할 수 있게 하는 것
setTimeout & setInterval
- setTimeout : 일정 시간이 지난 후에 함수를 실행할 수 있다.
- setInterval : 일정한 간격을 두고 함수를 실행할 수 있다.
//문법
let timerId = setTimeout(func, [delay], [arg1], [arg2], ...);//스케줄링
clearTimeout(timerId);//스케줄링 취소
//사용 예제
function sayHi(who, phrase) {
alert( who + ' 님, ' + phrase );
}
setTimeout(sayHi, 1000, "홍길동", "안녕하세요."); //1초 뒤, '홍길동 님, 안녕하세요.'
중첩 setTimeout
일정한 간격을 두고 함수를 실행할 수 있다. 이후에 나올 setInterval과 비슷한 역할을 한다. delay를 변수로 두어 함수를 실행할 때마다 일정하게 간격을 조정할 수 있다.
let timerId = setTimeout(function tick() {
alert('째깍');
timerId = setTimeout(tick, 2000); // (*)
}, 2000);
setInterval vs 중첩 setTimeout
setInterval은 지연간격에 함수의 실행시간을 포함시킨다.

setInterval

중첩 setTimeout
반면 , setTimeout은 함수가 끝나고 지연시간을 잰다.
call/apply와 데코레이터, 포워딩
직접 캐싱하기
function slow(x) {
alert(`slow(${x})을/를 호출함`);
return x;
}
function cachingDecorator(func) {//클로저
let cache = new Map();
return function(x) {// func를 캐싱 로직으로 감쌈.(wrapper)
if (cache.has(x)) { // cache에 해당 키가 있으면
return cache.get(x); // 대응하는 값을 cache에서 읽어옵니다.
}
let result = func(x); // 그렇지 않은 경우엔 func를 호출하고,
cache.set(x, result); // 그 결과를 캐싱(저장)합니다.
return result;
};
}
slow = cachingDecorator(slow);
decorator : 위의 cachingDecorator같이 인수로 받은 함수의 행동을 변경시켜주는 함수
decorator를 이용하면 장점
cachingDecorator를 재사용 할 수 있습니다. 원하는 함수 어디에든cachingDecorator를 적용할 수 있습니다.- 캐싱 로직이 분리되어
slow자체의 복잡성이 증가하지 않습니다. - 필요하다면 여러 개의 데코레이터를 조합해서 사용할 수도 있습니다(추가 데코레이터는
cachingDecorator뒤를 따릅니다).
func.call
this를 명시적으로 고정해 함수를 호출 가능
func.call(context, arg1, arg2, ...)
func.call이 필요한 예시
// worker.slow에 캐싱 기능을 추가해봅시다.
(3)let worker = {
someMethod() {
return 1;
},
slow(x) {
// CPU 집약적인 작업이라 가정
alert(`slow(${x})을/를 호출함`);
return x * this.someMethod(); // (*)
}
};
// 이전과 동일한 코드
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
console.log(this);//{someMethod:~,slow:~}
let result = func(x);
cache.set(x, result);
return result;
};
}
(1) worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용
(2)alert( worker.slow(2) ); // 에러 발생!, Error: Cannot read property 'someMeth
this
- this 는 함수가 호출 될 때 결정된다.
- 함수에서의 this
- 일반적으론 전역 객체가 바인딩된다.
- 메소드일 경우 해당 객체가 바인딩된다.
- 메소드의 내부함수는 전역객체가 바인딩된다.
따라서 ,
(1) 실행후의 worker,
let worker = {
someMethod() {
return 1;
},
slow: function(x) {
//cache변수는 메모리에 저장되어 있었기 때문에 사용가능 (클로저)
if (cache.has(x)) {
return cache.get(x);
}
(3)console.log(this);//{someMethod:~,slow:~}
let result = function(x){
alert(`slow(${x})을/를 호출함`);
(4)return x * this.someMethod();
};
cache.set(x, result);
return result;
};
};
(2) 실행 후,
(3)에서 worker에 할당된 객체를 가르킴
(4)에서는 메소드 내부함수 이므로 전역객체가 바인딩됨.
전역객체에 someMethod따위는 없음 → 에러
해결
let worker = {
someMethod() {
return 1;
},
slow(x) {
alert(`slow(${x})을/를 호출함`);
return x * this.someMethod();
}
};
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func.call(this, x); //this를 worker로 고정
cache.set(x, result);
return result;
};
}
worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용
argument를 이용한 코드
let worker = {
slow(min, max) {
alert(`slow(${min},${max})을/를 호출함`);
return min + max;
}
};
function cachingDecorator(func, hash) {
let cache = new Map();
return function() {
let key = hash(arguments); // (*)
if (cache.has(key)) {
return cache.get(key);
}
let result = func.call(this, ...arguments);
cache.set(key, result);
return result;
};
}
function hash(args) {
return args[0] + ',' + ar함
뒤에 인수를 각각 넣어야하기 때문에 …을 이용
func.apply
기능은 func.call이랑 같은데 인자를 각각 넣는대신 유사 배열을 넣어주면됨
func.apply(context, args)
위의 argument를 이용한 코드에서
let result = func.call(this, ...arguments); 대신
let result = func.apply(this, arguments);를 넣으면됨.
하
함수 바인딩
객체 메서드안에 funtion이 호출될때 this가 사라지는 문제 해결방법!
wrapper
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
1)setTimeout(user.sayHi, 1000); // Hello, undefined!
2)setTimeout(function() {
user.sayHi(); // Hello, John!
}, 1000);
1)번은 user의 문맥을 읽고 sayhi 함수만 전달되게 된다
2)번은 wrapper로 감싸 user.sayHi()를 호출함으로써 user의 문맥을 그대로 가지고 간다.
취약성 : 실행되전 1초안에 user가 바뀌면 변경된 객체의 메서드를 호출
func.bind
this를 특정한객체로 고정하게 해주는 함수
이후 인수도 고정해서 바인딩할수도 있음
let bound = func.bind(context, [arg1], [arg2], ...);
- call,apply와의 차이점 : bind는 함수를 실행시키지 않음.
let user = {
firstName: "John"
};
function func() {
alert(this.firstName);
}
let funcUser = func.bind(user);
funcUser(); // John
bind호출 시점에서의 user가 this로 고정됨. 위의경우처럼 시간 딜레이가 있을때 사이에 변경되도 안전함
화살표 함수
- 화살표함수에는 this가 없어서 외부에서 this를 가져옴
- arguments 지원 X
- new와 함께 호출 불가능
- super도 없음
바인딩 예시!
예시 출처: https://stackoverflow.com/questions/31866390/how-do-i-get-the-right-this-in-an-array-map
a = {
foo: 'bar',
things: [1, 2, 3],
showFooForEach: function() {
this.things.map(function(thing) {
console.log(this.foo, thing);
});
}
}
a.showFooForEach();
여기서 this.foo에서 this는 global가 되어 에러가 일어난다. (메서드 내의 함수→ 전역)
방법 1. 화살표함수
a = {
foo: 'bar',
things: [1, 2, 3],
showFooForEach: function() {
this.things.map((thing) => {
console.log(this.foo, thing);
});
}
}
a.showFooForEach();
화살표함수는this가 없기때문에 상위에서 this 를 가져오게 되어서 a를 가르킨다.
방법 2. bind
a = {
foo: 'bar',
things: [1, 2, 3],
showFooForEach: function() {
this.things.map(function(thing) {
console.log(this.foo, thing);
}.bind(this));
}
}
a.showFooForEach();
방법 3. 변수로 this저장
a = {
foo: 'bar',
things: [1, 2, 3],
showFooForEach: function() {
var self = this;
this.things.map(function(thing) {
console.log(self.foo, thing);
}));
}
}
a.showFooForEach();
방법 4. 일부함수에는 인자로 this를 넘겨줄 수 있다.(forEach, map)
arr.map(callback(currentValue[, index[, array]])[, thisArg])
arr.map(callback(currentValue[, index[, array]])[, thisArg])
a = {
foo: 'bar',
things: [1, 2, 3],
showFooForEach: function() {
this.things.map(function(thing) {
console.log(this.foo, thing);
}, this);
}
}
a.showFooForEach();