원시 자료형과 참조 자료형
원시 자료형: 고정된 저장 공간 사용
참조 자료형: 동적으로 변하는 저장 공간 사용(함수, 배열, 객체 등)
학습 목표
* 원시 자료형과 참조 자료형의 구분이 필요한 이유?
* 원시 자료형과 참조 자료형의 차이?
* 원시 자료형은 변수에 값이 할당되고 참조 자료형은 “주소”가 할당된다
* 참조 자료형은 동적으로 크기가 변하는 특별한 보관함을 사용한다원시, 참조 자료형 쉽게 이해하기
원시 자료형: 값(value)가 stack에 직접적으로 저장되어 있다. 값을 stack 보관함에서 바로 꺼내오는 구조
참조 자료형: 보관함에 값이 저장이 된 것이 아니라 어디를 참조 해라는 “주소”가 저장되어 있다.
그 주소에 해당하는 값은 heap에 저장이 되어 있어서 보관함에서 주소를 보고 heap에서 꺼내 오는 구조(간접적인 저장)
원시 자료형과 참조 자료형의 큰 차이?
원시 자료형은 그 값을 다른 변수가 가져와서 복사하고 수정하더라도 “원본에는 영향을 미치지 않는다”
let a = 60;
let b = a;
b=b+20; //b=80
console.log(a); // a=60;반면, 참조 자료형은 그 값을 다른 변수가 가져와서 복사하고 수정하면 “원본도 변한다”
→ 원본도 “주소의 위치”를 가지고 있는 것이기 때문에 결국 heap의 찐 데이터 값을 수정한 것이기 때문
let a =[1,2,3,5,6];
let b=a;
b.pop(); //b=[1,2,3,5]
console.log(a); // a=[1,2,3,5]원시 자료형에 대한 심화 이해
원시 자료형: string, number, boolean, undefined, (null), bigint, symbol
→ 변수에 “하나”의 데이터 만을 담고 있다.
→ 데이터 보관함 한 칸에 하나의 데이터만 담을 수 있는 “원시적인” 방식
→ 원시 자료형은 값(value) 자체에 대한 변경이 불가능(immutable)하다.
다만, 변수에 다른 값을 할당해서(재할당) 변수에 담긴 내용을 변경할 수는 있다.
"hello world!"
"hello code!"
// "hello world!" 와 "hello code!"는 모두 변경할 수 없는 고정된 값
let word = "hello world!"
word = "hello code!"
// 하지만, word라는 변수에 재할당을 하여 변수에 담긴 내용을 변경은 가능
const num1 = 123;
num1 = 123456789; // 에러 발생
// const 키워드로 선언하면, 재할당은 불가참조 자료형에 대한 심화 이해
참조 자료형에는 하나의 데이터가 아닌 “여러 데이터”가 담기게 된다
→ 여러 데이터 자체는 특별한 보관함인 heap에 저장된다
→ 참조 자료형의 변수에는 해당 heap을 찾아갈 수 있는 주소(reference)가 저장되는 개념
참고1)
let x = { foo: 3 };
let y = x; // y에 객체의 주소를 할당했다 y={foo:3};
y = 2 // y에 원시 자료형의 값인 2를 재할당 y=2;
x.foo? // y는 원시 자료형의 값을 할당했으므로 x에는 영향x → x.foo=3;
참고2)
let score = 80; // 변수에 number(원시 자료형) 값 할당;
function doStuff(value) { // 함수(참조 자료형)
value = 90;
}
doStuff(score) // 해당 함수가 돌아가면 score가 원시 자료형이기 때문에 value에 score의 주소가 아닌 값을 가져옴
// value=90; 이 되고 score에는 초기값인 80이 유지됨
// score=80;
스코프(scope)
스코프란 영어 단어로는 “범위”라는 의미
→ js에서는 “변수의 유효범위” 로 사용
학습 목표
* 스코프의 의미와 적용범위?
* 스코프의 주요 규칙
- 중첩 규칙
- block scope와 functioin scope
- 전역 스코프와 지역 스코프
- 전역 변수와 지역 변수 간의 우선 순위?
- 선언 키워드 let, const, var의 차이
- 전역 객체(window)의 이해스코프의 정의와 주요 규칙
정의: 변수 접근 규칙에 따른 유효 범위
주요 규칙
1. 안쪽 스코프에서 바깥쪽 스코프로는 접근할 수 있지만 그 반대는 불가능
2. 스코프는 중첩이 가능
3. 가장 바깥의 스코프: 전역 스코프(Global Scope) ↔ 지역 스코프(Local Scope): 그 외
4. 지역 변수가 전역 변수보다 높은 우선순위
ex1)
let name='kim'; // 전역 변수 선언 및 할당
function showName(){
let name='lee'; // 지역 변수 선언 및 할당
// 동일한 변수 이름으로 인해 바깥쪽 변수가 안쪽 변수에 의해 가려지는(shadow) 이러한 현상을 “쉐도잉”(variable shadowing)이라 함
https://share.getliner.com/JX9kSC
console.log(name);
}
console.log(name); // kim ← 아직 함수를 타기 전
showName(); // lee
console.log(name); // kim ← 지역 변수가 아닌 전역 변수 꺼를 타므로
ex2)
let name='kim'; // 전역 변수 선언 및 할당
function showName(){
name='lee'; // 값을 재할당
console.log(name);
}
console.log(name); // kim ← 아직 함수를 타기 전
showName(); // lee
console.log(name); // lee ← 함수로 인해 변수 name의 값이 변경됨
스코프의 종류와 선언 키워드 let, const, var
1.블록 스코프(block scope)
중괄호 `{}`를 기준으로 범위가 구분됨: 중괄호로 둘러싼 범위
2.함수 스코프(function scope)
function 키워드가 등장하는 함수 선언식(함수 표현식)은 함수 스코프를 생성: 함수로 둘러싼 범위
**cf. 화살표 함수는 블록 스코프로 취급(함수 스코프x)**
블록 스코프의 규칙
ex) 변수 선언 키워드로 let 사용
for(let i=0; i<5;i++){
console.log(i) //5번 반복
}
console.log(‘final i =’, i); //RefereceError
// i는 지역변수 이므로 출력 불가
ex) 변수 선언 키워드로 var 사용
for(var i=0; i<5;i++){
console.log(i) //5번 반복
}
console.log(‘final i =’, i); // ?
// 5; var 키워드는 블록 스코프를 무시var 키워드
var 키워드는 블록 스코프를 무시하고 함수 스코프만 따른다
→ block 범위를 벗어나도(같은 function scope 에서는) 사용이 가능!
→ 단, “화살표 함수”의 블록 스코프는 무시하지 않는다
블록 단위로 스코프를 구분했을 때(들여쓰기 하므로), 훨씬 더 가독성 있고 예측 가능한 코드 작성이 가능하므로 let 키워드의 사용을 권장!
또한, var 키워드는 재선언을 해도 아무런 에러를 내지 않지만(정상), let 키워드는 재선언을 방지(에러)
→ 실제 코딩을 할 때 변수를 재선언하는 경우가 있는가? 대부분 버그이기 때문에 let은 버그 방지!
const 키워드
값이 변하지 않는 ‘상수’를 정의할 때 쓰는 const(constant)
값의 재할당이 불가능
let 키워드와 동일하게 “블록 스코프”를 따름
값의 변경을 최소화하여 보다 안전한 프로그램 제작 가능. 값을 새롭게 할당할 일이 없다면 const 키워드의 사용을 권장
값을 재할당하는 경우 TypeError!
→ 의도치 않은 값의 변경을 방지!
let, var, const 키워드 비교
let const var
============================================================
유효범위 블록/함수scope 블록/함수 scope 함수 scope
------------------------------------------------------------
값 재할당 가능 X 가능
------------------------------------------------------------
재선언 X X 가능
개발자 콘솔을 통한 연습
breakpoint 사용 해보기, var 키워드 let 키워드 사용 해보기
function greetSomeone(firstName){
let time='night';
if(time==='night'){
let greeting ='Good Night';
debugger; // debugger; 명령어로 디버깅 가능
}
return greeting+' '+firstName; // greeting이 if 구문의 블록 스코프 안에서만 유효하므로 ReferenceError!
}
greetSomeone('Steve');
변수 선언에서 주의할 점
window 객체(브라우저 only)
브라우저에만 존재하는 window 객체
→ 브라우저 창을 대표하는 객체
→ 그러나, 브라우저 창과 관계없이 전역 항목도 담고 있음
”var”로 선언된 전역 변수와 함수 선언식(표현식)으로 선언된 전역 함수가 window 객체에 속함
전역 변수는 최소화할 것
전역 변수에 너무 많은 변수를 선언하지 마라!
→ 편리한 대신, 다른 함수 또는 로직에 의한 의도치 않은 변경이 발생 가능(부수효과: side effect)
→ 전역 변수 최소화는 side effect를 줄이는 좋은 방법!
let, const를 주로 사용하기
var가 가진 특징(재선언 가능, 블록 스코프 무시)때문에 버그 유발 가능
var로 선언한 전역 변수가 window 기능을 덮어 씌워서 내장 기능을 못 쓰게 만든다
선언 없는 변수 할당 금지
선언 키워드(let, var, const) 없이 변수 할당하지 말기
ex)
function showAge(){
age=90; // 선언 키워드 없이 값을 할당하면 마치 var로 선언된 전역 변수처럼 작동
console.log(age); // 90
}
showAge();
console.log(age); // 90
console.log(window.age); // 90
→ 선언 없이 사용하는 실수 방지를 위해 strict mode 사용 가능
strict mode는 브라우저가 엄격하게 작동하도록 만들어 줌
ex)
‘use strict’; // js 파일 상단에 ‘use strict’ → strict mode 사용
function showAge(){
age=90; // 선언 키워드 없이 넣었으므로 캐치하여 ReferenceError!
console.log(age);
}
showAge();
console.log(age);
console.log(window.age);
클로저(closure)
자바스크립트만이 가지는 특이한 특성
"함수와 함수가 선언된 어휘적(lexical) 환경의 조합을 말한다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다."
→ 자바스크립트(js)는 함수가 호출되는 환경과 별개로, 기존에 선언되어 있던 환경-어휘적 환경-을 기준으로 변수를 조회하려고 함
학습 목표
* 클로저 함수의 정의와 특징: 외부 함수의 변수에 접근할 수 있는 “내부 함수”; 클로저
* 클로저가 갖는 스코프 범위
* 클로저를 이용한 유용한 코딩 패턴
클로저(closure) 함수의 특징
1.함수를 리턴하는 함수의 형태
리턴값이 함수
→ 즉, 함수와 함수가 선언된 형태
ex)
const adder = x =>y =>x+y //화살표 함수로 표현한 함수
// 위와 동일한 코드의 함수 표현식
const adder=function(x){
return function(y){ // 리턴값이 함수의 형태이다!
return x+y;
}
}
2.외부 함수의 변수에 접근 가능한 “내부 함수”
→ 그 “내부함수”를 클로저라 한다!
리턴하는 함수에 의해 스코프(변수의 접근 범위)가 구분됨
클로저의 핵심은 스코프를 이용해서 변수의 접근 범위를 닫는(closure; 폐쇄) 것에 있다
→ 변수를 선언하는 곳이 중요!
ex) 외부 함수와 내부 함수
const adder = function(x){ // 외부 함수의 변수x
return function(y){ // 내부 함수의 변수y
return x+y; // 내부 함수
}
} // 외부 함수
→ 외부 함수는 y에 접근이 가능? 접근이 불가(바깥 스코프에서는 안쪽 스코프 접근x)
→ 내부 함수는 x에 접근이 가능? 접근이 가능(안쪽 스코프에서는 바깥 스코프의 변수에 접근가능)
클로저의 활용
“데이터를 보존하는 함수”
일반적인 함수는 함수 실행이 끝나면 함수 내부의 변수를 사용이 불가
→ 반면에 클로저는 외부 함수의 실행이 끝나더라도 외부 함수 내 변수가 메모리 상에 저장됨(어휘적 환경을 메모리에 저장하기 때문)
ex) 외부 함수(adder)의 실행이 끝나도, 외부 함수 내 변수인 x를 사용 가능
const adder = function(x) {
return function(y) {
return x+y;
}
}
const a5=adder(5); // adder(외부 함수)의 실행이 끝나도 5라는 값은 사용 가능
// a5는 adder 함수에서 인자로 넘긴 5라는 값을 x 변수에 계속 담은 채로 있음
// 따라서 다음과 같이 활용 가능
a5(7); // 12 → a5=adder(5) 이므로 adder(5)(7) → 7은 변수y에 대응됨
a5(10); // 15 → adder(5)(10) → 10은 변수y에 대
보다 실용적인 활용도 가능
ex) HTML 문자열 생성기
const tagMaker = tag => text => `<${tag}>${text}</${tag}>`
const divMaker = tagMaker(‘div’); // divMaker 함수는 ‘div’ 문자열을 tag변수에 담아두고 있는 상태
divMaker(‘hello’) // <div>hello</div>
divMaker(‘code’) // <div>code</div>
const anchorMaker = tagMaker(‘a’); // anchorMaker 함수는 ‘a’ 문자열을 tag 변수에 담아두고 있는 상태
anchorMaker(‘go’) // <a>go</a>
anchorMaker(‘to’) // <a>to</a>
→ 이처럼 클로저를 활용하면 특정 데이터를 스코프 안에 가두어 둔 채로 계속 사용할 수 있다
정보의 접근 제한(캡슐화)
클로저 모듈 패턴
ex) 클로저 모듈 패턴의 예
const makeCounter = () => {
let value = 0;
return {
increase: () => { // 내부 함수를 객체({a: 2} 형태)에 담아 여러 개 만듦
value=value+1
},
decrease: () => { // 객체 안에 여러 개의 내부 함수가 들어간 구조
value=value-1
},
getValue: () => value
}
}
const counter1=makeCounter();
counter1 // 객체(Object) // {increase: 함수1, decrease: 함수2, getValue: 함수3}
위에서 makeCounter 함수를 바꾸지 않고 value 변수에 값을 재할당 가능?
→ 스코프 규칙에 의해 불가능. 대신, 리턴하는 객체가 제공하는 메소드를 통한 간접적 조작은 가능
(counter1.increase(); 이런 식으로 조작은 가능할 듯?)
→ 이것이 정보의 접근 제한(캡슐화)
캡슐화를 하는 이유?
캡슐화하지 않았으면 value 변수가 전역변수여야 함. 전역 변수는 side effect 발생 가능성을 높임
→ 불필요한 전역 변수를 줄여서 side effect 방지
모듈화(재활용이 가능)
앞선 예제에서 여러 개의 counter 생성이 가능
const counter1=makerCounter();
counter1.increase(); // 1증가
counter1.increase(); // 1증가
counter1.decrease(); // 1 감소
counter1.getValue(); // value = 0+1+1-1= 1;
const counter2=makeCounter();
counter2.decrease(); // 1감소
counter2.decrease(); // 1감소
counter2.decrease(); // 1 감소
counter2.getValue(); // value = 0-1-1-1=-3;
// 주의할 점이 counter1와 상관없이 counter2가 makerCounter를 할당 받았을 때 value는 0임
// makeCounter에 의해 리턴된 객체는 makeCounter를 실행할 때 선언되는 value 값을 각각 독립적으로 가짐.
// counter1과 counter2의 value는 서로에게 영향을 끼치지 않음(서로 독립적)
→ 함수 하나를 독립적인 부품 형태(모듈)로 분리하여 재사용성을 극대화 하는 게 모듈화
→ 클로저를 통해 데이터와 메소드를 같이 묶어서 활용 가능하므로 클로저는 모듈화에 유리
클로저의 단점
일반 함수였다면 함수 실행 종료 후 가비지 컬렉션(참고 자료: MDN '자바스크립트의 메모리 관리') 대상이 되었을 객체가, 클로저 패턴에서는 메모리 상에 남아 있게 됨
→ 클로저를 남발하면 퍼포먼스 저하
심화 학습: 스코프 고급
자바스크립트의 작동 원리에 대한 이해(기술 면접)
* Strict Mode
* 즉시 실행 함수 표현식(IIFE)
* 변수 호이스팅과 TDZ(Temporal Dead Zone)Strict Mode: 엄격 모드(스크립트 최상단에 입력)
자바스크립트는 기존 기능을 변경하지 않으면서 새로운 기능이 추가되며 발전되어 옴
ECMAScript5(ES5) 가 등장하면서 새로운 기능이 추가되고 기존 기능 중 일부가 변경됨
→ 기존 기능이 변경되면서 하위 호환성 이슈가 발생
→ 그래서 변경 사항 대부분은 ES5 기본 모드에서는 비활성화 되도록 설계
→ 대신 ‘use strict’로 엄격 모드 활성화 시에만 변경 사항이 활성화 됨
cf. 보통 스크립트 최상단 위치가 국룰이나, 함수 본문 맨 앞에 쓸 수도 있다(이 때는 그 함수만 엄격 모드 적용)
* ‘use strict’ 는 반드시 최상단에 위치 // 오로지 주석만 그 위에 가능
* ‘use strict’는 취소 불가 // 한번 엄격 모드가 적용되면 되돌릴 수 없다’use strict’를 꼭 사용해야 하나?
→ 엄격 모드를 활성화 하면 스크립트 전체가 “모던한” 방식으로 실행된다. 진일보한 현대적인 방식으로 돌아간다는 뜻. 모던 js는 클래스와 모듈이라는 구조를 제공함
→ 클래스와 모듈을 사용하는 경우 자동으로 ‘use strict’ 가 적용됨. 즉 이 때는 명시하지 않아도 됨
선언 키워드 var와 변수 호이스팅
선언 키워드 var는 구식 스크립트의 잔재이다.
var는 블록 스코프를 뛰어 넘는다는 것을 인지해야 함
var 선언은 함수가 시작될 때 처리되므로 var로 선언한 변수의 선언 위치가 어디든 상관없이 “함수가 시작되는 지점”에서 정의된다.
ex1)
function sayHi() { (*)
phrase = "Hello";
alert(phrase);
var phrase; // 뒤에서 선언했으나, 상관없이 함수 시작 지점(*)에서 정의
}
sayHi();
ex2)
function sayHi() {(*)
var phrase; // 함수 시작 지점(*)에서 정의
phrase = "Hello";
alert(phrase);
}
sayHi();
→ 위의 ex1)과 ex2)는 동일하게 동작.
이처럼 변수가 끌어올려지는 현상; 호이스팅(hoisting) 이라고 한다.
(Hoist;끌어올리다)
→ 단, 선언은 호이스팅되지만 할당은 호이스팅되지 않는다!var phrase =”hello” 처럼 선언과 할당을 동시에 해도 선언만 호이스팅되고 할당은 그 할당 위치에서 처리됨
즉시 실행 함수 표현식(IIFE)
var도 블록 레벨 스코프를 가질 수 있도록 고안한 방식
요즘에는 쓰지 않는 방식이나 오래된 스크립트에서 발견할 수 있다
ex) 즉시 실행 함수 표현식(IIFE)의 예
(function() {
let message = "Hello";
alert(message); // Hello
})();
→ 함수 표현식을 괄호로 둘러쌓아 (function {…})와 같은 형태
함수를 괄호로 감싸면 js가 함수를 함수 선언문이 아닌 표현식으로 착각해서 인식함
함수 표현식은 이름이 없어도 되고, 즉시 호출도 가능
참고 // IIFE를 만드는 방법
(function() {
alert("함수를 괄호로 둘러싸기");
})();
(function() {
alert("전체를 괄호로 둘러싸기");
}());
!function() {
alert("표현식 앞에 비트 NOT 연산자 붙이기");
}();
+function() {
alert("표현식 앞에 단항 덧셈 연산자 붙이기");
}();
→ 모던 js에서는 굳이 쓸 필요 없으니깐 참고만 하면 됨(구식 스크립트의 잔재: var 때문)
호이스팅과 TDZ(Temporal Dead Zone; 일시적 사각지대)
호이스팅(Hoisting)이란, var 선언문이나 function 선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성
→ 모든 선언;var, let, const, function, class 등이 호이스팅됨
그런데, let으로 선언된 변수를 선언문 이전에 참조하면 참조 에러가 발생
→ let으로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(TDZ)에 빠지기 때문
ex) TDZ의 예
console.log(bar); // Error: Uncaught ReferenceError: bar is not defined
let bar;TDZ를 이해하려면 변수의 생성 단계를 이해해야 함
<변수의 생성 3단계>
1. 선언 단계
변수를 실행 컨텍스트의 변수 객체에 등록. 이 객체는 스코프가 참조하는 대상이 됨
2. 초기화 단계
변수 객체에 등록된 변수를 위한 “공간”을 메모리에 확보. 이 단계에서 변수는 undefined로 초기화됨
3. 할당 단계
undefined로 초기화된 변수에 실제 값(value)을 할당함→ var의 경우 선언 단계와 초기화 단계가 동시에 이루어짐
→ 반면, let으로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행
즉, 선언 단계와 초기화 단계의 사이가 일시적 사각지대(TDZ)가 되어서 초기화 전에 변수에 접근 시 참조 에러가 발생
// 스코프의 선두에서 선언 단계가 실행된다.
// 아직 변수가 초기화(메모리 공간 확보와 undefined로 초기화)되지 않았다.
// 따라서 변수 선언문 이전에 변수를 참조할 수 없다.
console.log(foo); // ReferenceError: foo is not defined
let foo; // 변수 선언문에서 초기화 단계가 실행된다.
console.log(foo); // undefined
foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1
checkpoint 추가 설명
js 에서는 함수의 전달 인자를 초과하는 인자를 전달했을 때
→ 에러가 나지 않고 무시한다 // 겉으로는 정상 작동
함수의 전달 인자가 필요한데 없이 호출한 경우
→ 에러가 나지 않고 undefined를 리턴
함수 내에서 매개변수는 그 안에서의 지역 변수로 취급된다
function(x) { // 함수 내의 매개변수 x는 그 안에서의 지역변수로 취급됨!
return result=x;
}클로저의 개념?
클로저 자체는 그 현상이라고 봐야 한다. 클로저 함수가 클로저는 아니다
클로저 함수가 구현되는 그 과정과 현상; 클로저
const adder = function(x) {
return function(y) {
return x+y;
}
}
→ 외부 함수의 변수에 접근 가능한 내부 함수; 클로저 함수
클로저 함수와 같은 현상을 통칭하는게 클로저(즉 클로저는 함수 외에도 발견될 수 있다)
단순히 함수가 중첩되어 있다고 클로저 함수는 아니다
→ 외부 함수의 변수에 접근 가능한 내부 함수가 있어야 하고, 리턴 값으로 함수값을 사용해야 한다
클로저 정의에 대한 다양한 해석들; 명확한 정의가 없다
→ 그나마 이해될 정의? 함수의 실행 컨텍스트가 종료된 후에도 해당 변수가 사라지지 않는 현상;클로저
실제로 코딩할 때 비슷한데 디테일이 다른 함수가 계속 쓰이는 경우?
→ 이 부분을 클로저(closure)로 만들면 좋다!
→ 코드를 간략화 가능
'SE Bootcamp 내용 정리' 카테고리의 다른 글
| 얕은 복사, 깊은 복사 내용에 대한 정리? (0) | 2021.09.10 |
|---|---|
| js/node - spread/rest 문법 (0) | 2021.09.09 |
| css - 레이아웃, selector (0) | 2021.09.07 |
| js/node - 배열, 객체 checkpoint (0) | 2021.09.06 |
| js/node - 배열, 객체 (0) | 2021.09.03 |
