[modern-js] 챕터 4.1 ~ 4.4

younoah·2021년 4월 27일
0

[modern-js]

목록 보기
3/3

4.1 객체

객체는 프로퍼티(키-값 쌍)을 저장한다.

프로퍼티의 키는 문자형 이어야만 하고, 값은 모든 자료형 이 허용된다.

자료형

  • 원시형 : 문자열, 숫자, boolean, null, undefined, Symbol
  • 참조형 : 객체
    - 배열, 함수, 정규표현식, Error, Date 등등...

객체 사용법

  • 객체 생성
// 객체 생성자
const user = new object(); 
user.['name'] = '존';
user.age = 30;
user['phone number'] = '010-1111-1111';
// 띄어쓰기가 있는 문자열(복수의 단어)는 때괄호 표기법으로만 정의가능하다.
// 점표기법에서 식별자는 숫자로 시작, 공백, $, _를 사용할 수 없다.

// 객체 리터럴
const user = {
    name: '존',
    age: 30,
    'phone number': '010-1111-1111',
}; 
  • 객체 프로퍼티 값 얻기 (프로퍼티 접근)
// 점 표기법
console.log(user.name); 
console.log(user['phone number']);
// 띄어쓰기가 있는 문자열(복수의 단어)는 때괄호 표기법으로만 읽어올 수 있다.
// 점표기법에서 식별자는 숫자로 시작, 공백, $, _를 사용할 수 없다.

// 대괄호 표기법
const key = 'name';
console.log(user['name']); 
console.log(user[key]); // 대괄호 표기법 사용시 변수를 키로 사용 가능
  • 프로퍼티 삭제하기
delete user.age;
delete user['age'];

계산된 프로퍼티

let dynamicKey1 = 'banana';
let dynamicKey2 = 'apple';

// 일반적인 방법
const bag = {};
bag[dynamicKey1] = 2;
bag[dynamicKey2 + ' computer'] = 4; // 복잡한 표현식도 가능

// 계산된 프로퍼티
const bag = {
    [dynamicKey1]: 2,
    [dynamicKey2 + ' computer']: 4, // 복잡한 표현식도 가능
};

단축 프로퍼티

const age = 20;

const user = {
  age,
};

변수를 사용해서 프로퍼티를 만드는 경우 위와 같이 프로퍼티의 키와 같을 단축해서 사용할 수 있다.

프로퍼티 이름 제약사항

변수로 for , let , return 와 같은 예약어를 사용할 수 없다.

하지만 프로퍼티에 키로 for , let , return 와 같은 예약어를 사용할 수 있다.

정수 프로퍼티

프로퍼티 키로 숫자를 사용할 수 있다.

const obj = {
0: 'hello',
};

obj[1] = 'world';

console.log(obj);
console.log(obj[0], obj['1']);

키에 숫자를 사용하면 문자열로 자동 형 변환되어 사용된다. 예를들어 키를 숫자형 0 으로 하면 문자형 '0' 으로 형변환 되어 사용된다.

또한 프로퍼티에 접근할 때도 똑같이 숫자형 0 으로 하면 문자형 '0' 형 변환되어 사용된다.

객체의 프로토타입

객체에는 __proto__ 라는 프로퍼티가 존재한다. 이 프로퍼티는 프토타입의 상속과 관련된 내용들이 정의되어있다.

따라서 __proto__ 라는 예약어는 사용하면 안된다.

프로퍼티 존재 여부 확인하기 in

자바스크립트에서는 존재하지 않는 프로퍼티에 접근하면 에러가 발생하지 않고 undefined 를 반환한다.

따라서 의도치 않은 에러를 만날수 있기 때문에 프로퍼티가 존재하는지 여부를 확인할 필요가 있을것이다.

const obj = {
  name: '존',
};

console.log('name' in obj); // true
console.log(obj['name']); // 존

console.log('age' in obj); // false
console.log(obj['age']); // undefined

for in 문

for in 문 사용하면 객체의 모든 프로퍼티 를 순회할 수 있다.

let user = {
  name: 'John',
  age: 30,
  isAdmin: true,
};

for (let key in user) {
  console.log(key, user[key]);
}

// name John
// age 30
// isAdmin true

객체 정렬방식

  • 정수 프로퍼티는 자동정렬.

  • 그 외의 프로퍼티는 객체에 추가한 순서대로 정렬.

let codes = {  49: '독일',  41: '스위스',  44: '영국',  // ..,  1: '미국',};for (let code in codes) {  console.log(code, codes[code]); // 1, 41, 44, 49}// 1 미국// 41 스위스// 44 영국

4.2 참조에 의한 객체 복사

원시형과 참조형의 복사

원시형, 참조형 모두 기본적으로 복사하면 같은 참조값을 가리킨다.

허나 복사한 곳에서 값을 수정했을 때 차이가 두드러진다.

복사한 곳에서 값을 수정했을 때

  • 원시형인 경우 (변수의 값 수정), 복사한곳에서 다른 값을 가리킨다.
  • 참조형인 경우 (프로퍼티의 값 수정), 프로퍼티의 값은 변경되었지만 여전히 같은 객체를 가리키고 원본에도 똑같이 변경된 프로퍼티가 적용된다.

이런 차이점은 객체는 변수가 객체를 곧바로 가리키는 것이아닌 객체를 참조하고있는 곳을 가리키기 때문이다.

참조에 의한 비교

객체 비교시 동등 연산자 == 와 일치 연산자 === 는 동일하게 동작한다.

  • 같은 객체를 바라보는 두 변수 비교 (객체 복사)
const obj1 = {};const obj2 = obj1;console.log(obj1 == obj2); // trueconsole.log(obj1 === obj2); // true
  • 다른 객체를 바라보는 두 변수 비교 (같은 형태)
const obj1 = {};const obj2 = {};console.log(obj1 == obj2); // falseconsole.log(obj1 === obj2); // false

  • 원시형 변수 복사 비교
const a = 1;const b = a;console.log(a == b); // trueconsole.log(a === b); // true
  • 같은 값을 가리키는 두 원시형 변수 비교
const a = 1;const b = 1;console.log(a == b); // trueconsole.log(a === b); // true

객체 복사 방법

값은 같지만 독립된 객체를 만드는 방법

  • 원시 프로퍼티 수준까지 순회하며서 복사
const user = {  name: 'John',  age: 30,};// 객체 복사const clone1 = user;// 객체 복제 (깊은복사)const clone2 = {}; // 새로운 빈 객체// 빈 객체에 user 프로퍼티 전부를 복사for (let key in user) {  clone2[key] = user[key]; // 모든 프로퍼티를 순회하면서 복사}// 결과확인console.log(user);   // { name: 'John', age: 30 }console.log(clone1); // { name: 'John', age: 30 }console.log(clone2); // { name: 'John', age: 30 }console.log(user == clone1); // trueconsole.log(user === clone1); // trueconsole.log(user == clone2); // falseconsole.log(user === clone2); // false
  • Object.assign 을 활용한 복사
Object.assign(dest, [src1, src2, src3...])

dest : 복사를 할 목표 변수

src : 복사를 하고자 하는 객체

반환값 : dest 를 반환

// 예제1let user = { name: "John" };let permissions1 = { canView: true };let permissions2 = { canEdit: true };// permissions1과 permissions2의 프로퍼티를 user로 복사한다.Object.assign(user, permissions1, permissions2);// now user = { name: "John", canView: true, canEdit: true }// 예제2// 동일한 이름을 가진 프로퍼티가 있는 경우엔 기존 값을 덮어씌운다.let user = { name: "John" };Object.assign(user, { name: "Pete" });console.log(user.name); // user = { name: "Pete" }// 예제3let user = {  name: "John",  age: 30};let clone = Object.assign({}, user);

중첩된 객체 복사

const user = {  name: 'John',  size: {    height: 182,    width: 50,  },};const clone = Object.assign({}, user);console.log(user === clone);  // falseconsole.log(user.size === clone.size); // trueclone.size.height = 200; // 내부의 중첩된 객체의 값 수정console.log(user);  // { name: 'John', size: { height: 200, width: 50 } }console.log(clone);// { name: 'John', size: { height: 200, width: 50 } }

객체 안에 또 다른 객체가 중첩되어 있는 형태의 경우 위에서 언급한 깊은 복사 방식을 사용해도 내부의 중첩된 객체는 동일한 객체를 참조하게 된다.

이런 문제를 해결하기 위해서는 모든 프로퍼티를 순회하면서 해당 프로퍼티의 값이 객체인 경우 객체의 구조를 복사하는 방식으로 해결해야한다.

이렇게 객체안에 중첩된 객체까지 복사를 하는 것을 깊은 복사(deep copy)라고 한다.

깊은 복사(deep copy) 방법

// lodash 메서드_.cloneDeep(obj)

반환값 : 깊은 복사된 객체

위 메서드를 사용하면 깊은 복사를 할 수 있다.

const user = {  name: 'John',  size: {    height: 182,    width: 50,  },};const clone = _.cloneDeep(user);console.log(user === clone); // falseconsole.log(user.size === clone.size); // falseclone.size.height = 200; // 내부의 중첩된 객체의 값 수정console.log(user);// { name: 'John', size: { height: 180, width: 50 } }console.log(clone);// { name: 'John', size: { height: 200, width: 50 } }

4.3 가비지 컬렉션

자바스크립트는 메모리 관리를 위해 엔진 내에서 가비지 컬렉터가 끈임없이 동작하며,

원시값, 객체, 함수등 메모리에 차지하고 있는 요소들을 모니터링하고 삭제한다.

가비지 컬렉션 기준

도달 가능성(reachability) 이라는 개념을 통해 메모리 관리를 수행.

도달 가능한(reachable) 값은 쉽게 말해 어떻게든 접근하거나 사용할 수 있는 값을 의미.

도달 가능한(reachable) 값은 메모리에서 삭제되지 않는다.

루트(root) : 태새부터 도달 가능하기 때문에 명백한 이유 없이는 삭제되지 않는다.

  • 현재 함수의 지역변수매개변수
  • 중첩 함수의 체인에 있는 함수에서 사용되는 변수와 매개변수
  • 전역 변수
  • 기타 등등

루트(root)가 참조하는 값이나 체이닝으로 루트에서 참조할 수 있는 값은 도달 가능한 값이 된다.

가비지 컬렉팅 대상

루트 or 전역(global)에서 도달할 수 없는 값은 가비지 컬렉팅 대상이 된다.

가비지 컬렉션 알고리즘 : mark-and-sweep

  • 루트 정보 수집
  • 루트가 참조하고 있는 모든 객체와 참조가 중첩된 모든 객체를 방문하면 mark
  • 루트가 도달가능한 모든 객체를 전부 방문 할 때가지 반복
  • mark되지 않은 모든 객체는 삭제

4.4 메서드와 this

메서드

객체의 프로퍼티에 저장된 함수를 메서드라고한다.

// 함수 표현식, const user = {    name: '존',    sayHi: function() {        console.log('hello');    }}// 화살표 함수const user = {    name: '존',    sayHi: () => {        console.log('hello');    }}// 메서드 단축구문const user = {    name: '존',    sayHi() {        console.log('hello');    }}

메서드와 this

메서드는 객체에 저장된 정보에 접근할 수 있어야 비로속 제 역할을 할 수 있다.

일반 적으로 메서드가 객체의 프로퍼티의 값을 활용한다.

메서드 내부에서 this 키워드를 사용하면 현재 객체에 접근할 수 있다.

const user = {  name: 'john',  sayHi: function sayHi() {    console.log(`hello! my name is ${this.name}`);  },};user.sayHi();

자바스크립트의 특이하고 자유로운 this

  • 자바스크립트에선 모든 함수는 this 키워드를 사용할 수 있다.

  • 함수의 실행 컨텍스트에 따라 this 가 참조하는 값이 달라진다.

function showName() {  console.log(this.name);}const user = {  name: '존',  showThis,};const admin = {  name: '피터',  showThis,};user.showThis(); // 존, this는 useradmin.showThis(); // 피터, this는 admin

궁금증

function showName() {console.log(this);}showName(); // node라면 global, 브라우저라면 window

위와 같은 함수를 이용하면 전역객체를 확인할 수 있다.

const name = '마이크';function showName() {console.log(this.name);}const user = {name: '존',showThis,};const admin = {name: '피터',showThis,};showName(); // undefined, 마이크를 예상했는데...user.showThis(); // 존admin.showThis(); // 피터

위와 같이 showName() 을 하면 this 가 전역객체이고 전역에 선언되어있는 name 을 출력할 줄 알았는데 undefined가 출력되었다.
왜 전역에 정의되어있는 name 을 참조하지 못할까..?

화살표 함수의 this

화살표 함수에는 고유한 this 를 가지고 있지 않다.

화살표 함수의 바로 한 단계 외부함수의 this를 참조한다.

아래 코드를 보면 많이 헷갈린다. 계속 고민해보자.

const user = {  firstName: '보라',  sayHi() {    const name = '철수';    const arrow = () => {      const name = '훈이';      console.log(this.firstName);    };    arrow();  },};user.sayHi(); // 보라

arrow()this는 외부 함수 user.sayHi()this가 된다.

  • 외부 컨텍스트에 있는 this를 이용하고 싶은 경우 화살표 함수를 사용하자.

화살표함수 this 테스트

const showName = () => {  console.log(this.name);};const user = {  name: '존',  showName,};const admin = {  name: '피터',  showName,};user.showName();  // undefinedadmin.showName(); // undefined
const showName1 = () => {  console.log(this.name);};const user = {  name: '존',  show: showName1,};const guest = {  name: '잭',  show() {    showName1();  },};const admin = {  name: '피터',  show() {    const showName2 = () => { //정의부, 외부함수 admin.show()의 this를 참조      console.log(this.name);    };    showName2();  },};user.show();  // undefinedguest.show(); // undefinedadmin.show(); // 피터

위 코드를 보아 함수의 정의부의 바로 윗 단계의 외부의 this 를 참조하는 것같다.

profile
console.log(noah(🍕 , 🍺)); // true

0개의 댓글