공부/JavaScript

JavaScript 문법 종합 3주차(2)

뀨뿌뀨뿌 2023. 6. 14. 23:36

1. 데이터 타입 심화 2

  ⅰ.  불변 객체

  • 불변 객체란?
    • 객체로 예를 들면, 객체의 속성에접근해서 값을 변경하면 가변이 성렵함
      하지만, 객체 데이터 자체를 변경(새로운 데이터를 할당)하고자 한다면 기존 데이터는 변경이 되지 않으므로 불변하다고 할수 있음
  • 불변 객체의 필요성
// user 객채를 생성
let user = {
  name: "wonjang",
  gender: "male",
};

// 이름을 변경하는 함수, 'changeName'을 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티(속성)에 접근해서 이름을 변경! => 가변
const changeName = (user, newName) => {
  // 2개 인자를 받음.
  // 첫번째 user => user객체는 이름과 성을 포함하고 있음
  // 두번째 newName
  let newUser = user; // 인자로 받은 user객체 복사(할당하는 방식으로)
  newUser.name = newName;  // 새롭게 만든 복사한 newUser의 속성에 접근해서 새로운 이름을 할당
  return newUser;
};

// 변경한 user정보를 user2 변수에 할당
// 가변이기 때문에 user1도 영향을 받게됨
const user2 = changeName(user, "twojang");

// 결국 아래 로직은 skip하게 됨
if (user !== user2) {
  console.log("유저 정보가 변경되었습니다.");
}

console.log(user.name, user2.name); // twojang twojang
console.log(user === user2); // true  => 가변성의 문제점

✔ 위에 코드로 객체의 가변성이 문제가 발생되는것을 알수 있음

// user 객채를 생성
let user = {
  name: "wonjang",
  gender: "male",
};

// 이름을 변경하는 함수, 'changeName'을 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티에 접근하는 것이 아니라, 아예 새로운 객체를 반환 => 불변
const changeName = (user, newName) => {
  // 기존은 새로운 객체를 복사해서 속성을 접근해서 변경하는 방식으로 변경
  // 밑에 함수는 항상 새로운 객체를 생성해서 return하게 됨
  return {
    name: newName,
    gender: user.gender,
  };
};

// 변경한 user정보를 user2 변수에 할당
// 가변이기 때문에 user1도 영향이 없음
const user2 = changeName(user, "twojang");

// 결국 아래 로직이 실행됨
if (user !== user2) {
  console.log("유저 정보가 변경되었습니다.");
}

console.log(user.name, user2.name); // wonjang twojang
console.log(user === user2); // false

✔ 위에 코드는 좋은 방법은 아니고 문제점이 발생함
 예시는 속성이 2개니깐 괜찮지만 속성 양이 많아지면 하드 코딩이 너무 많아짐
    => 이러한 이유때문에 얕은 복사라는 방법을 사용하게됨

  • 얕은 복사
    👇 얕은 복사의 패턴 예시
// copyObject는 target(객체)를 받음
const copyObject = (target) => {
  let result = {};

  // for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 잇음
  // 하드코딩을 하지 않아도됨
  // 이 copyObject로 복사를 한다음, 복사를 완료한 객체의 프로퍼티를 변경하면됨
  for (let prop in target){
    result[prop] = target[prop]
  } 
  return result
};

👇 적용 예시

const copyObject = (target) => {
  let result = {};

  for (let prop in target) {
    result[prop] = target[prop];
  }
  return result;
};

// 예제에 적용하기
let user = {
  name: "wonjang",
  gender: "male",
};

let user2 = copyObject(user);
user2.name = "twojang";

if (user !== user) {
  console.log("유저 정보가 변겨오디었습니다.");
}

if (user !== user2) {
  console.log("유저 정보가 변경되었습니다.");
}

console.log(user.name, user2.name); //wonjang twojang
console.log(user === user2);  // false

✔ 이러한 얕은 복사 패턴에도 문제가 발생하게됨
    => why? 중첩된 객체에 대해서는 완벽한 복사를 할수 없기 때문 이것이 바로 얕은 복사의 한계
📚 얕은 복사는 바로 아래 단계의 값만 복사하기 때문(for-in문은 조건문의 객체에 대해서 수행하는 것이기 때문에)
      => 중첩된 객체의 경우 참조형 데이터가 저장된 프로퍼티를 복사할 때 주소값만 복사하게됨

  • 깊은 복사
    • 내부의 모든 값들을 하나하나 다 찾아서 모두 복사하는 방법
// 중첩된 객체에 대한 얕은 복사
let user = {
	name: 'wonjang',
	urls: {
		portfolio: 'http://github.com/abc',
		blog: 'http://blog.com',
		facebook: 'http://facebook.com/abc',
	}
};

var user2 = copyObject(user);

user2.name = 'twojang'; // 이름은 변경됨 why? 1depth(가장상위) 이기 때문

// 바로 아래 단계에 대해서는 불변성을 유지하기 때문에 값이 달라지죠.
console.log(user.name === user2.name); // false

// 더 깊은 단계에 대해서는 불변성을 유지하지 못하기 때문에 값이 같음
// 혼란을 야기하게됨
user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio); // true

user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); // true

위에 코드의 문제를 해결할려면 결국 user.urls 프로퍼티도 불변 객체로 만들어야함!!!!!!

let user = {
	name: 'wonjang',
	urls: {
		portfolio: 'http://github.com/abc',
		blog: 'http://blog.com',
		facebook: 'http://facebook.com/abc',
	}
};

// 1차 copy
var user2 = copyObject(user);

// 2차 copy
user2.urls = copyObject(user.urls);

user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);

user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog);

✔ 중첩된 객체의 중첩된 객체는 또 불변객체로 만들어야 하기 때문에 임시적인 방법
  => 자동적으로 수행할 수 있는 방법을 찾아야함!!

재귀적 수행(recursive)
- 객체의 프로퍼티 중, 기본형 데이터는 그대로 복사 + 참조형 데이터는 다시 그 내부의 프로퍼티를 복사
❓ 재귀적으로 수행한다라는 말은 함수나 알고리즘이 자기 자신을 호출하여 반복하여 실행하는 것을 말함
    => 각종 알고리즘에서 사용됨?...

  • 결론적으로 적용한코드
const copyObjectDeep = (target) => {
	let result = {};
	if (typeof target === 'object' && target !== null) {
		for (let prop in target) {
        // 수행을 하지만 얕은복사와 다르게 자기자신을 수행함 =>재귀적 수행
			result[prop] = copyObjectDeep(target[prop]);
		}
	} else {
		result = target;
	}
	return result;
}
// 함수가 호출될때 함수 스스로를 다시 한번 안쪽에서 호출을 하면서
// 객체안의 깊은 곳까지 다 확인할수 있다

✔ 모든 요소 하나하나를 다 불변성을 유지하게끔 바꾸게됨

  • 마지막 방법! JSON(=JavaScript Object Notation)을 이용하는 방법도 존재
    but!!! 완벽한 방법은 아니기 때문에 내용만 참고
    • 장점 
      • JSON.stringify() 함수를 사용하여 객체를 문자열로 변환한 후, 다시 JSON.parse() 함수를 사용하여 새로운 객체를 생성하기 때문에, 원본 객체와 복사본 객체가 서로 독립적으로 존재
        => 복사본 객체를 수정해도 원본 객체에 영향을 미치지 않음
      • JSON을 이용한 깊은 복사는 다른 깊은 복사 방법에 비해 코드가 간결하고 쉽게 이해
    • 단점
      • JSON을 이용한 깊은 복사는 원본 객체가 가지고 있는 모든 정보를 복사않음
        ex. 함수나 undefined와 같은 속성 값은 복사되지 않음
      • JSON.stringify() 함수는 순환 참조(Recursive Reference)를 지원하지 않음
        => 객체 안에 객체가 중첩되어 있는 경우, 이 방법으로는 복사할 수 없음
    •  JSON을 이용한 깊은 복사는 객체의 구조가 간단하고, 함수나 undefined와 같은 속성 값이 없는 경우에 적합한 방법, 만약 객체의 구조가 복잡하거나 순환 참조가 있는 경우에는 다른 깊은 복사 방법을 고려해야함

  ⅱ. undefined와 null

  • 없음을 의미하는 값이지만, 두가지는 미세하게 다르고, 목적도 다름
  • undefined
    • 사용자(=개발자)가 직접 지정할 수도 있지만 일반적으로는 자바스크립트 엔진에서 값이 있어야 할 것 같은데 없는 경우, 자동으로 부여
    • undefined가 나오는 case
      • 변수에 값을 지정되지 않은 경우, 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할때
      • .이나 []로 접근하려 할때, 해당 데이터가 존재하지 않는 경우
      • return문이 없가나 호출되지 않는 함수의 실행결과
// 변수의 값이 지정되지 않은 경우 - 데이터 영역에 메모리주소가 없음
let a;
console.log(a);  // undefined

// 존재하지 않는 property에 접근하는 경우
const obj = { a: 1 };
console.log(obj.a);  // 1
console.log(obj.b);  // undefined

// 함수내부에 로직이 없음(return문이 없음)
const func = () => {};
const c = func();
console.log(c);  // undefined

📍 헷깔리지 말아야하는 undefined
✔ 지금 undefined로 나오는 이 변수가 필요에 의해 할당한건지 자바스크립트 엔진이 변환한건지 구분할수 없음
없다를 명시적으로 표현할때는 undefined를 사용하지 말아야함

  • null
    • null의 용도는 없다를 명시적으로 표현할 때 사용
    • null의 주의점!!!!
      typeof null - typeof null을 하면 object인 것은 유명한 javascript 자체 버그!!!!
const n = null;
console.log(typeof n); // object

// 동등연산자(equality operator)
// type까지는 일치하지 않아도 되는것
console.log(n == undefined); // true
console.log(n == null); // ture

// 일치연산자(identity operator)
// 이것을 사용하면 null 과 undefined는 구분이 명확해짐
console.log(n === undefined); // false
console.log(n === null); // true