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: {…}}