[C/F TIL] 13일차 - JavaScript 참조 자료형, 원시 자료, 참조 자료, 얕은 복사, 깊은 복사, 스코프

mu-eng·2023년 4월 27일
1

TIL (in boost camp)

목록 보기
14/53
post-thumbnail

Code States
Front-end boost camp
Today
I
Learned

☀️ 아침 오운완 완료! 4월 27일 13일차 수업 시작


☀️ 원시 자료형 vs 참조 자료형

  • 원시 자료형 : number, string, undefined, null, boolean, symbol

  • 참조 자료형 : 원시 자료형이 아닌 모든 자료형. 대표적으로 배열, 객체
    -- 원시 자료형과 참조 자료형의 특징 차이 (대조됨!!)

  • 힙(heap) : 참조 자료형을 저장하는 특별한 저장 공간
    -- ex) 변수 arr라는 배열에 해당하는 저장공간에는 주소값이 저장, 그 주소값을 통해 참조 자료형에 접근 가능! => '참조한다'라고 표현

  • 결론 :
    -- 1. 원시 자료형이 할당된 변수를 다른 변수에 할당하면 값 자체의 복사가 일어난다. 따라서 원본과 복사본 중 하나를 변경해도 다른 하나에 영향을 미치지 않는다.
    -- 2. 참조 자료형이 할당된 변수를 다른 변수에 할당하면 주소가 복사되어 원본과 복사본이 같은 주소를 참조한다.
    -- 3. 참조 자료형의 주소값을 복사한 변수에 요소를 추가하면 주소를 참조하고 있는 원본에도 영향을 미친다.
    -- 4. 참조 자료형이 저장된 변수를 다른 변수에 할당할 경우, 두 변수는 같은 주소를 참조하고 있을 뿐 값 자체가 복사되었다고 볼 수 없다!


☀️ 배열 복사하기

  • 첫째, slice() 사용
    -- 배열 내장 메서드인 slice()를 사용하면 원본 배열을 복사할 수 있다.
    -- 새롭게 생성된 배열은 원본 배열과 같은 요소를 갖지만 참조하고 있는 주소는 다르다.
let arr = [0, 1, 2, 3];
let copiedArr = arr.slice();
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr); // false
copiedArr.push(4);
console.log(copiedArr); // [0, 1, 2, 3, 4]
console.log(arr); // [0, 1, 2, 3]
  • 둘째, spread syntax 사용
    -- 배열이 할당된 변수명 앞에 '...'을 붙여주면 펼칠 수 있다.
let arr = [0, 1, 2, 3];

console.log(...arr); // 0 1 2 3
// 같은 요소를 가진 배열을 두 개 만든 후 변수에 각각 할당했을 경우, 두 변수는 같은 주소를 참조하지 않는다.
let num = [1, 2, 3];
let int = [1, 2, 3];

console.log(num === int) // false
// 새로운 배열 안에 원본 배열을 펼쳐서 전달한다면? 같은 배열과 같은 요소를 가지고 있지만 각각 다른 주소를 참조하게 됨. slice() 메서드에서 사용한 것과 동일하게 동작
let arr = [0, 1, 2, 3];
let copiedArr = [...arr];
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr); // false

copiedArr.push(4);
console.log(copiedArr); // [0, 1, 2, 3, 4]
console.log(arr); // [0, 1, 2, 3]

☀️ 객체 복사하기

  • 첫째, 객체를 복사하기 위한 메서드 Object.assign()를 사용
let obj = { firstName: "coding", lastName: "kim" };
let copiedObj = Object.assign({}, obj);

console.log(copiedObj) // { firstName: "coding", lastName: "kim" }
console.log(obj === copiedObj) // false
  • 둘째, spread syntax
let obj = { firstName: "coding", lastName: "kim" };
let copiedObj = {...obj};

console.log(copiedObj) // { firstName: "coding", lastName: "kim" }
console.log(obj === copiedObj) // false

-- 예외 : 참조 자료형 내부에 참조 자료형이 중첩되어 있는 경우, slice(), Object.assign(), spread syntax를 사용해도 참조 자료형 내부에 참조 자료형이 중첩된 구조는 복사할 수 없다.


☀️ 얕은 복사

  • slice(), Object.assign(), spread syntax 등의 방법으로 참조 자료형을 복사하면, 중첩된 구조 중 한 단계까지만 복사함
// 유저의 정보를 담고 있는 객체를 요소로 가지고 있는 배열 users를 slice()로 복사
let users = [
	{
		name: "kimcoding",
		age: 26,
		job: "student"
	},
	{
		name: "parkhacker",
		age: 29,
		job: "web designer"
	},
];

let copiedUsers = users.slice();

console.log(users === copiedUsers); // false!!!
// 하지만 users와 copiedUsers의 0번째 요소를 각각 비교하면?
console.log(users[0] === copiedUsers[0]); // true

☀️ 깊은 복사

  • 참조 자료형 내부에 중첩되어 있는 모든 참조 자료형을 복사하는 것
  • JavaScript 내부적으로는 깊은 복사 수행 불가하지만 다른 문법을 응용하여 깊은 복사과 같은 결과물은 만들 수 있다.
    -- JSON.stringify() : 참조 자료형을 문자열 형태로 변환하여 반환 => 1️⃣
    -- JSON.parse() : 문자열 형태를 객체로 변환하여 반환 => 2️⃣
    -- 1️⃣ -> 2️⃣ 순서를 거쳐 깊은 복사과 같은 결과물 반환
const arr = [1, 2, [3, 4]];
const copiedArr = JSON.parse(JSON.stringify(arr));

console.log(arr); // [1, 2, [3, 4]]
console.log(copiedArr); // [1, 2, [3, 4]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false
  • 하지만 위 1️⃣ -> 2️⃣방법도 예외는 존재함
    -- ex) 중첩된 참조 자료형 중에 함수가 포함되어 있을 경우 위 방법을 사용하면 함수가 'null'로 바뀜

  • 완전한 깊은 복사를 반드시 해야하는 경우라면? : 외부 라이브러리 사용!
    -- node.js환경에서 lodash, randa 설치 후 사용

const lodash = require('lodash');

const arr = [1, 2, [3, 4]];
const copiedArr = lodash.cloneDeep(arr);

console.log(arr); // [1, 2, [3, 4]]
console.log(copiedArr); // [1, 2, [3, 4]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false

☀️ 스코프

  • 변수의 유효범위
  • 중괄호(블록) 안쪽에 변수가 선언되었는가, 바깥쪽에 변수가 선언되었는가가 중요 -> 이 범위를 '스코프'라고 부름
  • 변수에 접근할 수 있는 범위가 존재한다.
  • 바깥쪽 스코프에서 선언한 변수는 안쪽 스코프에서 사용 가능
    -- 반면에, 안쪽에서 선언한 변수는 바깥쪽 스코프에서 사용 불가
  • 스코프는 중첩이 가능
  • 가장 바깥의 스코프는 특별히 전역 스코프(Global scope)라고 부름 <-> 나머지는 전부 지역 스코프(Local scope)
  • 지역 변수는 전역 변수보다 더 높은 우선순위를 가짐

☀️ 변수 선언과 스코프

  • 스코프의 종류 :
    -- 블록 스코프(block scope) : 중괄호를 기준으로 범위 구분
    -- 함수 스코프(function scope) : function 키워드가 등장하는 함수 선언식 및 함수 표현식은 함수 스코프를 만듦
  • 화살표 함수는 블록 스코프로 취급
  • var 키워드
    -- for 문이 만들어낸 블록 스코프를 무시, 함수 스코프만 따름
    -- var 키워드는 재선언을 해도 아무런 에러도 내지 않지만, let 키워드는 재선언을 방지
  • const 키워드
    -- let 키워드와 동일하게 블록 스코프를 따름
    -- 값의 변경을 최소화하여 보다 안전한 프로그램을 만듦
    -- 값을 새롭게 할당할 일이 없다면 const 키워드 권장

☀️ 변수 선언 시 주의할 점

  • var로 선언된 전역 변수 및 전역 함수는 window객체에 속한다.
    -- window 객체 : 브라우저에만 존재하는 브라우저의 창을 의미하는 객체, 이와 별개로 전역 영역을 담고 있음/ 함수 선언식으로 함수를 선언하거나 var로 전역 변수를 만들면 window객체에서도 동일한 값을 찾을 수가 있음
var myName = '김코딩';
console.log(window.myName);

function foo() {
  console.log('bar');
}

console.log(foo === window.foo); // true
  • 전역 변수에 너무 많은 변수를 선언하지 않기
    -- 전역 변수 : 어디서든 접근 가능한 변수
    -- 편리한 대신 다른 함수 혹은 로직에 의해 의도되지 않은 변경이 발생할 수 있음
  • var는 블록 스코프를 무시하며, 재선언을 해도 에러를 내지 않음
  • 전역 변수를 var로 선언하는 경우 문제가 될 수 있다
    -- 브라우저의 내장 기능을 사용하지 못하게 만들 수도 있음
  • 선언 없는 변수 할당 금지
    -- 선언 키워드 var, let, const 없이 변수 할당 금지!
  • 실수 방지를 위해 Strict Mode 사용!
    -- js 파일 상단에 'use strict'라고 입력 (''포함)

☀️ 클로저

  • 함수와 함수가 선언된 어휘적 환경의 조합, 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.
  • 즉, 함수와 그 함수가 접근할 수 있는 변수의 조합
  • 함수가 생성될 당시의 외부 변수를 기억, 생성 이후에도 계속 접근 가능
  • 클로저가 중요한 이유?
    -- 클로저의 함수는 어디에서 호출되느냐와 무관하게 선언된 함수 주변 환경에 따라 접근할 수 있는 변수가 정해지기 때문
  • 예시
const globalVar = '전역 변수';

function outerFn() {
  const outerFnVar = 'outer 함수 내의 변수';
  const innerFn = function () {
    return (
      'innerFn은 ' + outerFnVar + '와 ' + globalVar + '에 접근할 수 있습니다.'
    );
  };
  return innerFn;
}

const innerFnOnGlobal = outerFn();
const message = innerFnOnGlobal();
console.log(message); // ?

-- innerFnOnGlobal은 outerFn 내부의 innerFn의 주소값을 가집니다. 그다음 줄에서 innerFnOnGlobal을 호출합니다. 이때, innerFnOnGlobal은 innerFn 밖에 있기 때문에 outerFnVar에는 접근하지 못한다고 생각할 수 있는데, 실제 접근할 수 있습니다.

-- 내부 스코프에 접근할 수 없다며? 왜 가능하대? : innerFn 함수가 최초 선언되었던 환경에서는 outerFnVar에 접근할 수 있기 때문입니다. innerFnOnGlobal은 innerFn의 주소값을 가지고 있고, innerFn은 클로저로서 outerFnVar에 접근할 수 있기 때문입니다. 이 “환경”을 어휘적 환경(Lexical Environment)라고 합니다.

하.. 뭔 소리지.. ;;


☀️ 13일차 수업을 마치며...

이론을 한번 싹 훑어보긴 했는데 이건.. 복습이 필요해. 오늘 방과후에 유튜브 구글링 합쳐서 좀 훑어봐야겠다...

profile
[무엥일기] 무엥,,, 내가 머쨍이 개발자가 될 수 이쓰까,,,

0개의 댓글