Today I Learned
- javascript 심화 강의 수강 - 클로저
- javascript 퀴즈 풀기
- OSI 7계층 특강 수강
javascript 심화 강의 - 클로저
함수가 선언된 렉시컬 환경(lexical environment)
const x = 1;
function outerFunc() {
const x = 10;
function innerFunc() {
console.log(x); // 10
};
innerFunc();
};
outerFunc();
중첩 함수 innerFunc의 상위 스코프는 outerFunc의 스코프 → 그래서 x = 10에 [먼저] 접근하는 것
const x = 1;
// innerFunc()에서는 outerFunc()의 x에 접근할 수 없다.
// Lexical Scope를 따르는 프로그래밍 언어이기 때문이다.
function outerFunc() {
const x = 10;
innerFunc();
};
function innerFunc() {
console.log(x); // 1
};
outerFunc();
innerFunc 함수를 outerFunc 함수의 내부에서 호출(실행)한다 하더라도 innerFunc 함수가 outerFunc 함수 내부에 정의된 것이 아니기 때문에 outerFunc 함수의 변수에 접근할 수는 없다!
정의된 곳 !== 실행된 곳
렉시컬 스코프
const x = 1;
function foo() {
const x = 10;
bar();
};
function bar() {
console.log(x);
};
foo(); // 1
bar(); // 1
JS 엔진은 함수를 어디서 ‘호출(실행)했는지’가 아니라 함수를 어디에 ‘정의했는지’에 따라 상위 스코프를 결정함.
다시 말하면 “외부 렉시컬 환경에 대한 참조”에 저장할 참조값, 즉, 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치) 에 의해 결정된다. 이것이 바로 렉시컬 스코프다.
lexical environment 구성 간단 정리
-record: 변수(식별자) 정보 - 호이스팅
-outer: 외부 환경의 참조 정보 - 스코프 체인
(정의된 환경에 대한 정보를 저장)
클로저와 렉시컬 환경(LexicalEnvironment)
const x = 1;
function outer() {
const x = 10;
const inner = function () { console.log(x) };
return inner;
}
const innerFunc = outer();
innerFunc();
outer 함수를 호출하면 중첩 함수 inner를 반환한다. outer() = function () { console.log(x) };
그리고 outer 함수의 실행 컨텍스트는 역할을 다 했기 때문에 실행 컨텍스트 스탭에서 팝되어 제거된다. inner함수가 innerFunc에 저장(전달)되었는데, 이는 outer 함수의 렉시컬환경을 참조하고 있다. 즉, outer 함수의 실행 컨텍스트는 실행 컨텍스트 call stack에서 제거되지만 outer 함수의 렉시컬 환경까지 소멸하는 것은 아니다.
외부 함수보다 중첩 함수가 더 오래 유지되는 경우, 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 (여전히) 참조할 수 있다. ← 이 개념에서 중첩 함수가 바로 클로저
클로저인 경우와 클로저가 아닌 경우 구별하기
function foo() {
const x = 1;
const y = 2;
// 일반적으로 클로저라고 하지 않는다.
function bar () {
const z = 3;
//상위 스코프의 식별자를 참조하지 않는다.
console.log(z);
}
return bar;
}
const bar = foo();
bar();
function foo() {
const x = 1;
// bar 함수는 외부로 나가서 따로 호출되는게 아니라, 선언 후 바로 실행 + 소멸
// 이러한 함수는 일반적으로 클로저라고 하지 않는다.
function bar() {
//상위 스코프(foo 함수)의 식별자를 참조한다.
console.log(x);
}
bar();
}
foo();
function foo() {
const x = 1;
const y = 2;
// 클로저
// 중첩 함수 bar는 외부 함수(foo 함수)보다 더 오래 유지되며 상위 스코프의 식별자를 참조한다.
function bar() {
console.log(x);
}
return bar;
}
const bar = foo();
bar();
클로저를 사용하는 이유
클로저는 주로 의도치 않은 상태 변경을 막기 위해서, 상태를 안전하게 변경하고 유지하기 위해 사용한다.
상태를 안전하게 은닉한다 -> 특정 함수에게만 상태 변경을 허용한다!
클로저의 활용
// 카운트 상태 변경 함수
const increase = (function () {
// 카운트 상태 변수
let num = 0;
// 클로저
return function () {
return ++num;
}
}());
// 이전 상태값을 유지
console.log(increase()); //1
console.log(increase()); //2
console.log(increase()); //3
코드 설명
1. 위 코드가 실행되면 즉시 실행 함수가 호출되고 즉시 실행 함수가 반환한 함수가 increase 변수에 할당된다.
2. increase 변수에 할당된 함수는 자신이 정의된 위치에 의해 결정된 상위 스코프인 즉시 실행 함수의 렉시컬 환경을 기억하는 클로저다.
3. 즉시 실행 함수는 호출된 이후 소멸되지만, 즉시 실행 함수가 반환한 클로저는 increase 변수에 할당되어 호출된다.
4. 이때 즉시 실행 함수가 반환한 클로저는 자신이 정의된 위치에 의해 결정된 상위 스코프인 즉시 실행 함수의 렉시컬 환경을 기억하고 있다.
5. 따라서 즉시 실행 함수가 반환한 클로저는 카운트 상태를 유지하기 위한 자유 변수 num을 언제 어디서 호출하든지 참조하고 변경할 수 있다.
6. num은 초기화되지 않을 것이며, 외부에서 직접 접근할 수 없는 은닉된 private 변수이므로, 전역 변수를 사용했을 때와 같이 의도되지 않은 변경을 걱정할 필요도 없다.
함수형 프로그래밍에서 클로저를 활용하는 예시
// 함수를 인수로 전달받고 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다.
const counter = (function(){
// 카운트 상태를 유지하기 위한 자유 변수
let counter = 0;
// 클로저를 반환
return function (aux) {
//인수로 전달받은 보조 함수에 상태 변경을 위임한다.
counter = aux(counter);
return counter;
};
}());
// 보조 함수
function increase(n) {
return ++n;
}
// 보조 함수
function decrease(n) {
return --n;
}
// 함수로 함수를 생성
// makeCounter함수는 보조 함수를 인수로 전달받아 함수를 반환한다.
console.log(counter(increase));
console.log(counter(increase));
// 자유 변수를 공유
console.log(counter(decrease));
console.log(counter(decrease));
캡슐화와 정보 은닉
캡슐화란?
프로퍼티와 메서드를 하나로 묶는 것
1. 프로퍼티 : 객체의 상태(state)
2. 메서드 : 프로퍼티를 참조하고 조작할 수 있는 동작(behavior)
정보 은닉이란?
객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용
1. 객체의 상태 변경을 방지함으로써 정보 보호
2. 객체 간의 의존성(결합도 - coupling)을 낮춤
함수 레벨 스코프와 블록 레벨 스코프
함수 레벨 스코프(Function-level Scope)
함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다. 즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수이다.
블록 레벨 스코프(Block-level Scope)
모든 코드 블록(함수,if문,for문,while문,try/catch문 등)내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다. 즉, 코드 블록 내부에서 선언한 변수는 지역 변수이다.
javascript 퀴즈
문제: for문을 사용하여, 0부터 30 미만의 정수 중, 짝수만을 출력(console.log)해주세요.
for (i = 0; i < 30; i++) {
if ( i % 2 == 0 ) {
console.log(i);
};
};
처음에는 if문에 ( i / 0 == 0)으로 작성하고 출력 값이 0 하나뿐이라서 어리둥절했었는데 / 와 % 연산자가 같은 것으로 착각하고 있었다. ㅋㅋㅋㅋ.... 이번 기회에 확실하게 기억해 두기!
console.log(4 / 2) // 나누기 연산자. 2를 출력
console.log(10 % 3) // 나머지 연산자. 1을 출력
문제: while문을 사용하여, 0부터 30 미만의 정수 중, 짝수만을 출력(console.log)해주세요.
let i = 0;
while (i < 30) {
i++;
if (i % 2 == 0){
console.log(i)
}
}
문제 상황: 2~30까지의 짝수만 출력되고 0이 사라졌다. (근데 여기서 근본적인 의문.. 0은 짝수인가?)
let i = 0;
while (i < 30) {
if (i % 2 == 0){
console.log(i)
}
i++;
}
어쨌든 i++을 if문 밑으로 위치를 옮기니까 문제가 해결되었다.
문제: for문을 사용하여, 0부터 30 미만의 정수 중, 짝수만을 문자열로 출력(console.log)해주세요.
for (i = 0; i < 30; i++) {
if (i%2 == 0) {
int evenNum = i;
String stringNum = String.valueOf(evenNum);
console.log(stringNum)
}
}
구글링 검색하면서 어떻게든 풀어보려 했지만...ㅎㅎ
문제 풀어서 제출한 후에 팀원들과 답을 맞춰 봤는데 그 과정에서 드디어 방법을 알아냈다!
for (i = 0; i < 30; i++) {
if (i%2 == 0) {
console.log(i.toString());
console.log(typeof(i));
}
}
근데 아직도 i가 문자열이 아닌 숫자 타입으로 뜬다. 뭐가 문제일까?
for (i = 0; i < 30; i++) {
if (i%2 == 0) {
i = i.toString()
console.log(i);
console.log(typeof(i));
}
}
팀원 분들의 도움을 받아서 i 에 i.toString()을 할당한 후에 i를 출력하니 원하는 대로 짝수가 문자열로 출력되었다.
숫자를 문자열로 출력하는 방법
toString 메서드 사용하기
기본 문법 number.toString(radix)
var num = new Number(10);
alert(num.toString()); // string, '10'
alert(num.toString(2)); // string, '1010', 2진수
alert(num.toString(8)); // string, '12', , 8진수
alert(num.toString(16)); // string, 'a', ' 16진수
추가로 숫자인지 문자열인지 확인하는 방법
console.log(typeof(i));
출처 https://opentutorials.org/course/50/135
sort 함수 화살표 함수로 간단하게 쓰기
// 기본 함수
array.sort(function(a, b) {
return a - b;
});
// 화살표 함수 - 중괄호랑 return 생략 가능!
array.sort((a,b)=>a-b)
팀원분이 알려주신 꿀팁! sort 함수 간단하게 작성하기
programmers 문제 풀기
숫자 비교해서 같으면 1, 다르면 -1 출력
function solution(num1, num2) {
if (num1 === num2) {
return 1;
} else {
return -1;
}
}
내가 처음에 작성했던 코드. 교과서에서 가져온 듯한 정석적(?)인 코드다.
다른 사람들의 코드를 보다가 삼항 연산자를 사용하면 코드가 굉장히 간단해진다는 걸 깨달았다!
function solution(num1, num2) {
return num1 === num2 ? 1 : -1;
}
그래서 수정해 본 코드. 코드가 짧고 간결해졌다.
회고
4일에 걸쳐서 자바스크립트 심화 강의를 다 들었다! 왜인지 강의를 듣기 전보다 자바스크립트와 멀어진 기분이 들지만😅 당연히 강의를 들으면서 배운 점이 많다. 강의를 들으면서 대부분의 내용을 이해하지 못했다고 생각했지만 강의 후반부로 갈수록 앞에서 배웠던 내용들과 결합되며 맞아 이렇다고 하셨지~ 기억나는 부분도 있었고, 나중에 JS 코드를 작성하며 문제를 만났을 때 이거 그때 배웠던 그거 아닌가? 정도의 수준이면 충분하다고 생각한다. 어차피 나는 코딩을 배운 지 거의 2달도 안 된 사람이기 때문에 모든 걸 이해하려는 욕심을 버리면 마음이 편안해진다는 것을 깨달았다ㅋㅋ
오늘은 오후에 처음으로 자바스크립트 퀴즈 시간이 있었다. 2시간 동안 11문제를 풀고 제출하는 방식이었는데 생각보다는 간단한 문제였고(코테 수준으로 한 두 문제 나오는 걸까 예상했었다) DOM이나 SPA 등 개념을 설명하는 문제도 있었다. 답변을 제출하고 나서 팀원들과 다 같이 답을 맞춰 보는 시간을 가졌는데 내가 생각하지 못했던 방식으로 문제에 접근한 팀원들의 풀이 방법을 보는 게 재밌었다ㅋㅋ