📓 JS ES6 + 문법

  • 객체 관련
    • 객체 비구조화
    • 객체 프로퍼티 초기화 단축
    • 계산된 프로퍼티명 혹은 메서드명
    • 얕은 복사
  • 배열 관련
    • 객체 프로퍼티 메서드 혹은 배열 요소 Trailing Comma
    • 특정 요소 포함 여부 확인 Array.prototype.includes()


🏷️ 배열, 객체 비구조화 Destructure

배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 자바스크립트 표현식.

장점
1. 배열, 객체 내 값을 추출하는 코드가 간단해진다.
2. 필요한 객체와 나머지 요소 분리가 간단해진다.
3. 기본값 지정이 가능하다.


  • 배열 비구조화
let user = ['a', 'b', 'c']
console.log(user[0])
console.log(user[1])
console.log(user[2])

기존 ES5에서는 일일이 하나씩 값을 꺼내왔어야 했다.


var [a1, a2, ...rest_a] = [1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log(a1); // 1
console.log(a2); // 2
console.log(rest_a); // [3, 4, 5, 6, 7, 8, 9]

전개 연산자인 (...)을 사용하여 명시되지 않은 값들을 다 가져올 수 있다.
반드시 가장 마지막 요소에 넣어줘야한다.


  • 인덱스 값만 가져오기
const arr = [1,2,3,4,5,6,8]
const[x,,,y,,,z] = arr

console.log(x); // 1
console.log(y); // 4
console.log(z); // 8


  • 객체 비구조화
var { a1, a2, ...rest_a } = { a1 : 10, a2 : 20, a3 : 30, a4 : 40 };
console.log(rest_a); // { a3: 30, a4: 40 }

우항의 키가 화항의 변수명과 매칭.


function func(){
  return[1,2]
}
const result = func()

result[0] // 1
result[1] // 2

const[PREV, NEXT] = func()

PREV // 1
NEXT // 2

function funcobj(){
  return{
    prev: 1,
    next: 2
  }
}

const{prev, next} = funcobj()
prev // 1
next // 2

위 예시들로 return [1,2] 로 배열 반환하고 함수로 배열의 인덱스에 접근하기도 하고 배열 인자를 통해 출력하기도 한다.



  • key이름 대신 다른 이름으로 변수명 사용
var { a1 : awesome_name, a2 : dumb , ...rest_a } = { a1 : 10, a2 : 20, a3 : 30, a4 : 40 };

console.log(awesome_name); // 10
console.log(dumb); // 20
console.log(a1); // 에러 발생

변수 선언에 대한 명시(var, let, const)가 없을 경우 괄호를 사용하여 묶어줘야 한다.

({ a, b } = { a : 10, b : 20}); // 정상 출력
{ c, d } = { c : 30, d : 40}; // 에러
  • 기본값 할당
    비구조화 범위를 벗아나면 값 할당할 때 undefined가 반환된다.
[a, b] = [10];
console.log(a); // 10
console.log(b); // undefined

({a, b} = {c = 30, d : new_name=40} = {})
console.log(c);
console.log(new_name);

= 으로 값 할당을 해주면 기본값 할당할 수 있다



🏷️ 객체 프로퍼티 초기화 단축

객체 초기화하는 방법이다.
{}는 Object literal이다.

const object1 = {}; // 리터럴 객체 초기화
const object2 = {name: "rayrayr", age: 12};
console.log(object2)


// property 객체 초기화
let user = {     // 객체
  // 프로퍼티
  name: "John",  // 키: "name",  값: "John"
  age: 30        // 키: "age", 값: 30
};
function funcobj(){
  return{
    prev: 1,
    next: 2
  }
}
const{prev, next} = funcobj()
prev // 1
next // 2

// 콜론 단축 -> 바로 위 코드랑 차이 파악
function funcobj(){
  var prev = 1
  var next = 2
  return{
    prev,
    next,
  }
}

// 비구조화
const {prev, next} = funcobj()
prev // 1
next // 2

객체의 콜론을 사용하지 않고 변수에 값만 할당해서 초기화를 단축시킬 수 있다.



🏷️ 계산된 프로퍼티명 혹은 메서드명

객체를 만들 때 객체 리터럴 안의 프로퍼티 키가 대괄호로 둘러쌓여 있는 경우 계산된 프로퍼티라고 한다.

const phonebook = {
  'magnus name': 'magnus',
  'magnus phone': '010-1234-1234'
}

const pickedperson = 'magnus'
phonebook[pickedperson+' name'] // // 'magnus'
phonebook[pickedperson+' phone'] // '010-1234-1234'

phonebook['magnus name'] // 'magnus'
phonebook['magnus phone'] // '010-1234-1234'

프로퍼티로 magnus name과 phone이 있고 프로퍼티 키가 대괄호로 둘러쌓여 있다.
pickedperson이라는 프로퍼티로 phonebook에 있는 name과 phone에 접근하여 사용했다.


추가 예시

let prefix = 'prop';
let i = 0;

let obj = {};

obj[prefix + '-' + ++i] = i;
obj[prefix + '-' + ++i] = i;
obj[prefix + '-' + ++i] = i;

console.log(obj); // {prop-1: 1, prop-2: 2, prop-3: 3}

ES6에서는 객체 리터널 내부에서도 프로퍼티 키를 동적으로 생성 가능

const prefix = 'prop';
let i = 0;

const obj = {
  [`${prefix}-${++i}`]: i,
  [`${prefix}-${++i}`]: i,
  [`${prefix}-${++i}`]: i
};

console.log(obj); // {prop-1: 1, prop-2: 2, prop-3: 3}





📓 얕은 복사 (reference), 깊은 복사

얕은 복사는 객체의 주소 값을 복사하고 깊은 복사는 객체의 값을 복사하는 것이다.

  • 데이터 타입 (원시 타입)
    • 숫자
    • 문자열
    • boolean
    • null
    • Symbol
  • 참조형 타입 (객체)
    • 배열
    • 함수
    • Date
    • Map, WeakMap
    • Set, WeakSet

🏷️ 얕은 복사

객체를 직접 대입하는 경우 두 객체가 같은 주소를 가지게 될 때 얕은 복사라고 한다.
두 객체는 같은 주소를 참조하기 때문에 한 객체의 프로퍼티를 변경하면 나머지 객체들도 같이 변경된다.

var obj1 = {
  a: 1,
  b: {
    c: 2,
  },
};
var obj2 = { ...obj1 }; // 깊은 복사
// var obj2 = obj1; 얕은 복사
obj1.a = 3;
obj2.a = 3;
console.log(obj1); // {a : 3, b: { c: 2 } }
console.log(obj2); // {a : 3, b: { c: 2 } }
console.log(obj1 === obj2) // false
// 다른 프로퍼티들은 모두 같음.

obj2.b.c = 10;
console.log(obj1); // {a : 3, b: { c: 10 } }
console.log(obj2); // {a : 3, b: { c: 10 } }
console.log(obj1 === obj2); // false
console.log(obj1.b === obj2.b); // true
console.log(obj1.b.c === obj2.b.c) // true

obj1, obj2는 다른 주소를 가지고 있는 객체다. 하지만 객체 안 프로퍼티는 동일한 주소를 가지고 있다.
그래서 obj2의 객체 b에 저장된 c값을 변경하면 obj1의 c값도 같이 변경이 된다.


🏷️ Object.assign()

객체와 객체를 합쳐주는 메서드다.
쉬운 예시를 먼저 보자.

var obj1 = {
  a: 10,
  b: {
    c: 'abc',
  },
};
var obj2 = Object.assign({}, obj1);
obj2.a = 20;
obj2.b.c = 'def';

console.log(obj1); // { a: 10, b: { c: "def" } }
console.log(obj2); // { a: 20, b: { c: "def" } }
console.log(obj1 === obj2) // false

{} 빈 객체를 통해 복사되어 {}는 obj1과 다른 객체가 반환된다.
obj의 내용을 {} 안에 복사를 하고 반환한다.
프로퍼티 a는 기본형 타입의 값이 들어있기 때문에 변경하면 obj2의 a 프로퍼티만 변경된다.
프로퍼티 b는 참조형 타입의 값이 들어있기 때문에 변경하면 obj1, obj2의 b가 가진 구조 값이 동일하여 하나가 변경되면 서로 영향을 줘서 같이 변경이 된다.

Object.assign()으로 새로운 객체인 독립적인 객체를 만들었기 때문에 obj1 === obj2 는 false가 출력되는 것이다.



function changename(userobj /*0x123*/, newname){
  let newuserobj /*0x124*/ = Object.assign({}, userobj) /*0x123*/
  newuserobj.name = newname /*0x124*/
  return newuserobj /*0x124*/
}

let prevuserobj = {name: 'ray', age: 10} // 0x123
let nextuserobj = changename(prevuserobj, 'baram') // 0x124
prevuserobj.name = 'caron' // 깊은 복사 0x123

console.log(prevuserobj) // 0x123
console.log(nextuserobj) // 0x124
// { name: 'caron', age: 10 }
// { name: 'baram', age: 10 }

Object.assign(생성할 객체, 복사할 객체)
첫 번째 인자로 빈 객체를 넣어주고 두 번째 인자로 userobj를 넣었다.
userobj와 새로 생성된 객체는 다른 객체지만 같은 주소값을 가지고 있다.

위 코드로 보면 prevuserobj의 주소값이 0x123이고 nextuserobj가 0x124으로 다른 객체의 주소값을 가지고 있다.

changename() 함수를 실행했을 때 파라미터에 있는 userobj는 0x123, newname은 'Baram'이다.

두 개의 객체는 다른 주소를 가지고 있지만, 객체 안 프로퍼티는 동일한 주소를 가지고 있다.
그래서 newuserobj와 userobj는 서로 다른 주소값을 가지고 있지만 같은 주소의 프로퍼티를 가지고 있어 값이 같게 나온다.

function changename(userobj, newname){
  let newuserobj = Object.assign({}, userobj)
  console.log(userobj)
  console.log(newuserobj)
  console.log(userobj.name === newuserobj.name)
  console.log(userobj.age === newuserobj.age) // 주소값이 같아 값이 같음
  console.log(userobj === newuserobj) // 다른 객체
  
  newuserobj.name = newname
  console.log(newuserobj)
  return newuserobj
}

let prevuserobj = {name: 'ray', age: 10}
let nextuserobj = changename(prevuserobj, 'baram')
prevuserobj.name = 'caron'

console.log(prevuserobj)
console.log(nextuserobj)

좀 더 이해 쉽게 간단한 코드를 보자

let origin = {
  a: 1,
  b: {c: 2}
}

// let copy = origin;
let copy = Object.assign({}, origin)

console.log(origin === copy) // false
console.log(origin.b.c === copy.b.c) // true

origin과 copy는 다른 객체이다. 그런데 origin.b.c의 경우에는 얕은 복사를 통해 프로퍼티는 같은 주소값을 가리키고 있기 때문에 true가 출력된다.



☄️ 기본형 타입의 깊은 복사

깊은 복사는 객체의 프로퍼티 값들만 복사해서 두 객체는 다른 주소를 갖게되고 한 객체의 프로퍼티를 변경하더라도 값은 변하지 않는다.

var a = 10;
var b = a; // 할당 연산자를 통한 기본형 타입 복사
console.log(a); // 10
console.log(b); // 10
console.log(a === b) // true

a가 가지고 있는 주소값을 b에도 넣어 서로 같은 대상을 바라보게 하는 것이다.
b의 값을 변경하면 a의 값도 변경되는 것이 아닌 b의 값에 20이 저장되어 있는 메모리 값을 가리키게 되어 a와 b가 서로 달라지게 된다.

var c = 20;
var d = c;
console.log(c === d); // true

d = 30;
console.log(c); // 20
console.log(d); // 30
console.log(c === d); // false

d가 바라보는 주소값을 30이라는 데이터를 저장한 메모리 주소값으로 변경!!!



☄️ 참조형 타입의 깊은 복사

var obj1 = {
  a: 10,
  b: 'abc',
};
var obj2 = obj1;
console.log(obj1 === obj2); // true

obj2.a = 20;
console.log(obj1); // {a: 20, b: 'abc'}
console.log(obj2); // {a: 20, b: 'abc'}
console.log(obj1 === obj2); // true

주소 값이 동일하기 때문에 true 출력.
프로퍼티 a에 20을 재할당했으므로 obj1, obj2의 a 프로퍼티 값이 모두 20으로 변경됨.
그러면 obj1과 obj2의 주소 값이 여전히 동일하다.

기본형이라면 깊은 복사가 이루어지지만 참조형 타입의 데이터는 깊은 복사가 이루어지지 않는다.



정리

  1. 객체(참조형 타입)를 직접 대입한다 - 얕은 복사, 두 객체가 같은 주소를 가지게 됨.
  2. 기본형을 직접 대입한다 - 깊은 복사
  3. 참조형 타입은 깊은 복사되지 않는다. - 프로퍼티 값을 변경하면 같이 변하게 되어 깊은 복사를 하기 위해 재귀, JSON.stringify()를 사용한다.

추가 예시

var objectA = {
    name: 'Java'
}

var objectB = Object.assign({},objectA);

console.log("objectA : ", objectA);
console.log("objectB : ", objectB);

console.log("값 변경 후");

objectA.name = 'React';
objectB.name = 'React';

console.log("objectA : ", objectA);
console.log("objectB : ", objectB);
console.log(objectA === objectB) // false
console.log(objectA.name === objectB.name) // true


그렇다면 참조형 타입의 깊은 복사는 어떻게 해야 할까??

☄️ 재귀함수를 이용한 깊은 복사

var deepCopy = function (obj) {
  var result = {};
  if (typeof obj === 'object' && obj !== null) {
    for (var prop in obj) {
      result[obj] = deepCopy(obj[prop]);
    }
  } else {
    result = obj;
  }
  return result;
};

재귀를 사용하여 result 객체에 새롭게 할당해서 깊은 복사를 진행했다.

☄️ JSON.parse(JSON.stringify(obj))를 사용한 깊은 복사

var obj1 = {
  a: 10,
  b: 'abc',
};
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b = 3;
console.log(obj1); // {a: 10, b: 'abc'}
console.log(obj2); // {a: 10, b: 3}

JSON.stringify()는 JS에서 값이나 객체를 JSON문자열로 변환해준다.
JSON.parse() 는 반대로 JSON 문자열의 구문을 분석해서 JS내 값이나 객체로 변환해준다.
변환 후 다시 객체를 만들 때 새로운 객체를 만들기 때문에 참조가 모두 끊어져 깊은 복사를 이룰 수 있다.






📓 Spread Syntax와 Rest Parameter

🏷️ Rest Pramater

파라미터 (param1, param2, ...rest)로 ...을 붙여서 정의한 파라미터다. 앞의 파라미터들을 제외한 나머지 파라미터들을 나타낸다.
(반드시 마지막 파라미터여야 함.)

function bar(param1, param2, ...rest) {
  console.log(param1); // 1
  console.log(param2); // 2
  console.log(rest);   // [ 3, 4, 5 ]
}

Rest parameter는 매개변수 개수에는 영향이 가지 않는다.

function bar(x, ...rest) {}
console.log(bar.length); // 1

function baz(x, y, ...rest) {}
console.log(baz.length); // 2


🏷️ Spread 문법

객체 혹은 배열을 펼칠 수 있다.

function test(a,b,c){
  console.log(a+b+c)
}
const numbers = [1,2,3]
test(...numbers)

const prevobj = {name: 'magnus', sex: 'man', age: 10}
const nextobj = {...prevobj, age:10}
console.log(nextobj)

...prevobj를 통해 age 앞의 프로퍼티들을 출력할 수 있다.



Spread 문법(Spread Syntax, ...)는 대상을 개별 요소로 분리한다.
Spread 문법 대상은 이터러블이어야 한다.
배열, 문자열, Map, Set은 이터러블이다.

이터러블이 아닌 일반 객체는 Spread 문법의 대상이 될 수 없다.

console.log(...[1, 2, 3]) // 1 2 3
console.log([1, 2, 3]) // [1, 2, 3]

console.log(...'Hello');  // H e l l o

// Map과 Set은 이터러블이다.
console.log(...new Map([['a', '1'], ['b', '2']]));  // [ 'a', '1' ] [ 'b', '2' ]
console.log(...new Set([1, 2, 3]));  // 1 2 3

console.log(...{ a: 1, b: 2 }); // TypeError

const prevarr = [1,2,3,4,5,6,7]
const nextarr = [...prevarr, 1,2,3,4,5]
console.log(nextarr) // [ 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5 ]

const prevarr = {name: 'a', age: 12}
const nextarr = {name: 'B', age: 92, address : "earth"}
console.log({...prevarr, ...nextarr})
console.log({...nextarr, ...prevarr})

// { name: 'B', age: 92, address: 'earth' }
// { name: 'a', age: 12, address: 'earth' }


🏷️ Default 파라미터

JS는 오버로딩이 불가능하기 때문에 파라미터에 하나하나 값을 설정 해줘야 한다.

function defaultparams(a=0,b=0,c=0,d=0){
 console.log(a+b+c+d)
}

function defaults(a,b,c,d){
 console.log(a+b+c+d)
}

defaults(1,2) // 3
defaults(1,2,3) // 6
defaults(1,2,3,4) // 9

defaultparams(1,2) // NaN
defaultparams(1,2,3) // NaN
defaultparams(1,2,3,4) // 10


🏷️ concat

ES5에서 기존 배열의 요소를 새로운 배열 요소의 일부로 만들고 싶은 경우 concat을 사용해야 한다.
spread 문법을 사용하면 새로운 배열의 요소로 만들 수 있다.

var arr = [1, 2, 3];
console.log(arr.concat([4, 5, 6])); // [ 1, 2, 3, 4, 5, 6 ]

var arr2 = [1,2,3];
console.log([...arr2, 4,5,6]);


🏷️ Splice

nums.splice(5, 3)
// [5, 6, 7]
// 인덱스 5가 가리키는 값부터 3개의 값을 삭제하겠다는 의미

nums.splice(10, 2, -10, -11)
// 10인덱스에서 2개를 삭제하고 -10과 -11로 대체
var arr1 = [1, 2, 3, 6];
var arr2 = [4, 5];

console.log([3, 0].concat(arr2)) // [3,0,4,5]

Array.prototype.splice.apply(arr1, [3, 0].concat(arr2));
// arr1에서 3인덱스부터 0개를 삭제하고 4,5로 대체.

console.log(arr1); // [ 1, 2, 3, 4, 5, 6 ]

slice는 자바의 str.substring과 같다고 생각하면 된다.






📓 forEach, for in, for of의 차이

🏷️ forEach

Array의 prototype을 상속받은 객체가 사용할 수 있는 함수다.
반복문이 아닌 함수고 주어진 함수를 배열 요소들에 대해 반복 작업을 실행한다.

const arr = [1, 2, 3, 4, 5];

arr.forEach(q => {
  console.log(q); // 1 2 3 4 5
});

arr.forEach(function(element){
  console.log(element) // 1 2 3 4 5
})


🏷️ for .. in

Object에 있는 key에 차례로 접근하는 데 사용되는 반복문이다.

let obj = {
  name: 'answer',
  age: 20,
  country: 'korea'
}

for(let keys in obj){
  console.log(keys)
}
/*
'name'
'age'
'country'
*/

일반적으로 Object를 제외한 객체에는 사용하지 않는 것이 좋다. for in으로 순회를 하게 되면 해동 요소 뿐만 아니라 prototype chain을 따라 확장 속성들도 함께 순회하기 때문이다.

의미를 파악하기 위해 코드를 보면

let obj = {
  name: 'answer',
  age: 20,
  country: 'korea'
}

Object.prototype.func = function(){
  return 'none'
}

for(let keys in obj){
  console.log(keys, obj[keys])
}
/*'name' 'answer'
'age' 20
'country' 'korea'
'func' ƒ ()*/

Object의 prototype으로 만든 func도 같이 순회해서 출력이 되버린다.
또한 Array에 사용하면 특정 순서에 따라 인덱스를 반환하지 않을 수도 있어 index 순서가 중요한 배열을 순회할 때는 사용되지 않아야 한다고 명시되어 있다.

Array라면 for, forEach(), for .. of 권장.
for .. in은 키-쌍 형태의 데이터 경우나 특정 값을 가진 키가 있는지 확인을 할 때 유용하기 때문에 사용.



🏷️ for .. of

for...of는 iterable한 Object(Array, Map, Set, String, TypedArray, arguments 등을 포함)에 대해 속성값(value)을 반복 순회한다.
키만 접근하거나, 혹은 키와 값 모두 접근하거나 하는 것이 모두 가능하다.

const test = [1,3,null,undefined,NaN,""];
for(let i of test){
  console.log(i)
}
// 1 3 null undefined NaN ""

const str = "Hello world!";
for (let i of str){
  console.log(i)
}
// H e l l o ~~~ d !

객체는 이터러블하지 않으므로 for.. of 불가능



🏷️ map() 함수

배열을 순회하며 지정된 콜백 함수를 적용하여 각 요소를 변환하고, 그 변환된 값을 모아서 새로운 배열로 반환하는 역할을 수행.
값과 인덱스를 인자로 받아 자동으로 for문을 돌려 값을 빼도록 해준다.

const arr = [ 10, 20, 30, 40, 50 ];

//일반 함수 형태
arr.map(function(item, index) {
    console.log(index+"번 값", item);
});

//화살표 함수 형태
arr.map((item, index) => {
    console.log(index+"번 값", item);
});
let arr = [3, 4, 5, 6];

let test = arr.map(function(element){
    return element * element;
});

console.log(est); // [9, 16, 25, 36]





Reference

🔗 https://n-log.tistory.com/39 - forEach, for in, for of 차이
🔗 이터러블과 이터레이터
🔗 https://poiemaweb.com/es6-extended-parameter-handling - 매개변수 기본값, Rest, Spread
🔗 https://fromnowwon.tistory.com/entry/for-of-in#cont3 - for .. in, for .. of 차이
🔗 Map 자료형

profile
일상의 인연에 감사하라. 기적은 의외로 가까운 곳에 있을지도 모른다.

0개의 댓글