[TIL] Day13
[SEB FE] Day13
: 객체가 아니면서 method를 가지지 않는 6가지의 타입
👉🏻 number
, string
, boolean
, undefined
, null
, symbol
, bigint
// 변수에 재할당해서 변수에 담긴 내용 변경 가능
let word = "hello!"
word = "hello hi!"
----------
// const로 선언하면 재할당 불가
const num = 123;
num = 1234567; // error
----------
let x = 2;
let y = x;
y = 3;
// x === 2
// 숫자 데이터는 원시 자료형으로, 이를 할당하는 경우엔 그 값 자체를 변수에 할당
// 즉, 그 값을 복사하여 변수에 저장
// x는 원시 자료형이므로 x의 값인 2를 y에 복사하여 할당되고, y에 3이 재할당되어도 x는 여전히 2
// x의 값을 y로 복사하여 할당했기 때문
: js에서는 원시 자료형이 아닌 모든 것은 참조 자료형
👉🏻 배열([]
), 객체({}
), 함수(function(){}
)
데이터는 별도로 관리되고, 우리가 직접 다루는 변수에는 주소가 저장
하나의 데이터가 아닌 여러 데이터가 담기게 됨
참조 타입 데이터는 주소를 복사 → 주소 안에 있는 데이터가 변경 → 기존 데이터에 영향 O
reference
: 변수가 가리키고(refer) 있는 데이터를 참조한다는 의미
⇒ 변수의 주소를 ‘참조'하여 실제 변수가 있는 장소에 어떤 데이터가 있는지 도착하고 나서야 읽을 수 있음.
let first = [10, 20, 30, 40];
let second = first;
second[0] = 5;
// first === second -> true
// first[0] === 5
// first & second는 같은 주소 공유
// first & second는 배열이므로 참조 자료형
let x = { foo: 3 };
let y = x; // { foo: 3 }의 주소를 y에 할당
y.foo = 2; // 값이 3이었던 y.foo에 2를 할당 -> y.foo와 x.foo는 같은 주소를 바라보고 있으므로
x.foo; // x.foo도 2가 됨
y = 2; // y에 2 할당 -> 참조 자료형 {foo: 3}에는 영향X
x.foo; // 3
----------
let myArray = [2, 3, 4, 5];
let ourArray = myArray; // 변수 ourArray에 myArray의 주소값 할당
ourArray[2] = 25; // ourArray 주소값에 위치한 배열의 2번째 요소를 25로 변경
// myArray; -> [2, 3, 25, 5]
ourArray = undefined; // ourArray에 원시 자료형인 undefined 할당 -> myArray에 접근X
// myArray; -> 그대로 [2, 3, 25, 5]
let player = { score: 3 };
function doStuff(obj) {
obj.score = 2;
}
doStuff(player); // player.score; -> 2
----------
let score = 80;
function doStuff(value) { // 매개변수 value에 score 값 80이 전달 (주소값은 전달X)
value = 90; // value에 90 할당
}
doStuff(score); // score; -> 80
// 주소값은 전달하지 않으므로 함수에서 어떤 일이 발생했던가와 관련 없이 score는 초기에 할당된 값 80이 그대로 유지
console.log([1, 2, 3] === [1, 2, 3]); // false
[Summary]
✔️ 원시 자료형이 할당될 때는 변수에 값(value) 자체가 담긴다.
✔️ 참조 자료형이 할당될 때는 보관함의 주소(reference)가 담긴다.
⇒ 기존에 고정된 크기의 보관함이 아니라, 동적으로 크기가 변하는 특별한 보관함 사용 가능
: 변수 접근 규칙에 따른 유효 범위
✋ 전역 스코프(Global scope): 가장 바깥 스코프 ↔ 지역 스코프(Local scope)
✋ 전역 변수: 전역 스코프에서 선언한 변수 ↔ 지역 변수: 지역 스코프에서 선언한 변수
✅ 전역 변수는 최소화하기
⇒ 🤷♀️ why? 전역 변수는 어디서든 접근 가능한 변수라서 편리하지만 다른 함수나 로직에 의해 의도되지 않은 변경이 발생할 수 있음. → 부수 효과(side effect) 발생
let name = '김코딩'; // 전역변수
function showName() {
let name = '박해커'; // 지역변수
console.log(name); // second(지역변수) -> 박해커
// 지역 변수가 전역 변수보다 우선순위가 높으므로, 지역변수 name 출력
// 쉐도잉(variable shadowing) ⬇️
// : 동일한 변수 이름으로 인해 바깥쪽 변수가 안쪽 변수에 의해 가려지는(shadow) 현상
}
console.log(name); // first(전역변수) -> 김코딩
showName(); // 박해커
console.log(name); // third(전역변수) -> 김코딩
----------
let name = '김코딩'; // 전역변수
function showName() {
name = '박해커'; // let 키워드 사용X -> 전역에 선언된 name 변수 그대로 사용
console.log(name); // second(전역변수: 김코딩->박해커) -> 박해커
// 지역 스코프에서 새로 선언되지 않으면 전역 변수와 같은 변수
}
console.log(name); // first(전역변수) -> 김코딩
showName();
console.log(name); // third(전역변수) -> 박해커
// 전역변수 name의 값 자체가 '김코딩' -> '박해커'로 변경됨
블록 스코프(block scope): 중괄호({})를 기준으로 범위 구분 / 화살표 함수로 둘러싼 범위
let getAge = user => {
return user.age;
}
----------
// let 키워드 사용
for (let i = 0; i < 5; i++) {
console.log(i) // 5번 반복
}
console.log('final i', i); // ReferenceError (block 범위 벗어남)
----------
// var 키워드 사용 -> for문의 블록 스코프 무시하고, 함수 스코프만 따름
for (var i = 0; i < 5; i++) {
console.log(i) // 5번 반복
}
console.log('final i', i); // 5
함수 스코프(function scope): 함수로 둘러싼 범위 (function 키워드 사용) (함수의 실행~종료까지)
let getAge = function (user) {
return user.age;
}
let
키워드: 블록 스코프를 따름
var
보다 let
권장let
은 재선언 방지 → let
이 더 안전var
키워드: 블록 스코프를 무시하고, 함수 스코프만 따름 (화살표 함수의 블록 스코프는 무시X)
재선언을 해도 에러가 발생하지 않음
전역 변수로 var로 선언하는 경우 문제 발생 가능성 ⤴️
- 🤷♀️ why? var로 선언한 전역 변수가 window 기능을 덮어씌워 내장 기능 사용 불가하게 만들 수 있음
✋ var
를 사용하지 않아도, 함수 스코프는 let으로 선언된 변수의 접근 범위를 제한
const
키워드: 값이 변하지 않는 상수(constant)를 정의할 때 사용 → 재할당 불가 ❌
블록 스코프를 따름
값을 재할당할 경우 TypeError → 의도하지 않은 값의 변경 방지
값을 새롭게 할당할 일이 없다면 const
키워드 사용 권장
let | const | var | |
---|---|---|---|
유효 범위 | 블록/함수 스코프 | 블록/함수 스코프 | 함수 스코프 |
값 재할당 | ⭕️ | ❌ | ⭕️ |
재선언 | ❌ | ❌ | ⭕️ |
브라우저에는 window라는 객체 존재
var로 선언된 전역변수 및 전역함수는 window 객체에 속함
함수 선언식으로 함수 선언 / var로 전역 변수 생성시 → window 객체에서도 동일한 값을 찾을 수 있음
var myName = '김코딩';
console.log(window.myName); // 김코딩
function foo() {
console.log('bar');
}
console.log(foo === window.foo); // true
문법적으로 실수할 수 있는 부분들을 에러로 판단
// Strict Mode 적용
'use strict';
function showAge() {
age = 90; // error!! (선언 없는 변수 할당이므로)
console.log(age); // 90
}
showAge();
: 함수와 함수가 선언된 어휘적(lexical) 환경의 조합으로, 외부 함수의 변수에 접근할 수 있는 내부 함수를 뜻함.
✋ 어휘적 환경이란? 변수 및 함수 선언의 형태를 뜻함
✋ JS는 함수가 호출되는 환경과 별개로 기존에 선언되어 있는 환경, 즉 어휘적 환경을 기준으로 변수 조회
const add = (x, y) => x + y;
add(5, 7) // 12
// 함수 호출이 2번 발생
const adder = x => y => x + y;
adder(5)(7); // 12
=====
const adder = function(x) { // x: 외부 함수의 변수
// 리턴값이 함수의 형태
return function (y) { // y: 내부 함수의 변수
return x + y;
}
}
const add5 = adder(5); // 외부함수 실행이 끝나도 x변수 자리에 있는 5 값 사용가능!
add5(7) // 12 (내부함수 내의 y 변수자리에 7 사용)
add5(10) // 15 (내부함수 내의 y 변수자리에 10 사용)
함수를 리턴하는 함수
const adder = x => y => x + y;
adder(5)(7); // 12
typeof adder(5) // 'function'
adder(5) // y => x + y (x=5)
// 두 경우 모두 리턴 값이 함수 형태
// 즉, adder는 '함수를 리턴하는 함수'
외부 함수 & 내부 함수: 리턴하는 함수에 의해 스코프가 구분됨
일반적인 함수는 함수 실행이 끝나면 함수 내부의 변수 사용 불가
클로저는 외부함수 실행이 끝나도 외부 함수 내 변수가 메모리 상에 저장 → 사용 가능
// HTML 문자열 생성기 활용 예제
const tagMaker = tag => content => `<${tag}>${content}</${tag}>`
const divMaker = tagMaker('div');
divMaker('hello'); // '<div>hello</div>'
const anchorMaker = tagMaker('a');
anchorMaker('hi'); // '<a>hi</a>'
// => 클로저는 특정 데이터를 스코프 안에 가두어 둔 채로 계속 사용할 수 있게 해줌
정보의 접근 제한
모듈화: 함수 재사용성을 극대화하여, 함수 하나를 완전히 독립적인 부품 형태로 분리하는 것
클로저를 통해 데이터&메서드를 같이 묶어서 다룸 ⇒ 클로저는 모듈화에 유리
// 클로저 모듈 패턴
const makeCounter = () => {
let value = 0; //counter1&2의 value는 서로에게 영향x
return {
increase: () => {
value = value + 1
},
decrease: () => {
value = value - 1
},
getValue: () => value // 외부함수에 선언된 value 값을 리턴
}
}
const counter1 = makeCounter();
const counter2 = makeCounter();
counter1; // {increase: f, decrese: f, getValue: f}
// counter1은 increase, decrease, getValue 메서드를포함한 하나의 객체
// Q: makeCounter 함수를 변경하지 않고 value 변수에 새로운 값 할당이 가능할까?
// A: 외부 스코프에서 내부 스코프 변수에 접근할 수 없기 때문에 불가능.
// but, 리턴하는 객체가 제공하는 메서드를 통해 value 값을 간접적으로 조작 가능
// => 정보의 접근 제한 (캡슐화)
// makeCounter에 의해 리턴된 객체는, value 값을 각자 독립적으로 가짐
// => counter1의 value와 counter2의 value는 서로 영향x -> 각각의 값 보존 가능