자료형은 크게 두 가지 자료형(primitive type)과 참조 자료형(reference type)로 구분할 수 있다.
JS에서는 6개의 자료형(number, string, boolean, undefined, null, symbol)을 원시 자료형으로 구분한다.
이중 symbol 타입은 잘 사용되지 않는 타입이다.
// 원시 자료형(primitive type) - number, string, boolean, undefined, null
42, 'string', true, undefined, null
원시 자료형이 아닌 모든 자료형은 참조 자료형이다.
배열, 객체가 대표적인 참조 자료형이며, 함수도 포함된다.
// 참조 자료형(reference type)
[0, 1, 2] // 배열
{name: 'kimcoding', age: 45} // 객체
function sum (x, y) { return x + y } // 함수
JavaScript에서의 스코프는 "변수의 유효범위"로 사용됩니다.
Q. 콘솔에 순서대로 출력되는 결과는 무엇일까?
let greeting = 'Hello'; function greetSomeone() { let firstName = 'Josh'; return greeting + ' ' + firstName; } console.log(greetSomeone()); // ? console.log(firstName); // ?
// A.
'Hello Josh'
ReferenceError
greeting 변수는 바깥 스코프에 정의되어 있으므로, 함수 안쪽에서 사용할 수 있다.
따라서 greeting 변수와 firstName 변수의 조합에 의해 'Hello Josh' 문자열이 출력된다.
반면에, firstName 변수는 안쪽 스코프에 정의되어 있으므로 바깥쪽에서는 접근이 불가능하다.
따라서 ReferenceError를 내게 된다.
그 이유는 var를 사용하는 것은 살짝 위험하다고 할 수 있기 때문이다.
var 키워드는 재선언을 해도 에러가 나지 않는다.
반면에, let const은 그렇기 않기에 let const이 더 안전하다고 할 수 있다.
클로저는 함수와 그 함수 주변의 상태의 주소 조합이다.
조금 더 쉽게는, 클로저는 함수와 그 함수가 접근할 수 있는 변수의 조합을 뜻한다.
Q. 아래 코드에서 message의 값은 무엇일까?
function outerFn() { const innerFn = function() { const message = 'outerFn은 message에 접근할 수 있습니다.'; } return message; } const message = outerFn();
A. 'outerFn은 message에 접근할 수 있습니다.'
B. 알 수 없음
// A.
답은 B번이다.
outerFn을 호출할 때, message를 리턴하려고 시도하지만 message는 innerFn의 스코프
즉, 내부 함수의 스코프에 있기 때문에 접근이 불가하여 ReferenceError가 난다.
→ 만약 `outerFn` 이 `message` 를 리턴하지 않고, `innerFn` 을 리턴했다면 결과는 바뀐다.
최종적으로 message 값은 outerFn() 이었을 것이다.
result 값은?
let x = 10;
function outer () {
x = 20;
function inner () {
let x
x = x + 20;
return x;
}
inner();
}
outer();
let result = x;
답은 20이다.
outer 함수는 전역 변수 x에 20을 재할당합니다. 따라서 result의 값은 20이 됩니다.
outer 내부에서 inner 함수가 호출되고 있긴 하지만, inner 함수는 바깥 스코프에 아무런 영향을 미치지 않는다.
다음 중 틀린 것은?
const Subject = function () {
const observers = [];
return {
subscribeObserver: function (observer) { observers.push(observer); },
unsubscribeObserver: function (observer) {
const index = observers.indexOf(observer);
if (index > -1) { observers.splice(index, 1); }
},
notifyObserver: function (observer) {
const index = observers.indexOf(observer);
if (index > -1) { observers[index].notify(); }
},
notifyAllObservers: function () {
for (let i = 0; i < observers.length; i += 1){
observers[i].notify()
}
},
};
};
const Observer = function (observerName) {
let name = observerName;
return {
getName: function (name) { console.log("Observer Name:" + name); },
notify: function () { console.log("Observer " + name + " is notified!"); },
};
};
const subject = Subject();
const kimcoding = Observer('kimcoding');
const parkhacker = Observer('parkhacker');
subject.subscribeObserver(kimcoding);
subject.subscribeObserver(parkhacker);
subject.unsubscribeObserver(kimcoding);
subject.notifyAllObservers();
-----------------------------------------------------------------------------------------------
A. 콘솔 출력 결과는 `Observer parkhacker is notified!`이다.
B. 코드 실행이 모두 끝나면 `parkhacker`는 `observers` 배열의 요소다.
C. 코드 실행이 모두 끝나면 `kimcoding`과 `parkhacker`는 `observers` 배열을 조회할 수 있다.
D. 코드 실행이 모두 끝나면 `kimcoding` 과 `parkhacker`는 다른 주솟값을 가진다.
답은 C이다.
이번 문제의 코드는 Observer 디자인 패턴이 적용되었다.
subject 객체의 subscribeObserver 함수를 사용하여 observer(kimcoding, parkhacker)를 등록하면,
향후 notifyAllObservers 함수를 통해 자동으로 kimcoding 과 parkhacker 의 notify 를 호출할 수 있다.
가장 쉬운 Observer 패턴의 예는 이벤트 핸들러이다.
이벤트 핸들러를 특정 이벤트가 특정 요소에서 발생할 때 작동하도록
“구독(addEventListener)”해두면 브라우저는 이벤트 발생 시 이벤트 핸들러를 호출한다.
구독을 취소(removeEventListener)하면 해당 이벤트는 발생하지 않는다.
이렇게 Observer 패턴은 한 동작을 구독한 여러 부분에 작동시킬 때 유용하다.
kimcoding 과 parkhacker 는 observers 배열의 요소로 포함되지만,
서로 다른 스코프에 위치하기 때문에 observers 배열을 직접 조회할 수 없다.
Observer 함수를 똑같이 이용했지만, 완전히 새로운 객체의 주솟값이 각각 담기기 때문에
kimcoding 과 parkhacker 는 서로 다른 주솟값을 갖는다.
만약 같은 주솟값을 가지면, observers 가 [kimcoding, parkhacker] 인 경우
subject.unsubscribeObserver(parkhacker); 를 실행해도 같은 주솟값을 가진 kimcoding 이 배열에서 삭제된다.