[TIL] Day13 #원시&참조 자료형 #스코프 #클로저

Beanxx·2022년 5월 12일
0

TIL

목록 보기
13/120
post-thumbnail

[TIL] Day13
[SEB FE] Day13

1️⃣ 원시 자료형 & 참조 자료형

📎 원시 자료형(primitive data types; 원시 타입의 데이터)

: 객체가 아니면서 method를 가지지 않는 6가지의 타입

👉🏻 number, string, boolean, undefined, null, symbol, bigint

  • 모두 ‘하나'의 정보 즉, 데이터를 담고 있음.
  • 원시 자료형이 담기는 보관함의 크기는 고정 → 변수에는 데이터 크기와 관계없이 하나의 데이터만 담는다.
  • 값 자체에 대한 변경 불가능(immutable) but, 변수에 다른 데이터 할당 가능
  • 원시 타입 데이터를 복사할 경우엔 기존의 데이터에 영향x
    
    // 변수에 재할당해서 변수에 담긴 내용 변경 가능
    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로 복사하여 할당했기 때문

📎 참조 자료형(reference data type; 참조 타입의 데이터)

: 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)가 담긴다.
⇒ 기존에 고정된 크기의 보관함이 아니라, 동적으로 크기가 변하는 특별한 보관함 사용 가능


2️⃣ 스코프

: 변수 접근 규칙에 따른 유효 범위

✋ 전역 스코프(Global scope): 가장 바깥 스코프 ↔ 지역 스코프(Local scope)

✋ 전역 변수: 전역 스코프에서 선언한 변수 ↔ 지역 변수: 지역 스코프에서 선언한 변수

✅ 전역 변수는 최소화하기
⇒ 🤷‍♀️ why? 전역 변수는 어디서든 접근 가능한 변수라서 편리하지만 다른 함수나 로직에 의해 의도되지 않은 변경이 발생할 수 있음. → 부수 효과(side effect) 발생


📎 스코프 규칙

  1. 바깥쪽 스코프에서 선언한 변수는 안쪽 스코프에서 사용 가능 ⭕️
  2. 안쪽 스코프에서 선언한 변수는 바깥쪽 스코프에서 사용 불가능 ❌ → ReferenceError
  3. 중첩 가능
  4. 지역 변수는 전역 변수보다 더 높은 우선순위를 가짐
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의 값 자체가 '김코딩' -> '박해커'로 변경됨

📎 스코프 종류

  1. 블록 스코프(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
  2. 함수 스코프(function scope): 함수로 둘러싼 범위 (function 키워드 사용) (함수의 실행~종료까지)

    1. var 선언은 함수 스코프의 최상단에 선언
    2. 선언 키워드가 없는 선언은 최고 스코프에 선언 (함수 실행 전까지 선언되지 않은 것으로 취급)
    let getAge = function (user) {
    	return user.age;
    }
  • let 키워드: 블록 스코프를 따름

    • var보다 let 권장
      • 🤷‍♀️ why? var는 재선언을 해도 에러 발생X, let은 재선언 방지 → let이 더 안전
  • var 키워드: 블록 스코프를 무시하고, 함수 스코프만 따름 (화살표 함수의 블록 스코프는 무시X)

    • 재선언을 해도 에러가 발생하지 않음

    • 전역 변수로 var로 선언하는 경우 문제 발생 가능성 ⤴️
      - 🤷‍♀️ why? var로 선언한 전역 변수가 window 기능을 덮어씌워 내장 기능 사용 불가하게 만들 수 있음

        ✋ var를 사용하지 않아도, 함수 스코프는 let으로 선언된 변수의 접근 범위를 제한

  • const 키워드: 값이 변하지 않는 상수(constant)를 정의할 때 사용 → 재할당 불가 ❌

    • 블록 스코프를 따름

    • 값을 재할당할 경우 TypeError → 의도하지 않은 값의 변경 방지

    • 값을 새롭게 할당할 일이 없다면 const 키워드 사용 권장

      letconstvar
      유효 범위블록/함수 스코프블록/함수 스코프함수 스코프
      값 재할당⭕️⭕️
      재선언⭕️

📎 window 객체

  • 브라우저에는 window라는 객체 존재

    • 브라우저 창을 대표하는 객체
    • 브라우저 창과 관계없이 전역 항목 또한 담고 있음
  • var로 선언된 전역변수 및 전역함수는 window 객체에 속함

  • 함수 선언식으로 함수 선언 / var로 전역 변수 생성시 → window 객체에서도 동일한 값을 찾을 수 있음

    var myName = '김코딩';
    console.log(window.myName); // 김코딩
    
    function foo() {
        console.log('bar');
    }
    
    console.log(foo === window.foo); // true

📎 변수 선언시 주의 사항

  • 전역 변수 사용 최소화하기
  • var보다는 let, const를 주로 사용하기
  • 선언 키워드(let, var, const) 없이 변수를 할당하지 말기 (var로 선언한 전역변수 취급)
    • 실수 방지를 위해 Strict Mode 사용 가능
      • 문법적으로 실수할 수 있는 부분들을 에러로 판단

        // Strict Mode 적용
        'use strict';
        
        function showAge() {
        	age = 90;  // error!! (선언 없는 변수 할당이므로)
        	console.log(age);  // 90
        }
        showAge();

 


3️⃣ 클로저(Closure)

 : 함수와 함수가 선언된 어휘적(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 사용)

📎 클로저 함수의 특징

  1. 함수를 리턴하는 함수

    const adder = x => y => x + y;
    adder(5)(7);  // 12
    
    typeof adder(5) // 'function'
    adder(5) // y => x + y  (x=5)
    // 두 경우 모두 리턴 값이 함수 형태
    // 즉, adder는 '함수를 리턴하는 함수'
  2. 외부 함수 & 내부 함수: 리턴하는 함수에 의해 스코프가 구분됨

    1. 외부함수는 내부함수의 변수에 접근 불가능 ❌
    2. 내부함수는 외부함수의 변수에 접근 가능 ⭕️

📎 클로저의 활용

  • 데이터를 보존하는 함수: 외부함수의 실행이 끝나도, 외부 함수 내의 변수 사용 가능
    • 일반적인 함수는 함수 실행이 끝나면 함수 내부의 변수 사용 불가

    • 클로저는 외부함수 실행이 끝나도 외부 함수 내 변수가 메모리 상에 저장 → 사용 가능

      // 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 -> 각각의 값 보존 가능
profile
FE developer

0개의 댓글