본문 바로가기

JavaScript

20210519 JavaSciprt 04 : 데이터 가변성(원시형 데이터), 불변성(참조형 데이터), 메모리 사용, 복사(깊은, 얕은 복사), import, export, Lodash API, JSON

JavaScript04

Javascript : 데이터의 불변성, 가변성, 메모리 사용

데이터 불변성 (Immutability)

  • 원시 데이터 (Primitive) : String, Number, Boolean, undefined, null
  • 데이터 불변성 : 메모리에 쓰인 데이터가 변하지 않음, 수정되지 않음
    • 데이터 모양이 다르면 다른 것이다.
    • 새로운 모양의 데이터 일 경우 새로운 메모리를 사용하고 같은 모양의 경우에는 같은 메모리를 사용 (메모리 위치와 데이터 모양의 일치)

원시형 데이터의 메모리 사용

let a = 1;
let b = 4;

// -------------------- Memory --------------------------
// |1:  1     |2:   4      |3:         |4:
// ------------------------------------------------------
// |    a     |     b      |           |
// ------------------------------------------------------

console.log(a, b, a === b); // 1 4 false

b = a;

// -------------------- Memory --------------------------
// |1:  1     |2:   4      |3:         |4:
// ------------------------------------------------------
// |  a, b    |            |           |
// ------------------------------------------------------
// 단지, 변수 b는 1번 메모리를 같이 바라보게 됨 (a === b)

console.log(a, b, a === b); // 1 1 true

a = 7;

// -------------------- Memory --------------------------
// |1:  1     |2:   4      |3:    7      |4:
// ------------------------------------------------------
// |     b    |            |      a      |
// ------------------------------------------------------
// 다른 모양의 값을 할당하면 기존의 메모리에 덮어 쓰는게 아닌 새로운 메모리에 값을 저장하고 가르킴

console.log(a, b, a === b); // 7 1 false

let c = 1;

// -------------------- Memory --------------------------
// |1:  1     |2:   4      |3:    7      |4:
// ------------------------------------------------------
// |   b, c   |            |      a      |
// ------------------------------------------------------
// 기존의 메모리에 있는 숫자 데이터를 바라 봄

console.log(b, c, b === c); // 1 1 true
  • 위 처럼 원시형 (Primitive)의 경우에는 데이터 모양과 바라보는 메모리 위치가 일치함 (메모리에 쓰인 데이터가 변하지 않음, 수정되지 않음)
  • 할당 연산자를 통해 다른 변수를 받거나 데이터 모양이 변하면 참조 주소가 바뀜

데이터 가변성

  • 참조형 데이터 : Object, Array, Function
    • 데이터가 만들어 질때 마다 새로운 메모리를 사용하고, 메모리에 쓰인 데이터의 수정이 가능함 (데이터 가변성)
  • 데이터 모양이 변한다고 해서 참조 주소가 바뀌지 않고 참조 주소는 그대로 두고 해당 메모리 위치의 값을 변경
  • 할당 연산자를 통해 다른 변수를 받을 때만 참조 주소만 변경되어 같은 곳을 바라봄

참조형 데이터의 메모리 사용

let a = { k: 1 };
let b = { k: 1 };
// -------------------- Memory --------------------------
// |1: {k: 1} |2:  {k: 1}  |3:         |4:
// ------------------------------------------------------
// |    a     |     b      |           |
// ------------------------------------------------------
// 새로운 값을 만들때 마다 새로운 메모리를 사용함 -> 불변성이 X -> 가변성
console.log(a, b, a === b); // {k: 1} {k: 1} false

a.k = 7; // 1번 메모리의 값을 7로 수정
// -------------------- Memory --------------------------
// |1: {k: 7} |2:  {k: 1}  |3:         |4:
// ------------------------------------------------------
// |    a     |     b      |           |
// ------------------------------------------------------
console.log(a, b, a === b); // {k: 7} {k: 1} false

b = a; // 같은 메모리를 바라보게 함
// -------------------- Memory --------------------------
// |1: {k: 7} |2:  {k: 1}  |3:         |4:
// ------------------------------------------------------
// |   a, b   |            |           |
// ------------------------------------------------------
console.log(a, b, a === b); // {k: 7} {k: 7} true

a.k = 2; // 같은 메모리를 바라보고 있는 상태에서 값을 수정
// -------------------- Memory --------------------------
// |1: {k: 2} |2:  {k: 1}  |3:         |4:
// ------------------------------------------------------
// |   a, b   |            |           |
// ------------------------------------------------------
// 같은 메모리를 바라보게 하는 변수가 여러개 일때 같은 곳을 바라보기 때문에 모든 변수의 값이 업데이트 됨
console.log(a, b, a === b); // {k: 2} {k: 2} true

let c = b; // 또 같은 메모리를 바라보게 함으로 모두 같은 메모리를 바라봄
// -------------------- Memory --------------------------
// |1: {k: 2} |2:  {k: 1}  |3:         |4:
// ------------------------------------------------------
// |  a, b, c |            |           |
// ------------------------------------------------------
console.log(a, b, c, a === c); // {k: 2} {k: 2} {k: 2} true

a.k = 9;
// -------------------- Memory --------------------------
// |1: {k: 9} |2:  {k: 1}  |3:         |4:
// ------------------------------------------------------
// |  a, b, c |            |           |
// ------------------------------------------------------
console.log(a, b, c, a === c); // {k: 9} {k: 9} {k: 9} true
  • 위처럼 같은 메모리를 바라 보고 있는 상태로 수정이 가능 하기 때문에, 다른 변수들에도 영향을 미쳐 의도치 않는 결과를 초래할 수 있음
  • 참조형 데이터에서 다른 변수를 할당 연산자로 받아 올때는 주의 해야 한다.
  • 참조 주소만 옮기는 것이 아닌 별개로 구분해서 사용하고자 할때는 복사라는 개념을 사용해야 함
  • 복사는 얕은 복사(표면만), 깊은 복사(모든 관계 까지)가 있음

Javascript : 얕은 복사(Shallow copy), 깊은 복사(Deep copy)

  • 복사를 사용하는 이유는 같은 메모리를 바라보는 상태의 관계를 끊고 독립적으로 자료를 활용하기 위함
  • 복사의 종류는 얕은 복사, 깊은 복사가 존재함
    • 얕은 복사의 경우에는 일반적인 상위 참조 데이터만 독립적으로 복사하고, 안에 있는 하위 참조 데이터의 경우 독립적으로 복사하지 못한다.
    • 그래서 복사를 했음에도 불구하고 데이터가 수정되었을 때 하위 데이터만 같이 반영되는 구조가 된다.
    • 이를 방지하기 위해 하위 참조 데이터 까지 독립적으로 복사하는 것을 깊은 복사라고 한다.

얕은 복사(Shallow copy)

  • 상위 참조형 데이터만 표면적으로 독립 복사 O
  • 하위 참조형 데이터 독립 복사X
  • 얕은 복사 방법 : assign을 통한 복사, 전개 연산자(...)를 통한 복사
const user = {
  name: "Tom",
  age: 25,
  emails: ["BraveGirls@gmail.com"],
};

// assign을 통한 복사 (얕은 복사)
// 새로운 객체(새로운 메모리)를 만들어서 모양을 복사 함
const copyUser = Object.assign({}, user);

// 전개 연산자를 통한 복사 (얕은 복사)
const copyUser = { ...user };

console.log(copyUser === user); // false

user.age = 22;

console.log("user: ", user); // {name: "Tom", age: 22, emails: Array(1)}
console.log("copyUser: ", copyUser); // {name: "Tom", age: 25, emails: Array(1)}

// 얕은 복사의 문제점
user.emails.push("una@gmail.com");
console.log(user.emails === copyUser.emails); // true

// user은 참조형 데이터인 객체이고 복사처리를 했지만, emails도 참조형 데이터인 배열이지만 복사처리한 과정이 없음

console.log("user: ", user); // {name: "Tom", age: 22, emails: Array(2)}
console.log("copyUser: ", copyUser); // {name: "Tom", age: 25, emails: Array(2)}
// emails가 같이 수정됨

깊은 복사(Deep copy)

  • 하위의 참조 데이터 까지 모두 복사 하고자 함
  • 상위 참조형 데이터 독립 복사 O
  • 하위 참조형 데이터 독립 복사 O
  • 그냥 javascript만으로는 힘듦 -> lodash 활용
  • Lodash 사이트
    • 설치 : npm i lodash (D옵션 X)
    • lodash 의 cloneDeep 함수 사용
    • import _ from 'lodash'
import _ from "lodash";

const user = {
  name: "Tom",
  age: 25,
  emails: ["BraveGirls@gmail.com"],
};

// Lodash를 통한 깊은 복사
const deepCopyUser = _.cloneDeep(user);

console.log(deepCopyUser === user); // false

user.age = 22;

console.log("user: ", user); // {name: "Tom", age: 22, emails: Array(1)}
console.log("deepCopyUser: ", deepCopyUser); // {name: "Tom", age: 25, emails: Array(1)}

user.emails.push("deepCopy@gmail.com");

console.log(user.emails === deepCopyUser.emails); // false
// user은 참조형 데이터인 객체이고 복사처리를 했지만, emails도 참조형 데이터인 배열이지만 복사처리한 과정이 없음

console.log("user: ", user); // {name: "Tom", age: 22, emails: Array(2)}
console.log("deepCopyUser: ", deepCopyUser); // {name: "Tom", age: 25, emails: Array(1)}
// 변경해도 반영 되지 않음!

Javascript : import, export

  • 모듈을 만들어 내보내거나 사용하는 방법이다.
  • 하나의 모듈은 import를 통해서 다른 모듈을 받을 수 있고, export를 통해서 2가지의 통로로 내보낼 수 있다.

export 종류

Default export

  • export default 키워드로 사용
  • 하나만 내보 낼수 있음
  • 기본 통로로 바져 나가는 것으로 이름이 필요 없음 (지정해도 상관은 없지만)
  • 가져 와서 활용 할때 이름을 변경해서 사용해도 상관 없음
// Default 사용
// export default function getType(data) {
//     return Object.prototype.toString.call(data).slice(8, -1);
// }

// 익명 함수 사용 가능
export default function (data) {
  return Object.prototype.toString.call(data).slice(8, -1);
}
// 단, default로 내보내는 항목은 무조건 하나만 가능

// Default export - import 사용하기
import _ from "lodash"; // From node_modules
import checkType from "./getType"; // 이름 변경 가능
console.log(checkType([1, 2, 3])); // Array
console.log(_.camelCase("im your energy")); // imYourEnergy

Named export

  • export 이름 필수 키워드로 사용
  • 이름이 지정되어 있으면 몇개를 내보내도 상관 없음
  • 내보내야 될 내용이 많은 경우 default 없이 함
  • 이름이 지정된 통로로 나오는 이 함수의 경우 {}로 묶어서 사용해야함(구조 분해 할당 처럼)
  • 객체 구조분해 에서 사용했던 :을 통한 이름 변경은 import에서는 as로 대체해서 사용이 가능하다.
  • Named export로 구성된 함수를 모두 사용 할때 한번에 가져오고자 하는 경우 * (에스터리크, 와일드 카드) 문자 사용 (특수 문자 읽는법)
  • Default export, Named export 한 모듈에 공존 가능
// Named export
export function random() {
  return Math.floor(Math.random() * 10);
}

export const user = {
  name: "Tom",
  age: 24,
};

export default 123;

// Named export - import 사용하기
import { random, user as tom } from "./getRandom";
import * as R from "./getRandom";

console.log(random(), random()); // 9 3
// console.log(user);
console.log(tom); // {name: "Tom", age: 24}
console.log(R);
// {user: {…}, default: 123, __esModule: true, random: ƒ}

Lodash : uniqBy, unionBy, find, findIndex, remove

uniqBy

  • 배열의 item 중복을 없애 고유한 item만 가진 Array를 return
  • _.uniqBy(target: array, checkProperty: string )
  • _.uniqBy(array, [iteratee=_.identity])
import _ from "lodash";

const usersA = [
  { userId: "1", name: "Tom" },
  { userId: "2", name: "Jerry" },
];

const usersB = [
  { userId: "1", name: "Tom" },
  { userId: "3", name: "Snoopy" },
];

const usersC = usersA.concat(usersB);
console.log("concat", usersC);
// 0: {userId: "1", name: "Tom"}
// 1: {userId: "2", name: "Jerry"}
// 2: {userId: "1", name: "Tom"}
// 3: {userId: "3", name: "Snoopy"}

console.log("uniqBy", _.uniqBy(usersC, "userId"));
// _.uniqBy(target: array, checkProperty: string )
// 0: {userId: "1", name: "Tom"}
// 1: {userId: "2", name: "Jerry"}
// 2: {userId: "3", name: "Snoopy"}

unionBy

  • 두개의 배열을 합치면서 중복을 제거한 Array를 return
  • _.unionBy(targetA: array, targetB: array, checkProperty: string)
import _ from "lodash";

const usersA = [
  { userId: "1", name: "Tom" },
  { userId: "2", name: "Jerry" },
];

const usersB = [
  { userId: "1", name: "Tom" },
  { userId: "3", name: "Snoopy" },
];

const usersD = _.unionBy(usersA, usersB, "userId");
// _.unionBy(targetA: array, targetB: array, checkProperty: string)
// 두개의 배열을 합치면서 중복을 제거한 배열을 return
console.log("unionBy", usersD);
//0: {userId: "1", name: "Tom"}
// 1: {userId: "2", name: "Jerry"}
// 2: {userId: "3", name: "Snoopy"}

find, findIndex

find

  • 배열 데이터에서 특정한 객체 데이터를 찾고자 하는 경우
  • _.find(collection(Array|Object), [predicate=_.identity], [fromIndex=0])

findIndex

  • 배열 데이터에서 특정한 객체 데이터의 index를 찾고자 하는 경우
  • _.findIndex(array, [predicate=_.identity], [fromIndex=0])
const users = [
  { userId: "1", name: "Tom" },
  { userId: "2", name: "Jerry" },
  { userId: "3", name: "Snoopy" },
  { userId: "4", name: "Micky" },
  { userId: "5", name: "Minnie" },
];

const foundUser = _.find(users, { name: "Snoopy" });
console.log(foundUser);
// {userId: "3", name: "Snoopy"}

const foundUserIndex = _.findIndex(users, { name: "Snoopy" });
console.log(foundUserIndex);
// 2

remove

  • 해당 배열에서 해당 조건의 item을 제거하고자 하는 경우
  • _.remove(array, [predicate=_.identity])
const users = [
  { userId: "1", name: "Tom" },
  { userId: "2", name: "Jerry" },
  { userId: "3", name: "Snoopy" },
  { userId: "4", name: "Micky" },
  { userId: "5", name: "Minnie" },
];
_.remove(users, { name: "Minnie" });
console.log(users);
// 0: {userId: "1", name: "Tom"}
// 1: {userId: "2", name: "Jerry"}
// 2: {userId: "3", name: "Snoopy"}
// 3: {userId: "4", name: "Micky"}

JavaScript : JSON (JavaScript Object Notation)

  • 자바스크립트의 객체 표기법으로서 문자 데이터 임
  • AJAX 통신 자료형으로 XML말고 JSON이 있음
  • 기본 자료형 : number, string, boolean, array, object, null
  • undefined (X)
  • 쌍 따옴표("")만 지원
  • JavaScript 파일에서는 property에 ''표를 안붙여도 상관 없지만, 특수문자를 쓰려면 ''를 붙여 주어야 함 (물론 camelCase로 작성하지만 특수한 경우가 있는 경우 사용)
  • JSON은 기본적으로 문자 데이터인데, JS파일에 import 시에 자동으로 변환되어 객체데이터 처럼 사용할수 있는 것임
// myData.json
{
  "string": "Tom",
  "number": 123,
  "boolean": true,
  "null": null,
  "object": {},
  "array": [],
  "undefined": "undefined 는 불가함"
}
import myData from "./myData.json";

console.log(myData);
// {string: "Tom", number: 123, boolean: true, null: null, object: {…}, …}

const user = {
  name: "Tom",
  age: 25,
  emails: ["BraveGirls@gmail.com", "Tom@gmail.com"],
  "company-name": {},
  companyName: {},
};
console.log("user", user);
// user {name: "Tom", age: 25, emails: Array(2), company-name: {…}, companyName: {…}}

const str = JSON.stringify(user); // 모든 데이터를 넣어서 JSON의 문자 데이터로 만들수 있는 것임

console.log("str", str);
// str {"name":"Tom","age":25,"emails":["BraveGirls@gmail.com","Tom@gmail.com"],"company-name":{},"companyName":{}}
console.log(typeof str);
// string
const obj = JSON.parse(str);
console.log("obj", obj);
// obj {name: "Tom", age: 25, emails: Array(2), company-name: {…}, companyName: {…}}