1차적으로 지금까지 배운 내용 중에 부족하거나, 기억하기 쉽지 않은 내용들을 위주로 정리할 것이다. 사실 내 기준에서 기억하기 쉽지 않았던 것들인데, 이 글을 읽는 사람들도 나와 같은 것을 모르길 바라면서 써본다. 하지만 내 블로그를 보는 사람은 없겠지...? 😂
그래도 계속 쓰면 언젠가 보겠지 하하. 같이 공부하는 분이 스코프, 클로저 글에 좋아요 하나 달아주셔서 기부니가 기부니하다. 나와 같은 선에서 시작하는 모든 분들에게 도움이 되길 바란다!
뭐 엄청 많다..정리하니까 한 열 몇개 정도 되는 것 같다. 엄격한 비교 vs 느슨한 비교, 얕은 복사 vs 깊은 복사.....단어도 이상하고 어감이가 정감이 안간다. 오늘은 코드 위주로 이해를 해볼 겸 코드 설명이 많다! 바로 정리해보자!
레릿고~레릿고~🧚🏻♀️
Primitive Type
- 원시(primitive) 타입의 데이터는 변수에 할당이 될 때 메모리 상에 고정된 크기로 저장이 되고 해당 변수가 원시 데이터 값을 보관한다. 원시 타입 자료형은 모두 변수 선언, 초기화, 할당 시 값이 저장된 메모리 영역에 직접적으로 접근한다. 즉, 변수에 새 값이 할당이 될 경우, 변수에 할당된 메모리 블럭에 저장된 값을 바로 변경한다.
- 원시 자료형으로는 Boolean, number, String, null, undefined가 있다.
- Primitive Type의 변수 복사
- 변수 간에 원시 타입 데이터를 복사할 경우, 데이터의 값이 복사된다.
var x = 100; var y = x; x = 99; console.log(y); // 100;
Reference Type
- 참조(reference) 타입의 데이터는 크기가 정해져 있지 않다. 변수에는 힙(heap) 메모리의 주소값을 저장한다.
- 참조 타입은 변수의 값이 저장된 메모리 블럭의 주소를 가지고 있고 자바스크립트 엔진이 변수가 가지고 있는 메모리 주소를 이용해서 변수의 값에 접근한다.
- 참조 자료형으로는 Array, Function, Object가 있다.
- Reference Type의 변수 복사
- 변수 간에 참조 타입 데이터를 복사할 경우, 데이터의 주소값이 복사된다.
var x = { count : 100 }; var y = x; x.count = 99; console.log(y); // 99
- 분리된 메모리 공간을 생각해 보자. 아래 코드에서 Object는 'arr' 뿐이다.
let num = 123; const msg = "hello"; let arr = [1, 2, 3]; const isOdd = true; /* 원시 자료형의 데이터가 저장되는 공간 (stack) 1 | num | 123 2 | msg | "hello" 3 | arr | heap의 12번부터 3개 // (실제 데이터가 저장되어 있는 주소) 4 |isOdd| true ============================== Object 자료형의 데이터가 저장되는 공간 (heap) 10 || 11 || 12 || 1 13 || 2 14 || 3 */
const
- 재선언 불가, 재할당 불가
- const로 선언된 객체와 배열을 살펴보자!
// const는 오브젝트에도 잘 동작한다. const MY_OBJECT = {'key': 'value'}; // 오브젝트를 덮어쓰면 오류가 발생한다. MY_OBJECT = {'OTHER_KEY': 'value'}; // 하지만 오브젝트의 키는 보호되지 않는다. // 그러므로 아래 문장은 문제없이 실행된다. MY_OBJECT.key = 'otherValue'; // 오브젝트를 변경할 수 없게 하려면 Object.freeze() 를 사용해라! // 배열에도 똑같이 적용된다. const MY_ARRAY = []; // 배열에 아이템을 삽입하는 건 가능하다. MY_ARRAY.push('A'); // ["A"] // 하지만 변수에 새로운 배열을 배정하면 에러가 발생한다. MY_ARRAY = ['B']
let
- 재선언 불가, 재할당 가능
let a = 200; a = 300; console.log(a); // 300 let b = 100; let b = 101; // Uncaught SyntaxError: Identifier 'b' has already been declared
var
- 재선언 가능, 재할당 가능
var a = 200; a = 300; console.log(a); // 300 var b = 100; var b = 101; console.log(b); //101
느슨한 비교
느슨한 비교(‘==‘)는 값 자체가 같으면 true를 반환한다.
엄격한 비교
엄격한 비교(‘===‘)는 값과 주소값 모두 같아야 true를 반환한다.
- toEqual은 느슨한 비교이고, toBe는 엄격한 비교인 것을 알 수 있다.
// 먼저 toEqual을 살펴보자 const overTwenty = ['hongsik', 'minchul', 'hoyong’]; // allowedToDrink에 overTwenty의 주소값을 할당한다. let allowedToDrink = overTwenty; overTwenty.push('san’); // 주소값이 같기 때문에 overTwenty에 'san'을 추가하면 allowedToDrink도 업데이트된다. // True expect(allowedToDrink).toEqual(['hongsik', 'minchul', 'hoyong', 'san’]); overTwenty[1] = 'chanyoung'; // 주소값이 같기 때문에 overTwenty[1]을 'chanyoung'로 재할당하면 allowedToDrink도 업데이트된다. //True expect(allowedToDrink[1]).toEqual('chanyoung'); //이제 toBe를 살펴보자 const ages = [22, 23, 27]; allowedToDrink = ages; // allowedToDrink와 ages를 비교하면 true가 나온다. // 배열 변수 ages의 주소 값을 변수 allowedToDrink에 할당했기 때문이다. expect(allowedToDrink === ages).toBe(true); // 하지만 allowedToDrink와 [22,23,27]을 비교하면 값이 같은데 false가 나온다. // allowedToDrink는 [22,23,27] 배열과 주소값이 다르기 때문이다. expect(allowedToDrink === [22, 23, 27]).toBe(false); // 같은 이유로 아래 결과도 false가 나온다. const nums1 = [1, 2, 3]; const nums2 = [1, 2, 3]; expect(nums1 === nums2).toBe(false);
객체의 복사는 얕은 복사와 깊은 복사로 나뉘어 진다. 얕은 복사와 깊은 복사의 차이는 복사하고자 하는 객체의 속성(key)의 값으로 객체나 배열이 있을때, 그 내부 값에 대하여 참조(주소) 값을 복사하느냐 또는 그 값을 복사하느냐의 차이를 가지고 있다. 깊은 복사와 얕은 복사를 할 수 있는 방법이 각각 몇 가지 존재하지만 전부를 언급하진 못하니 참조 사이트를 아래에 남겨 놓겠다.
이렇게 적고 보니 더 헷갈릴 것 같기 때문에, 간단하게 예로 들어서 얕은 복사와 깊은복사를 설명 해보겠다!
얕은 복사
const testData = {a: {aa: 1}, b: [1, 2, 3]}; const copyData2 = Object.assign({}, testData); testData.a.aa = 2; testData.b[0] = 100; console.log(copyData2); // {a: {aa: 2}, b: [100, 2, 3]}
- 위에서 이야기 했듯이 복사하고자 하는 객체가 객체나 배열을 담고 있으면 참조 값을 복사하기 때문에 ‘복사 대상 객체’의 값이 바뀌면 ‘복사한 객체’의 값이 바뀐다. 반대로도 마찬가지다.
깊은 복사
const testData = {a: {aa: 1}, b: [1, 2, 3]}; const copyData = JSON.parse(JSON.stringify(testData)); testData.a.aa = 2; testData.b[0] = 100; console.log(copyData); // {a: {aa: 1}, b: [1, 2, 3]}
const testData = {a: {aa: 1}, b: [1, 2, 3]}; const copyData = eval('('+JSON.stringify(testData)+')'); testData.a.aa = 2; testData.b[0] = 100; console.log(copyData); // {a: {aa: 1}, b: 2} // {a: {aa: 1}, b: [1, 2, 3]}
- 깊은 복사는 객체 안의 객체, 배열의 참조 값도 완전히 원시 데이터 값으로 복사하는 방법이다.
- 가장 간단하게 사용할 수 있는 방법은 JSON 객체를 사용하거나, JSON 객체와 eval() 메서드를 사용하는 방법이다. JSON.stringify() 메서드로 객체의 데이터를 JSON 형태의 문자열로(원시 데이터)로 만들어서 복사한 후 JSON.parse() 또는 eval() 메서드를 사용해서 다시 객체로 만든다.
let obj = {key1: 1, key2: 2, key3: 3};
console.log(obj.length); // undefined
let objKeyArr = obj.keys();
console.log(objKeyArr.length) // 3
만약 서로 다른 배열을 붙이고 싶다면 어떻게 할 것인가? concat을 제외하고 말이다. spread operator는 배열 내부의 값을 마치 뿌리는 것 처럼 펼쳐주는 연산자이다. 이를 활용하여 함수의 정의 방법과 상관없이 함수를 호출할 때 넘겨주는 인수의 개수엔 제약이 없게 한 것이 rest parameter이다.
역시나 말로 설명하면 모르니 코드로 살펴보자!
Spread Operator
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const arr3 = [7, 8, 9]; const newArr = [...arr1, ...arr2, ...arr3]; document.write(newArr.length); // 9 document.write(newArr); // [1,2,3,4,5,6,7,8,9]
- 위와 같이 ...(spread operator)를 사용하면 여러개의 객체, 배열을 합치는데에 용이하다.
Rest Parameter
function getAllParamsByRestParameter(...args) { return args; } // arguments를 통해 '비슷하게' 함수의 인자들을 다룰 수 있다. (spread syntax 도입 이전) function getAllParamsByArgumentsObj() { return arguments; } /* arguments는 유사 배열 객체고 rest parameter(...)는 배열이다. 유사 배열 객체(array-like object)는 간단하게 순회가능한(iterable) 특징이 있고 length 값을 알 수 있는 특징이 있는 것이다. 즉, 배열처럼 사용할 수 있는 객체를 말한다. 무슨 말이냐면 arguments는 유사배열객체이기 때문에 Array 오브젝트의 메서드를 사용할 수 없다. 따라서 ES6에서는 arrow function에 arguments는 사용할 수 없을 뿐더러 Rest 파라미터를 사용하면 더 유연한 코드를 작성할 수 있는 것이기 때문에 Rest 파라미터 사용을 권장한다. */ const restParams = getAllParamsByRestParameter('first', 'second', 'third'); const argumentsObj = getAllParamsByArgumentsObj('first', 'second', 'third'); expect(restParams).toEqual(['first', 'second', 'third']); expect(argumentsObj).toEqual(FILL_ME_IN); // argumentsObj는 유사 배열 객체이기 때문에 표현하기 힘드므로 코드를 복사해서 결과를 확인 바란다. // 이 아래는 함수에 인자로 rest parameter가 들어 갔을 때 나오는 배열과 arguments(유사 배열 객체)의 차이를 보여준다. expect(restParams === argumentsObj).toEqual(false); expect(typeof restParams).toEqual('object'); expect(typeof argumentsObj).toEqual('object'); expect(Array.isArray(restParams)).toEqual(true); expect(Array.isArray(argumentsObj)).toEqual(false); const argsArr = Array.from(argumentsObj); expect(Array.isArray(argsArr)).toEqual(true); expect(argsArr).toEqual(['first', 'second', 'third']); expect(argsArr === restParams).toEqual(false);
- Rest Parameter는 인자의 수가 정해져 있지 않은 경우에도 유용하게 사용할 수 있다.
- Rest Parameter는 인자의 일부에만 적용할 수도 있다.
function abc(a,b ...c) { console.log(c); // arr1:[1,2,3,4] arr2:[1,2] return [a, b, ...c]; } let arr1 = abc(1, 2, 1, 2, 3, 4); let arr2 = abc(1, 2, 1, 2); console.log(arr1); // [1, 2, 1, 2, 3, 4] console.log(arr2); // [1, 2, 1, 2]
function print(str){ // let str = 'hello'와 같다.
console.log(str);
}
print('hello');
for( ~ in A)
let obj = {a: 1, b: 2, c: 3}; let result = []; for(let key in obj) { result.push(key); } console.log(result); // ['a', 'b', 'c']
- for in 문은 객체의 키값을 차례로 넘기기 위해 사용한다.
for( ~ of A)
let arr = [1, 2, 3, 4]; let result = []; for(let value of arr) { result.push(value); } console.log(result); // [1, 2, 3, 4]
- for of 문은 배열의 값을 차례로 넘기기 위해 사용한다.
function returnFirstArg(firstArg) {
return firstArg;
}
expect(returnFirstArg('first', 'second', 'third')).toBe('first');
// returnfirstArg 함수는 인자를 1개 밖에 받지 않으므로, 함수로 인자를 넘길 때 첫 번째 인자만 넘어간다.
function returnSecondArg(firstArg, secondArg) {
return secondArg;
}
expect(returnSecondArg('only give first arg')).toBe(undefined);
// returnSecondArg 함수는 인자를 2개 받으므로, 함수의 인자를 넘길 때 첫 번째, 두 번째 인자만 넘긴다.
// 위에서 returnSecondArg에 한개의 문자열을 보내지만, 반환값은 secondArg이므로 undefined가 출력된다.
it('여러 개의 객체를 병합할 수 있습니다.', function () {
const fullPre = {
cohort: 7,
duration: 4,
mentor: 'hongsik',
}
const me = {
time: '0156',
status: 'sleepy',
todos: ['coplit', 'koans']
};
const merged = { ...fullPre, ...me };
/*
- 변수 'merged'에 할당된 것은 'obj1'과 'obj2'의 value일까요, reference일까요?
-> 얕은 복사로써 primitive 자료형은 value가 저장되고, reference 자료형은 주소가 저장된다.
- 만약 값(value, 데이터)이 복사된 것이라면, shallow copy일까요, deep copy일까요?
-> shallow copy
*/
expect(merged).toEqual({cohort: 7,
duration: 4,
mentor: 'hongsik',
time: '0156',
status: 'sleepy',
todos: ['coplit', 'koans'],
});
});
블로그를 작성할 수록 나의 지식을 쌓아나는 것에 보람을 느낀다. 잊어버리는 개념이 생기면 다시 돌아 볼 저장소는 큰 자산이다. 작성하는 것에 시간과 노력이 많이 들어가지만, 그 이상으로 돌아오는 것이 많다는 생각을 했다. 앞으로도 열심히, 잘, 꾸준히 블로깅해야겠다!
(블로그를 돌아보니까 말투가 엄청 딱딱하닼ㅋㅋㅋㅋㅋ실제로 그렇지 않은데말이지....자소서를 많이써서 그런가...군대로 돌아간거갔네... 다음부턴 친근하게 얘기해보련다ㅋㄷ 그래야 다른 사람들도 이해가 쉽지않을까?)
[참고문서]
"Javascript #6 객체의 얕은 복사와 깊은 복사에 대해 알아봅니다.", , https://falsy.me/javascript-6-%EA%B0%9D%EC%B2%B4%EC%9D%98-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%99%80-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B4%85%EB%8B%88%EB%8B%A4/ 2019.01.23. (접속일: 2020.09.23 07:15AM)
"[자바스크립트] var를 사용할 때 발생하는 문제들", Dale Seo,https://www.daleseo.com/js-var-issues/ 2017.02.05. (접속일: 2020.09.23 07:15AM)
"JavaScript - 전개연산자(spread)와 와 나머지 매개변수(rest)", bigbrothershin,https://velog.io/@bigbrothershin/JavaScript-%ED%95%A8%EC%88%98-%EC%8B%AC%ED%99%94-%EB%82%98%EB%A8%B8%EC%A7%80-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EC%99%80-%EC%A0%84%EA%B0%9C-%EC%97%B0%EC%82%B0%EC%9E%90 2020.02.06. (접속일: 2020.09.23 07:15AM)
"자바스크립트 for in vs for of 반복문 정리", Kyoung Deok Kwon,https://jsdev.kr/t/for-in-vs-for-of/2938 2017.01.09. (접속일: 2020.09.23 07:15AM)