1. 로컬 커밋 메시지 수정

1.1 가장 최근 커밋만 수정하기

git commit --amend

1.2 이전 커밋 수정 및 커밋 여러 개 수정하기

git rebase -i HEAD~3

위에서부터 3번째 커밋까지 다음과 같은 화면에 나타난다.

수정을 하고 싶은 커밋 앞에 있는 pickreword로 바꾸고 저장하면 커밋 수정화면으로 이동한다.

여러 커밋을 reword로 바꾸고 저장하면 순서대로 커밋 수정화면이 나타난다.

수정을 하고 저장하면 log에 반영이 된다.

 

 

2. remote 레포지토리 커밋 메시지 수정

위의 방법으로 로컬의 커밋 메시지를 수정한 다음 --force 옵션으로 push를 하면 된다.

git push --force {브랜치명}

이렇게 하면 remote의 커밋 메시지는 수정되지만 해당 커밋을 log로 가지고 있는 팀원의 커밋 메시지는 수정되지 않는다.

github에서도 권장하지 않는 방법이기 때문에 push 전에 커밋 메시지를 확인하는 습관을 들이자!

 

개요
const element = <h1>Hello, React!</h1>;

JSX는 자바스크립트를 확장한 문법으로 React에서 많이 사용한다.

위 문장을 자바스크립트로 변환하면 다음과 같이 React element를 반환하는 것을 확인할 수 있다.

https://bit.ly/2wMpkk2

 

닫힌 태그

JSX에서 사용하는 HTML 태그들은 모두 닫아주어야 한다.

HTML에서 input, br 태그를 닫지 않고 사용할 수 있지만 JSX에서는 허용되지 않는다.

또한 두개 이상의 태그는 하나의 태그 안에 포함되어야 한다.

const element = (
  <h1>Hello, Nungu</h1>
  <h2>hi</h2>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

위처럼 element를 렌더링하면 에러가 발생하기 때문에 다음과 같이 하나의 태그 안에 다 넣어줘야 한다.

const element = (
  <div>
    <h1>Hello, Nungu</h1>
    <h2>hi</h2>
  </div>
);

그러나 위의 div 태그는 불필요한데 이와 같은 경우에는 리액트의 Fragment를 사용하면 된다.

const element = (
  <>
    <h1>Hello, Nungu</h1>
    <h2>hi</h2>
  </>
);

태그를 이름 없이 작성하면 Fragment가 만들어지는데 브라우저 상에서 별도의 엘리먼트로 나타나지 않는다.

 

표현식 포함하기

JSX에 유효한 모든 자바스크립트 표현식을 넣을 수 있다.

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

ReactDOM.render(
  element,
  document.getElementById('root')
);

name이라는 변수를 선언하고 중괄호로 감싸서 JSX 안에 사용한 예시이다.

함수의 호출 결과도 JSX에 넣을 수 있다.

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

위와 같이 JSX를 여러 줄로 작성하려면 괄호를 넣는 것이 좋다.

자바스크립트는 자동으로 줄바꿈마다 세미콜론을 넣기 때문에 괄호로 감싸지 않으면 에러가 발생할 수 있다.

 

JSX 자체도 표현식이기 때문에 if 구문, for문 안에 사용할 수 있고 변수 할당 및 함수에서 반환값으로 사용하는 것도 가능하다.

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

 

속성 정의

HTML에서 태그의 속성을 정의하는 것처럼 JSX도 속성을 정의할 수 있다.

// 문자열
const element = <a href="https://www.reactjs.org"> link </a>;
// 자바스크립트 표현식
const element = <img src={user.avatarUrl}></img>;

첫번째처럼 문자열로 정의할 수도 있고 두번째처럼 자바스크립트 표현식을 넣을 수도 있다.

다만, 두 가지를 동시에 사용하는 것은 안된다.

 

style 속성을 정의할 때 이미 정의된 객체를 사용하는 것이 가능하다.

const style = {
  backgroundColor: "black",
  color: "aqua",
  fontSize: 24, // 기본 단위 px
  padding: "1rem", // 다른 단위 사용 시 문자열로 설정
};

const element = (
  <div style={style}>React</div>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

 

위에서 설명한 것처럼 JSX는 자바스크립트이기 때문에 class라는 단어를 사용할 수 없다. 물론 다른 예약어도 쓸 수 없다.

그래서 HTML의 속성명을 그대로 사용하지 않고 camelCase 프로퍼티 명명 규칙을 사용한다.

이때 class는 className이 된다.

// index.css
.gray-box {
  background: gray;
  width: 64px;
  height: 64px;
}

// index.js
const element = <div className="gray-box"></div>;

ReactDOM.render(
  element,
  document.getElementById('root')
);

 

주석

JSX 내부에 주석을 넣는 것도 가능하다. { /* 주석 내용 */ } 과 같이 사용한다.

const element =(
  <div>
    {/* 주석 내용 */}
    React
    <br
    // 이건 열린 태그 내부 주석
    />
  </div>
);

열린 태그 내부에는 //로 주석을 추가할 수 있다.

'Web > React' 카테고리의 다른 글

[React] React는 왜 만들어졌을까?  (0) 2022.06.24

 

심볼

자바스크립트는 객체의 프로퍼티 키로 문자열과 심볼형만 가능하다고 했다. 그럼 심볼형이란 무엇일까?

'심볼(symbol)'은 유일한 식별자(unique indentifier)를 만들고 싶을 때 사용한다.

Symbol()을 사용해서 심볼값을 만들 수 있다. 매개변수를 넣으면 심볼 이름을 붙일 수 있다.

let id1 = Symbol();
let id2 = Symbol("id2")

심볼은 유일성이 보장되는 자료형이기 때문에 설명이 같은 심볼을 만들어도 각 심볼값은 다르다.

심볼 이름은 단지 심볼에 붙이는 설명이 될 뿐 심볼값에 영향을 주지 않는다.

 

자바스크립트의 대부분의 자료형은 문자형으로의 암시적 형 변환이 일어나지만 심볼형은 그렇지 않다.

심볼을 출력해야하는 상황이라면 toString()을 명시적으로 호출해야 한다.

또는 description 프로퍼티를 이용해서 심볼 이름만 보여주는 것도 가능하다.

let id = Symbol("id");
alert(id) // TypeError: Cannot convert a Symbol value to a string
alert(id.toString()); // Symbol(id)가 얼럿 창에 출력됨
lert(id.description); // id

 

hidden 프로퍼티

심볼을 이용하면 hidden 프로퍼티를 만들 수 있다.

hidden 프로퍼티는 외부 코드에서의 접근과 값을 덮어쓰는 것이 불가능한 프로퍼티이다.

// 서드파티 코드에서 가져온 객체
let user = {
  name: "John"
};

let id = Symbol("id");

user[id] = 1;

alert( user[id] ); // 심볼을 키로 사용해 데이터에 접근

user에 식별자를 붙여주기 위해 Symbol("id")를 프로퍼티 키값으로 설정했다.

user는 서드파티 코드에서 가져온 객체이기 때문에 함부로 새로운 프로퍼티를 추가할 수 없다.

그러나 심볼은 서드파티 코드에서 접근할 수 없기 때문에 심볼을 사용해서 서드파티 코드가 모르게 user에 식별자를 부여한 것이다.

 

// ...
let id = Symbol("id");

user[id] = "외부 스크립트 id 값";

또 다른 상황으로 자바스크립트 라이브러리와 같은 외부 스크립트에서 서드파티 코드 객체인 user를 식별해야 한다고 해보자.

외부 스크립트는 위와 같이 전용 식별자를 만들어서 사용할 수 있을 것이다.

만약에 심볼을 사용하지 않고 문자열 "id"를 사용해서 식별자를 만들었다면 충돌이 발생할 가능성이 있다.

 

객체 리터럴을 사용해서 객체를 만들고 심볼을 프로퍼티 키로 설정하고 싶다면 대괄호를 사용하면 된다.

let id = Symbol("id");

let user = {
  name: "John",
  [id]: 123 // "id": 123은 안됨
};

 

또한 키가 심볼인 프로퍼티는 for...in 구문에서 제외된다.

let id = Symbol("id");
let user = {
  name: "John",
  age: 30,
  [id]: 123
};

for (let key in user) alert(key); // name과 age만 출력되고, 심볼은 출력되지 않음

Object.keys(user)에서도 키가 심볼인 프로퍼티는 배제되지만 Object.assign은 키가 심볼인 프로퍼티를 배제하지 않고 객체 내 모든 프로퍼티를 복사한다.

 

내장 메서드 Object.getOwnPropertySymbols(obj)를 사용하면 모든 심볼을 볼 수 있고,  Reflect.ownKeys(obj) 메서드를 호출하면 심볼형 키를 포함한 객체의 모든 키를 반환한다.

심볼을 완전히 숨길 수는 없지만 대부분의 라이브러리, 내장 함수 등은 위 메서드를 사용하지 않는다.

 

전역 심볼

심볼 이름이 같아도 심볼 값은 다르다고 했다.

그러나 여러 곳에서 심볼 이름이 같은 객체에 접근해야 할 수도 있는데 전역 심볼 레지스트리는 이런 경우를 위해 만들어졌다.

전역 심볼 레지스트리 안에 심볼을 만들고 해당 심볼에 접근하면 이름이 같은 경우 항상 동일한 심볼을 반환한다.

레지스트리 안에 있는 심볼을 읽거나 새로운 심볼을 생성하려면 Symbol.for(key)를 사용하면 된다.

// 전역 레지스트리에서 심볼을 읽고 없으면 새로 만듦
let id = Symbol.for("id");
let id2 = Symbol.for("id"); // 같은 심볼 반환
alert(id === id2); // true

 

Symbol.for(key)는 이름을 가진 심볼을 반환하고 Symbol.keyFor(sym)해당 심볼의 이름을 반환한다.

// 이름을 이용해 심볼을 찾음
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// 심볼을 이용해 이름을 얻음
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id

전역 심볼에 대해서만 사용할 수 있고 전역 심볼이 아닌 인자가 넘어오면 Symbol.keyFor는 undefined를 반환한다.

일반 심볼 이름을 알고 싶다면 description 프로퍼티에 접근하면 되고 전역 심볼 이름을 알고 싶다면 Symbol.keyFor를 사용하면 된다.

 

ToPrimitive

객체 형 변환은 세 종류로 구분되는데 'hint'라고 불리는 값이 구분 기준이 된다.

hint는 목표로 하는 자료형 정도로 생각하면 된다.

 

1. string

alert와 같이 문자열을 기대하는 연산을 수행할 때 hint는 string이 되고 객체는 문자형으로 형 변환이 된다.

// 객체를 출력하려고 함
alert(obj);

// 객체를 프로퍼티 키로 사용하고 있음
anotherObj[obj] = 123;

 

2. number

수학 연산을 적용하려고 할 때 hint는 number가 되고 객체는 숫자형으로 형 변환이 된다.

// 명시적 형 변환
let num = Number(obj);

// (이항 덧셈 연산을 제외한) 수학 연산
let n = +obj; // 단항 덧셈 연산
let delta = date1 - date2;

// 크고 작음 비교하기
let greater = user1 > user2;

 

3. default

연산자가 기대하는 자료형이 확실하지 않을 때 hint는 default가 된다. 아주 드물게 발생한다.

이항 덧셈 연산자 +는 문자열을 합치는 연산과 숫자를 더해주는 연산 둘 다 가능하기 때문에 이 +의 피연산자가 객체이면 hint는 default가 된다.

동등 연산자 ==를 사용해서 객체-문자형, 객체-숫자형, 객체-심볼형끼리 비교할 때도 hint는 default가 된다.

// 이항 덧셈 연산은 hint로 `default`를 사용
let total = obj1 + obj2;

// obj == number 연산은 hint로 `default`를 사용
if (user == 1) { ... };

비교 연산자 <, >도 피연산자가 문자형, 숫자형이 될 수 있지만 이 경우는 hint를 number로 고정한다.

또한 모든 객체는 true이기 때문에 boolean hint는 없다.

 

자바스크립트는 형 변환이 필요할 때 아래와 같은 알고리즘에 따라 원하는 메서드를 찾고 호출한다.

  1. 객체에 obj[Symbol.toPrimitive](hint) 메서드가 있는지 찾고 있다면 호출한다. Symbol.toPrimitive는 시스템 심볼로 심볼형 키로 사용된다.
  2. 1에 해당하지 않고 hint가 string이라면, obj.toString()이나 obj.valueOf()를 호출한다.
  3. 1, 2에 해당하지 않고 hint가 number 또는 default라면, obj.valueOf()나 obj.toString()을 호출한다.

 

Symbol.toPrimitive

Symbol.toPrimitive는 자바스크립트의 내장 심볼로 목표로 하는 자료형(hint)를 명명하는 데 사용된다.

user 객체에 객체-원시형 변환 메서드 obj[Symbol.toPrimitive](hint)를 구현해보자.

let user = {
  name: "John",
  money: 1000,

  [Symbol.toPrimitive](hint) {
    alert(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.money;
  }
};

alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500

이렇게 메서드를 구현함으로써 user는 hint에 따라 문자열로 변환되기도 하고 숫자로 변환되기도 한다.

메서드 하나로 모든 종류의 형 변환을 다룰 수 있다.

 

toString과 valueOf

객체 형 변환을 할 때 Symbol.toPrimitive가 없으면 toString이나 valueOf를 호출한다고 했다.

  • hint == "string" : toString -> valueOf 순으로 호출
  • 그 외 : valueOf -> toString 순으로 호출

이 메서드들은 반드시 원시값을 반환해야 하고 객체를 반환하면 그 결과는 무시된다.

일반 객체는 기본적으로 다음 규칙을 따른다.

  • toString은 문자열 "[object Object]"를 반환
  • valueOf는 객체 자신을 반환

valueOf는 객체를 반환하기 때문에 그 결과가 무시된다.

이제 이 메서드들을 사용해서 Symbol.toPrimitive를 호출한 것과 같은 결과를 내도록 만들어보자.

let user = {
  name: "John",
  money: 1000,

  // hint가 "string"인 경우
  toString() {
    return `{name: "${this.name}"}`;
  },

  // hint가 "number"나 "default"인 경우
  valueOf() {
    return this.money;
  }

};

alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500

 

형 변환을 한 곳에서 처리해주고 싶으면 toString만 구현하면 된다.

객체에 Symbol.toPrimitive와 valueOf가 없으면 toString이 모든 형 변환을 처리한다.

let user = {
  name: "John",

  toString() {
    return this.name;
  }
};

alert(user); // toString -> John
alert(user + 500); // toString -> John500

 

'Web > JS' 카테고리의 다른 글

[JS] 옵셔널 체이닝  (0) 2022.06.27
[JS] 객체(Object) - 2  (0) 2022.06.27
[JS] 가비지 컬렉션  (0) 2022.06.27
[JS] 객체(Object)  (0) 2022.06.08
[JS] 함수(function)  (0) 2022.06.07

왜 사용할까?

어떤 서비스의 사용자들이 있고 그 중 몇명은 주소 정보를 입력하지 않았다고 해보자.

어떤 사용자의 주소 정보를 얻기 위해 user.address.street과 같이 접근할 수 있는데 그 사용자가 주소 정보가 없다면 에러가 발생할 수 있다.

아니면 웹을 개발할 때 페이지에 존재하지 않는 요소에 접근해서 정보를 가져오려고 할 때도 문제가 생길 수 있다.

위와 같은 상황에서는 && 연산자를 사용해서 객체나 객체 프로퍼티가 있는지 검사하곤 했다.

간편하지만 코드가 길어진다는 단점이 있고 이를 보완하기 위해 옵셔널 체이닝이 등장했다.

 

옵셔널 체이닝

옵셔널 체이닝은 '?.'로 나타낸다.

?. 앞의 대상이 undefined 또는 null이면(값이 존재하지 않으면) 뒷부분의 실행을 멈추고 undefined를 반환한다.

let user = {}; // 주소 정보가 없는 사용자

// AND 연산자
alert( user && user.address && user.address.street );
// 옵셔널 체이닝
alert( user?.address?.street );

만약에 user?.address.street과 같이 접근을 한다면 user 값이 존재하는 이상 address 프로퍼티는 반드시 있어야 한다.

그렇지 않으면 street에 접근할 때 에러가 발생하게 된다.

 

?.(), ?.[]

?. 는 연산자가 아니고 함수나 대괄호와 함께 동작하는 특별한 문법 구조체이다.

존재 여부가 확실치 않은 함수를 호출할 때도 옵셔널 체이닝을 적용할 수 있다.

let user1 = {
  admin() {
    alert("관리자 계정입니다.");
  }
}

let user2 = {};

user1.admin?.(); // 관리자 계정입니다.
user2.admin?.();

user1에는 admin 프로퍼티가 있기 때문에 실행이 되지만 user2는 admin 프로퍼티가 없기 때문에 뒷부분 실행이 되지 않는다.

 

프로퍼티에 접근할 때는 다음과 같이 대괄호를 사용하는 것도 가능하다.

let user1 = {
  firstName: "Violet"
};

let user2 = null;

let key = "firstName";

alert( user1?.[key] ); // Violet
alert( user2?.[key] ); // undefined

 

delete와 조합해서 값이 존재하면 삭제하는 로직을 짜는 것도 가능하다.

delete user?.name; // user가 존재하면 user.name을 삭제

 

옵셔널 체이닝은 읽기, 삭제하기에는 사용할 수 있지만 쓰기에는 사용할 수 없다.

// user가 존재할 경우 user.name에 값 넣기

user?.name = "Violet"; // SyntaxError: Invalid left-hand side in assignment
// 에러가 발생하는 이유는 undefined = "Violet"이 되기 때문입니다.

 

'Web > JS' 카테고리의 다른 글

[JS] 심볼형과 객체 형변환  (0) 2022.06.28
[JS] 객체(Object) - 2  (0) 2022.06.27
[JS] 가비지 컬렉션  (0) 2022.06.27
[JS] 객체(Object)  (0) 2022.06.08
[JS] 함수(function)  (0) 2022.06.07

메서드

객체의 프로퍼티에는 모든 자료형이 허용된다고 했는데 함수도 할당할 수 있다.

이 때 객체 프로퍼티에 할당된 함수를 메서드(method)라고 한다.

let user = {
  name: "John",
  age: 30
};

function sayHi() {
  alert("안녕하세요!");
};

// 메서드 등록
user.sayHi = sayHi;
user.sayHi(); // 안녕하세요!

객체 리터럴 안에 직접 메서드를 선언하는 것도 가능하다.

user = {
  sayHi: function() {
    alert("Hello");
  }
};

// 단축 구문
user = {
  // "sayHi: function()"과 동일
  sayHi() { 
    alert("Hello");
  }
};

아래와 같이 단축 구문으로 선언하는 것이 더 깔끔하다.

 

메서드와 this

대부분의 메서드는 객체 프로퍼티의 값을 활용한다.

위에서 정의한 user 객체의 name 값을 sayHi 메서드에서 사용한다고 해보자.

이때 this 키워드를 사용해서 메서드를 호출할 때 사용된 객체에 접근할 수 있다.

let user = {
  name: "John",
  age: 30,

  sayHi() {
    // 'this'는 '현재 객체'를 나타냄
    alert(this.name);
  }
};

user.sayHi(); // John

물론 alert(user.name)와 같이 외부 변수를 참조해서 객체에 접근하는 것도 가능하다.

만약에 user를 복사해서 다른 변수에 할당하고 user에 다른 값으로 덮어쓰게 된다면 user.name에서 에러가 발생한다.

위와 같이 예기치 못한 상황을 방지하기 위해서는 this를 사용하는 것이 좋다.

 

동일한 함수라도 다른 객체에서 호출했다면 this가 참조하는 값이 달라진다.

let user = { name: "John" };
let admin = { name: "Admin" };

function sayHi() {
  alert( this.name );
}

user.f = sayHi;
admin.f = sayHi;

user.f(); // John  (this == user)
admin.f(); // Admin  (this == admin)

admin['f'](); // Admin (점과 대괄호는 동일하게 동작함)

 

객체가 없어도 this를 호출할 수 있는데 이 때 this는 undefined가 된다.

이는 엄격 모드일 경우이고 엄격 모드가 아닐 때 this는 전역 객체를 참조하며 브라우저 환경에서는 window라는 전역 객체를 참조하게 된다.

 

자바스크립트의 모든 함수에서는 this를 사용할 수 있는데 이는 this 값이 런타임에 결정되기 때문이다.

함수를 하나만 만들어서 여러 객체에서 재사용할 수 있다는 장점이 있지만 이러한 유연함은 실수로 이어지기 쉽다.

따라서 this의 동작 방식을 충분히 이해하고 실수를 피할 수 있도록 해야 한다.

 

화살표 함수에서의 this

화살표 함수는 일반 함수와 달리 this를 가지지 않는다.

화살표 함수에서 this를 참조하면 화살표 함수가 아닌 일반 외부 함수에서 this 값을 가져온다.

let user = {
  firstName: "보라",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // 보라

arrow 함수의 this는 외부 함수 sayHi 함수의 this를 가져오므로 sayHi를 호출하는 user를 참조하게 된다.

 

생성자 함수

객체 리터럴을 사용하면 객체를 쉽게 만들 수 있지만 비슷한 객체를 여러 개 만들어야 할 수도 있다.

이 때 생성자 함수를 사용하면 된다. 생성자 함수는 다음과 같은 관례를 따른다.

  1. 함수 이름의 첫 글자는 대문자로 시작한다.
  2. 반드시 'new' 연산자를 붙여서 실행한다.
function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("보라");

alert(user.name); // 보라
alert(user.isAdmin); // false

new로 생성자 함수를 호출하면 다음과 같은 알고리즘이 동작한다.

  1. 빈 객체를 만들어 this에 할당한다.
  2. 함수 본문을 실행한다. this에 새로운 프로퍼티를 추가하게 된다.
  3. this를 반환한다.

사실 모든 함수는 생성자 함수가 될 수 있다. new를 붙여서 실행하면 어떤 함수라도 위의 알고리즘이 동작하기 때문이다.

 

생성자 함수는 this를 자동으로 반환하기 때문에 return문을 명시적으로 적어주지 않아도 된다.

만약에 return문이 있다면 다음과 같은 규칙이 적용된다.

  • 객체를 return하면 this 대신 객체가 반환된다.
  • 원시형을 return하면 return문은 무시된다.

 

생성자 함수 내에서 메서드를 정의하는 것도 가능하다.

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "제 이름은 " + this.name + "입니다." );
  };
}

let bora = new User("이보라");

bora.sayHi(); // 제 이름은 이보라입니다.

/*
bora = {
   name: "이보라",
   sayHi: function() { ... }
}
*/

 

 

 

'Web > JS' 카테고리의 다른 글

[JS] 심볼형과 객체 형변환  (0) 2022.06.28
[JS] 옵셔널 체이닝  (0) 2022.06.27
[JS] 가비지 컬렉션  (0) 2022.06.27
[JS] 객체(Object)  (0) 2022.06.08
[JS] 함수(function)  (0) 2022.06.07

가비지 컬렉션 기준

자바스크립트는 도달 가능성(reachability)이라는 개념으로 메모리를 관리한다.

도달 가능한 값은 어떻게든 접근하거나 사용할 수 있는 값을 의미한다. 따라서 메모리에서 삭제되지 않는다.

아래 값들은 명백한 이유가 없으면 삭제되지 않는 것들이다.

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

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

자바스크립트의 엔진 내에 있는 가비지 컬렉터(garbage collector)는 메모리에 있는 값들이 도달 가능한지 끊임없이 모니터링하고 도달 불가능한 값은 삭제한다.

 

예시

function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
});

marry 함수는 매개변수로 받은 두 객체를 서로 참조하고 새로운 객체를 반환한다.

메모리에서 다음과 같은 구조를 가질 것이다.

현재는 모든 객체가 도달 가능한 상태이다.

이 상태에서 참조 2개를 지워보자.

delete family.father;
delete family.mother.husband;

위 코드를 실행하고 나면 메모리 구조는 다음과 같다.

John에 대한 참조가 모두 사라졌기 때문에 해당 객체는 도달 불가능한 상태가 되고 가비지 컬렉터에 의해 삭제된다.

John에서 Ann에 대한 참조가 있긴 하지만 외부에 대한 참조는 도달 가능한 상태에 영향을 주지 않는다.

John이 삭제되고 나면 다음과 같은 메모리 구조를 가질 것이다.

 

내부 알고리즘

mark-and-sweep은 가비지 컬렉션 기본 알고리즘으로 다음과 같은 단계를 거쳐서 수행된다.

  • 가비지 컬렉터가 루트 정보를 수집하고 mark한다.
  • 루트가 참조하고 있는 모든 객체를 방문하고 mark한다.
  • mark된 객체가 참조하고 있는 객체도 mark한다. 한번 방문한 객체는 mark하기 때문에 같은 객체를 다시 방문하지 않는다.
  • 루트에서 도달 가능한 모든 객체를 방문할 때까지 위 과정을 반복한다.
  • mark되지 않은 객체는 메모리에서 삭제된다.

자바스크립트 엔진은 실행에 영향을 주지 않으면서 가비지 컬렉션을 더 빠르게 수행하기 위한 최적화 기법도 적용한다.

  • generational collection(세대별 수집): 대부분의 객체는 생성 이후 제 역할을 다하고 쓸모가 없어지는데 이러한 객체를 '새로운 객체'로 구분하고 가비지 컬렉터가  우선적으로 삭제한다. 일정 시간 이상 살아남은 객체는 '오래된 객체'로 분류하고 모니터링을 줄인다.
  • incremental collection(점진적 수집): 방문할 객체가 많으면 mark에 시간이 많이 소요되고 가비지 컬렉션 속도도 느려지게 된다. 이를 개선하기 위해 가비지 컬렉션을 여러 부분으로 분리해서 각 부분을 별도로 수행한다. 긴 작업을 여러 개의 짧은 작업으로 분산시킬 수 있다는 장점이 있다.
  • idle-time collection(유휴 시간 수집): 가비지 컬렉터가 실행에 주는 영향을 최소화하기 위해 CPU가 idle할 때에만 가비지 컬렉션을 수행한다.

이 외에도 다양한 최적화 기법과 가비지 컬렉션 알고리즘이 더 존재한다.

'Web > JS' 카테고리의 다른 글

[JS] 옵셔널 체이닝  (0) 2022.06.27
[JS] 객체(Object) - 2  (0) 2022.06.27
[JS] 객체(Object)  (0) 2022.06.08
[JS] 함수(function)  (0) 2022.06.07
[JS] 연산자  (0) 2022.06.03
개요

이전에는 사용자와 인터렉션을 할 때 자바스크립트의 특정 값이 바뀌면 특정 DOM의 속성이 바뀌도록 했다.

React는 DOM을 어떻게 업데이트를 할 지 규칙을 정하는 것이 아니라 아예 새로 그려서 보여준다는 발상으로 개발되었다.

기존에 있던 것을 다 날리고 새로 그리는 것은 시간이 오래 걸릴 것으로 생각할 수 있는데 이는 Virtual DOM을 통해 해결했다.

 

Virtual DOM

브라우저에 실제로 보여지는 DOM이 아닌, 메모리에 가상으로 존재하는 DOM이다.

자바스크립트 객체이기 때문에 브라우저에 실제 DOM을 보여주는 것보다 속도가 훨씬 빠르다.

React에서 상태 업데이트가 일어나면 VirtualDOM에 렌더링을 한다.

그리고 React 개발팀에서 만든 효율적인 알고리즘을 통해 실제 브라우저의 DOM과 Virtual DOM을 비교해서 차이점을 실제 DOM에 적용한다.

아래 동영상에서 React 작동 원리에 대해 간단하고 쉽게 설명하고 있다.

 

 

참고 사이트

리액트 학습을 위해 다음 사이트들을 참고할 예정이다.

 

시작하기 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

벨로퍼트와 함께하는 모던 리액트 · GitBook

벨로퍼트와 함께하는 모던 리액트 본 강의자료는 패스트캠퍼스 온라인 강의에서 제공하는 리액트 강의에서 사용되는 강의 문서입니다. 이 튜토리얼은 여러분들이 JavaScript 의 기초를 잘 알고있

react.vlpt.us

 

Understanding client-side JavaScript frameworks - Web 개발 학습하기 | MDN

자바스크립트 프레임워크는 최신 프론트엔드 웹 개발의 필수 부분으로, 개발자에게 확장 가능한 대화형 웹 응용 프로그램을 구축하기 위한 검증된 도구를 제공합니다. 많은 현대 기업들은 프레

developer.mozilla.org

 

'Web > React' 카테고리의 다른 글

[React] JSX  (0) 2022.07.04

2021 KAKAO BLIND RECURITMENT

https://programmers.co.kr/learn/courses/30/lessons/72412

 

코딩테스트 연습 - 순위 검색

["java backend junior pizza 150","python frontend senior chicken 210","python frontend senior chicken 150","cpp backend senior pizza 260","java backend junior chicken 80","python backend senior chicken 50"] ["java and backend and junior and pizza 100","pyt

programmers.co.kr


map과 이분 탐색의 환장적인 콜라보!

  • 나는 어째서인지 조건들을 무조건 분리해서 map에 다 따로 저장하려고 했는데 조건 전체를 하나의 문자열로 해서 key로 저장하는건 생각 못했다.
  • 주어진 조건을 만족할 수 있는 모든 경우의 수를 만들어보는 것도 역시 생각 못했다.

이틀을 고민하다가 결국 해설지를 보고 풀 수 있었다.

 

1. map을 사용한 자료구조

"java backend junior pizza 150"

위와 같은 정보를 가진 지원자는 쿼리 조건에 -가 올 수 있는 것을 고려해서 총 16가지의 조건에 부합하는 사람이다.

이 조건들을 key로 하고 점수를 value로 가지는 map을 만들 수 있다.

이렇게 되면 map에는 "javabackendjuniorpizza"를 key로 가지고 150을 value로 가진 요소가 있을 것이다.

16가지의 key를 만드는 법은 다양한데 나는 조합으로 만들었다.

이 때, 같은 조건을 가진 사람들이 여러 명일 수 있기 때문에 value를 vector<int>로 했다.

 

2. 이분 탐색

지원자의 정보 및 쿼리의 최대 갯수를 고려했을 때, 점수를 브루트포스로 하나씩 비교하면 5만 * 10만 = 50억의 시간이 걸리기 때문에 다른 방법을 써야 한다.

자료의 갯수가 많을 때 가장 적절한 방법은 이분 탐색이다.

이 때, 특정 점수를 가진 사람이 처음으로 나오는 인덱스를 알면 몇 명인지 알 수 있기 때문에 lowerbound를 찾으면 된다.

이분 탐색은 정렬된 자료에서 사용 가능하기 때문에 lowerbound를 찾기 전에 vector를 정렬해야 한다.

 

더보기
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <map>
#include <algorithm>

using namespace std;

map<string, vector<int>> list;

void make_comb(vector<string> splitted, string s, int idx, int depth) {
    if (depth == 4) {
        list[s].push_back(stoi(splitted[4]));
        return;
    }
    make_comb(splitted, s + splitted[idx], idx + 1, depth + 1);
    make_comb(splitted, s + "-", idx + 1, depth + 1);
}

vector<string> split_info(string info) {
    vector<string> splitted;
    stringstream input(info);
    string temp;

    while (getline(input, temp, ' ')) {
        splitted.push_back(temp);
    }

    return splitted;
}

pair<string, int> split_query(string query) {
    string splitted = "";
    stringstream input(query);
    string temp;

    for (int i = 0; i < 7; i++) {
        getline(input, temp, ' ');
        if (temp.compare("and") == 0) continue;
        splitted += temp;
    }
    getline(input, temp, ' ');

    pair<string, int> p;
    p.first = splitted;
    p.second = stoi(temp);

    return p;
}

vector<int> solution(vector<string> infos, vector<string> queries) {
    vector<int> answer;

    for (string info : infos) {
        vector<string> splitted = split_info(info);
        make_comb(splitted, "", 0, 0);
    }

    for (auto l : list) {
        sort(l.second.begin(), l.second.end());
        list[l.first] = l.second;
    }

    for (string query : queries) {
        pair<string, int> p = split_query(query);
        // 해당 조건을 가진 지원자가 없을 때
        if (list.find(p.first) == list.end()) {
            answer.push_back(0);
        }
        // 해당 조건을 가진 지원자가 있을 때
        else {
            vector<int> score = list[p.first];
            int idx = lower_bound(score.begin(), score.end(), p.second) - score.begin();
            // 주어진 점수 이상을 가진 지원자가 있을 때
            if (idx < (int)score.size()) {
                answer.push_back((int)score.size() - idx);
            }
            else {
                answer.push_back(0);
            }
        }
    }

    return answer;
}

int main() {
    vector<string> info = {
        "java backend junior pizza 150",
        "python frontend senior chicken 210",
        "python frontend senior chicken 150",
        "cpp backend senior pizza 260",
        "java backend junior chicken 80",
        "python backend senior chicken 50"
    };

    vector<string> query = {
        "java and backend and junior and pizza 100",
        "python and frontend and senior and chicken 200",
        "cpp and - and senior and pizza 250",
        "- and backend and senior and - 150",
        "- and - and - and chicken 100",
        "- and - and - and - 150"
    };

    vector<int> answer = solution(info, query);
    for (int i : answer)
        cout << i << '\n';
}

 

참고) 만약 효율성 검사를 통과하지 못했다면 쿼리를 분리하는 반복문에서 vector를 정렬하고 있지 않은지 봐야한다.

그렇다면 지원자의 정보로 map을 다 만든 후에 순회하면서 vector를 정렬하면 효율성 테스트도 통과할 수 있다.

'Algorithm > programmers' 카테고리의 다른 글

[c++] 수식 최대화  (0) 2022.06.08
[c++] 거리두기 확인하기  (0) 2022.06.07
[c++] 뉴스 클러스터링  (0) 2022.06.03

+ Recent posts