뉴런

  • 한 사람의 뇌에는 평균 860억 개의 뉴련이 있다.
  • 뉴런 간의 연결은 약 100~1,000조 개 정도이다.
  • 뉴런 하나가 약 7,000개 연결을 갖고있다.
  • 가지돌기: 데이터 수신
    • 새로운 패턴이 만들어지면 돌기가 새로 만들어지거나 발달하여 다른 뉴런의 축색 말단에 가까이 가게 된다.
  • 축삭: 데이터 전송, 전선
  • 축삭말단: 데이터 전달
  • 미엘린: 전선 피복
    • 축삭을 따라 흐르는 신호가 손실되지 않고 빠르게 전달되는데에 도움을 준다.
    • 잘 쓰지 않는 경로의 미엘린이 부실하거나 손상되면 뉴런에서 전달하는 신호도 약해지고 느려진다.
  • 시냅스: 뉴런 사이의 연결부
    • 전기 신호를 화학 물질로 바꿔 전달
    • 시냅스의 길이는 20~40 나노미터이며 전기 신호보다 느리긴하지만 공간이 짧아 빠른 통신 가능
    • 학습과 연습이 지속되어 해당 경로가 필요해지면 발신부에서 나오는 신경 전달 물질과 이 물질을 감지하는 수용체를 늘린다.
  • 뉴런이 새로운 것을 배운다는 것은 새로운 경로의 생성과 강화를 의미한다.

구조

뇌의 3층 구조

  • 전두엽: 감정, 운동, 지능
  • 두정엽: 공간, 감각
  • 후두엽: 시각
  • 측두엽: 언어
  • 뇌의 10% 만 사용한다는 설이 상식처럼 자리잡고 있지만, 사실 뇌의 100% 를 모두 사용하고 있다. (fMRI, EGG)
  • 뉴런이 많다고 똑똑한 것은 아니다.
  • 복잡한 일에는 많은 뉴런이 사용되어야하고, 단순한 일에는 적은 뉴런이 사용되어야 효율적인 결과가 나온다.
  • 단순한 일에 많은 뉴런을 사용하면 과적합(overfitting)이고, 복잡한 일에 적은 뉴런을 사용하면 저적합(underfitting)이 된다.

퍼셉트론

뉴런에서 신호를 받는 과정은 4단계로 나눌 수 있다.

  1. 다른 뉴런들에게 신호를 받기
  2. 신호의 합이 충분히 큰지 확인하고 활성화하기
  3. 신호를 먼 곳으로 전달하기
  4. 신호를 다른 뉴런에 전달하기
  • 1단계는 가지돌기에서 일어나며 신호 여러 개를 하나로 모아주는 시그마 연산과 같다.
  • 2단계는 역치를 넘는지 확인하고 활성화를 시킨다. 활성화는 1, 비활성화는 0이므로 이를 만들어주는 활성화 함수 (계단 함수)가 필요하다.
1
2
3

→ ( ∑ ) → ( 활성화함수 ) →

  • 3단계는 미엘린의 발달 여부에 따라 신호의 강도가 달라진다. 발달 정도를 나타내는 가중치인 w'를 곱해준다. 잘 발달된 경우 1에 가까운 값을 주면 된다.
1
2
3

→ ( ∑ ) → ( 활성화함수 ) → ( × w' ) →

  • 4단계는 축색 말단에서 시냅스를 통해 다른 뉴런으로 전달한다. 시냅스의 발달, 가지돌기 가시 크기, 시냅스에서의 화학물질 양, 수용체의 민감도에 따라 신호의 효율이 달라진다. 이 값을 3단계와 동일하게 가중치인 w"를 곱해준다.
1
2
3
4
5
6
7

→ ( ∑ ) → ( 활성화함수 ) → ( × w' ) → ( × w") →



→ ( ∑ ) → ( 활성화함수 ) → ( × W ) →

이 연결을 여러 개로 구성한다면

1
2
3
4
5

→ ( ∑ ) → ( 활성화함수 ) → ( × W₁ ) ↘
→ ( ∑ ) → ( 활성화함수 ) → ( × W₂ ) → ( ∑ ) → ( 활성화함수 ) → ( × W₄ ) →
→ ( ∑ ) → ( 활성화함수 ) → ( × W₃ ) ↗

이를 가운데 부분만 떼어내 퍼셉트론 모델을 만들 수 있다.

1
2
3
→ ( × W₁ ) ↘
→ ( × W₂ ) → ( ∑ ) → ( 활성화함수 ) →
→ ( × W₃ ) ↗

이 인공뉴런을 기본 단위로 지능을 갖게 되는데 여기서 학습을 통해 정해지는 값은 W 이다.
각각의 W 값이 0이되면 연결 소멸, 0보다 크면 연결 생성, 값이 커지면 연결 강화, 기존보다 작아지면 연결 약화가 된다. 학습을 통해 이 값이 최적화 되면 이후 학습한 일을 언제든지 수행 가능하다.

강화학습

  • agent: 학습하고 행동을 취하는 주체
  • environment: 환경
  • reward: 보상
  • state: 상태
  • policy: 방침, 상황을 파악해 행동하는 것
  • 강화학습이 가장 많이 쓰이는 분야는 자율주행이다.
  • 차는 에이전트이며 차도라는 환경에 나와 차선, 사고 등의 마이너스 보상 (패널티)를 받으면 방침을 수정해 도로에서 해야할 것과 하지 말아야할 것을 경험하며 방침을 수정해 결과적으로 도로를 안전하게 주행이 가능하다.
  • 보상과 패널티를 잘 조정해야 원하는 결과가 나온다.
  • 시행착오를 하면서 방침을 찾아갈 수도 있지만, Q 함수에 현재 상태와 하고싶은 일인 action을 정해 물어보고 현재 상태에서 그 일이 얼마나 유익한지를 받아 학습할 수 있다.
  • 物理エンジンくん 채널에 이런 영상들이 많다.

평생학습

  • life-long learning 또는 continual learning 으로 불린다.
  • 현재의 뉴럿넷은 새로운 업무를 하면 이전 학습은 완전히 잊어버린다.
  • 뇌는 연결이 만들어질 때 수신 뉴런에서 가지돌기 가시가 나온 후 발신 뉴런의 축산 말단에 닿아 새로운 연결을 만들어 기존 돌기의 변동성을 떨어뜨려 변하지 않게 함으로써 기존 경로를 보호한다.

EWC

  • Elastic Weight Conditioning
  • 이전 학습에서 중요한 연결은 고정해 놓고 다른 연결만을 이용해 학습하는 것
  • 고정된 숫자의 뉴런으로 무한한 일을 해내는 것은 불가능하므로 사람처럼 새로운 일을 배울 때마다 새로운 인공 뉴런을 추가해 효과적으로 처리하는 방법인 Progressive Neural Network가 고안되었다.

인지

의미

  • 기계가 사물을 인식하는 방법은 숫자로 나타내는 것이다.
  • 사물을 숫자로 나타내는 것을 벡터화 또는 인코딩, 변한된 결과를 벡터 또는 임베딩이라 한다.
  • One-hot representation: 1, 0으로 사물을 나타낸다.
    • [1, 0, 0], [0, 1, 0] 처럼 항목별로 나누기에 비효율적
  • Distributed representation: 여러 개의 요소들의 숫자를 통해 사물을 나타낸다.
    • [1, 1, 0], [0.5, 0.5, 0] 처럼 여러 요소를 저장
  • 단어를 숫자로 잘 표현된다면 사물들의 벡터에서 단어 사이의 거리는 동일해야한다.
    • MAN - WOMAN === UNCLE - AUNT
    • KING - KINGS === QUEEN - QUEENS
  • 이를 단어에서 해결하기 위해 아래 방법들이 나왔고, 이는 문장에서 자주 같이 나오는 단어들끼리 비슷하게 표현하는 방법이다.
    • Skip-gram
    • Cbow
    • Glove
  • 유사한 단어는 가깝게 모이고 다른 단어들은 멀리 떨어지며 단어 벡터에 의미가 담기게 된다.

시각

  • 망막에서 이미지가 들어오면 막대세포에서 빛, 원뿔세포에서 색 정보를 따로 받아 시신경을 통해 뇌로 전달한다.
  • LGN -> V1 -> V2 -> V3 -> LOC 순으로 통과하면서 물리적, 인지적 한계를 넘는 처리를 한다.
    • 각막의 혈관 제거
    • 수정체에서 180° 뒤집혀서 들어오는 상을 다시 처리
    • 황반의 몰려있는 원뿔세포로 인해 생기는 맹점 처리
    • 원근 처리
  • LGN 에서는 공간 및 대조, V1, V2 에서는 물체 윤곽, 색, 움직임, 마지막엔 물체 간의 그룹화, 연관, 시각에 대한 의미적 내용을 통합한다.
  • 시각은 한 번에 완성되는 것이 아니라 빛, 색, 윤곽, 깊이감 등이 점차 통합 되어 완성된다.

CNN

  • Convolutional Neural Network
  • 98년에 발표된 Lenet-5 가 그 기원이다.
    • 두 개의 convolution layer: 이미지의 다양한 특징 추출
    • 두 개의 multi-layer perception, fully connected layer: 시각 정보 통합을 통해 정보 추상화와 의미적 정보 도출
    • 하나의 결과 출력 레이어: 추상화되고 의미적인 정보를 이용해 사물 결정
  • 머핀과 치와와를 구별하기 힘들어하지만 근래에는 1,000개가 넘는 레이어를 구성해 극복하려하고 있다.

청각

  • 소리는 매질의 진동이며 소리에서 가장 필요한 정보는 무엇어디이다.
  • 진동이 고막에서 물리적인 진동으로 변하고, 귓속뼈를 통해 증폭돼 달팽이관으로 전달된다.
  • 전달된 진동은 달팽이관 내부 유모세포를 흔들어 전기신호를 발생시키고 이 신호가 청각 신경을 통해 뇌로 전달돼 소리를 듣는다.
  • 달팽이관의 초입은 고주파를 받고 중심부에서는 저주파를 받는다. 주파수별로 처리함으로 모든 청신경이 켜지지 않고 효율적인 정보처리가 가능하다.
  • 대역별로 받은 신호는 청각 신경을 통해 뇌의 1차 청각 피질에서 대역별로 처리한다.
  • 청각피질도 주파수별로 구획화되어있다.
  • '무엇’에 대한 정보는 측두엽에 위치한 베르니케 영역과 브로카 영역을 순차적으로 통과하면서 음소 모델과 언어 모델을 이용해 이해한다.
  • '어디’에 대한 정보는 두정엽에서 귀에 도착한 타이밍의 차이, 주파수에 따른 소리 크기 차이, 도플러 효과 등과 같은 방법을 통해 방향과 거리를 알아낸다.
  • 이 두 정보는 시각적 인지에 의존하므로 시각과 청각을 통합해 사용하는 멀티모달 연구가 진행 중이다.
  • 고주파보다 저주파 대역의 변화에 민감해 저주파 대역에서 많은 정보를 뽑고 고주파 대역에서 적게 뽑으면 인지적으로 적합한 정보를 추출할 수 있는데, 이는 멜 주파수 필터로 가능하다.
  • 무엇: Speech Recognition, Sound Classification
  • 어디: Sound Localization

기억

기억은 뉴런들의 연결 강화 또는 약화이다.

내포적 기억

  • Implicit Memory
  • 어떻게, 몸으로 익힌 기억
  • 기저핵과 소뇌에서 주로 담당
  • 절차 기억
    • Procedural Memory
    • 몸이 하는 기억
  • 예비화 기억
    • Priming Memory
    • 과거의 경험에 의해 특정 자극이 들어왔을 때 기억의 민감도가 증가하는 기억

명시적 기억

  • Explicit Memory
  • 구체적으로 설명 가능한 기억
  • 고등적인 일 처리인 대화, 언어, 시각적 정보, 청각적 정보 등을 처리하는데에 필수
  • 대뇌피질에 저장되며 특히 전두엽이 많이 사용된다.
  • 일화 기억
    • Episodic Memory
    • 인상적인 날을 기억한다.
  • 의미 기억
    • Semantic Memory
    • 객관적인 지식에 대한 기억

단기 기억

  • RAM
  • 작업 기억, Working Memory
  • 단순한 전기 신호로 저장

RNN

  • Recurrent Neural Network
  • 단기기억을 모방
  • 뉴럴넷이 연속으로 연결돼 있고 매 뉴럴넷마다 입력을 받는 구조
  • 1, 2, ⋯, t-1, t, t+1 의 순차 구조에서 t-1 에서 처리된 결과와 t 에서의 입력이 함께 새로운 결과를 만들어 주는 모델
  • 하나의 셀을 지날 때마다 모든 정보를 누적하므로 최근 이전 다섯 셀 이내의 정보만을 잘 기억해낸다.

LSTM

  • Long Short Term Memory
  • RNN 의 짧고 비효율적인 단기 기억을 해결하기 위해 제안
  • 정보의 컨베이어 벨트가 있고 매번 필요한 정보를 넣고 필요 없는 정보를 빼서 효율적으로 정보를 관리한다.

장기 기억

  • HDD
  • 물리적인 연결을 통해 저장
  • 정보에 따른 고유 경로가 활성화되면서 머리 속에서 떠오른다.
  • 전전두엽의 단기기억이 주로 수면 중에 해마와 편도체에서 내부 반복해 대뇌피질에 기억 관련 연결을 굳혀놓는다.

DNC

  • Differentiable Neural Computer
  • deepmind/dnc
  • 정보를 외장 메모리에 차곡차곡 저장하고 필요할 때 불러 쓰는 방식
  • Q&A에 주로 사용한다.
  • Neural Turing Machine, Memory Network 등도 비슷한 방식이다.

효율적 기억

  • 사람은 매초 4,000억 bits 즉 50GB의 정보를 쏟아받지만 필요한 정보만 선택해 실제로 2,000 bits만 사용한다.
  • 이는 부주의맹(Inattention Blindness)라 하며 우리가 정보를 2억분의 1로 필터링하는 비결이다.
  • 기계도 필요한 정보에 집중하는 Attention을 사용한다. 이 개념은 이미지 캡셔닝, 번역기에 활용된다.
    • 원문에서 문맥적 의미가 비슷한 부분에 집중해 이 정보를 중점적으로 확인한다.

Next

  • 직관과 추론: 감정
  • 심리, 도덕: 트롤리 문제
  • 원샷 학습: 한 번의 예시만으로 학습
  • 메타 학습
    • 학습법의 학습
    • MANN, Matching Network, MAML
  • 전이 학습: 비슷한 것을 배운 지식을 새로운 것을 배울 때 활용해 빠르게 학습

용어

  • 뉴럴넷: Neural Network
  • 베이지안 네트워크: Bayesian Network
  • SVM: Support Vector Machine
  • 오버피팅: overfitting, 과적합
  • MFCCs: Mel Frequency Cepstral Coefficients
  • 스펙트로그램: 주파수를 대역별로 시각화한 것
  • Memory consolidation: 기억의 응고, 장기기억화

활성 객체

  • Activation Object
  • 함수가 호출되면 활성 객체가 만들어진다. 이는 숨겨진 데이터 구조로 호출된 함수의 반환 주소와 실행에 필요한 정보를 저장하고 호출된 함수에 바인딩한다.
  • C와 같은 언어에서는 활성 객체가 스택에 저장되고 함수가 종료되고 반환되면 스택에서 제거하지만 자바스크립트에서는 활성 객체를 다른 객체와 동일하게 힙에 저장한다.
  • 함수가 종료된다고 활성 객체를 자동으로 비활성화하지 않는다.
  • 활성 객체는 해당 객체에 대한 참조가 있는 한 계속 살아있으며 다른 객체와 마찬가지로 가비지 컬렉터에 의해 처리된다.

활성 객체 내부 정보

  • 함수 객체에 대한 참조
  • caller 함수의 활성 객체에 대한 참조 (이 참조는 return 이 실행흐름을 caller 로 돌릴 때 사용된다.)
  • 함수 호출이 끝난 뒤 실행을 재개하기 위해 필요한 정보 (대개 함수 호출문 바로 다음 명령어의 주소)
  • 파라미터에 의해 초기화되는 함수 매개변수
  • undefined로 초기화된 함수 변수들
  • 함수가 복잡한 표현식을 계산하기 위해 임시로 사용하는 변수들
  • 함수 객체가 메소드로서 호출되었을 때 사용할 수 있는 this 참조

함수

함수 객체

  • 함수 객체는 다른 일반적인 객체와 마찬가지로 속성을 갖고 있으며 변경 가능하다.
  • 공유되는 변경 가능한 함수 객체가 취약점으로 사용될 수 있다. (프로토타입 오염 등)
  • 함수 객체는 prototype 이란 속성을 가지고 있고, prototype은 Object.prototype에 대한 delegation link와 constructor 속성을 가진 객체의 참조를 가진다.
    • constructor 속성은 함수 객체에 대한 역참조를 가지고 있다.
  • 함수 객체는 Function.prototype 에 대한 델리게이션 링크를 가지고 있어 applycall을 상속받는다.
  • 함수 객체에는 두 가지 숨겨진 속성이 있는데 다음과 같다.
    • 함수 실행 코드에 대한 참조
    • 함수 객체가 생성되는 시점에 활성화된 활성 객체에 대한 참조 클로져를 사용할 수 있게 해준다.

함수 호출

  • 파라미터 표현식 계산
  • 함수의 매개변수와 변수를 저장할 수 있는 충분한 크기의 활성 객체 생성
  • 호출된 함수 객체에 대한 참조를 새로운 활성 객체에 저장
  • 전달받은 파라미터를 새로운 활성 객체의 매개변수에 저장
    • 빠진 파라미터는 undefined로 간주, 남는 파라미터는 버림
  • 활성 객체의 모든 변수 값을 undefined로 할당
  • 함수 호출 명령어의 바로 다음 명령어를 활성 객체의 next instruction 필드 값으로 할당
  • 새로운 활성 객체의 caller 필드 값에 현재 활성 객체를 할당
    • 실제 호출 스택이 아닌 활성 객체의 연결된 목록이다.
  • 새로운 활성 객체를 현재 활성 객체로 지정
  • 호출된 함수 실행

꼬리 재귀

  • 파라미터 표현식 계산
  • 현재 활성 객체가 충분히 크다면 (일반적으로 충분히 큼)
    • 현재 활성 객체를 새로운 활성 객체로 사용
  • 아닌 경우
    • 함수 매개변수와 변수를 저장할 수 있는 충분한 크기의 활성 객체 생성
    • 새로운 활성 객체의 caller 필드 값에 현재 활성 객체 저장
    • 새로운 활성 객체를 현재 활성 객체로 지정
  • 전달받은 파라미터를 새로운 활성 객체의 매개변수에 저장
    • 빠진 파라미터는 undefined로 간주, 남는 파라미터는 버림
  • 활성 객체의 모든 변수 값을 undefined로 지정
  • 호출된 함수를 실행

활성 객체를 재사용하므로 재귀 함수 호출을 반복문만큼 빠르게 처리 가능하다.
try 블록의 꼬리 호출은 최적화되지 않는다.

  • try는 실행 흐름을 caller의 catch문으로 변경해야하는데, 이러면 활성 객체가 최적화 될 수 없음
  • 내부에서 새로운 변수를 가지고 있다면 활성 객체가 최적화 될 수 없음

꼬리 호출 예시

1
2
3
4
5
6
; recursion
call func
return

; tail recursion
jump func
1
2
3
4
5
6
7
8
9
(function loop() {
// do some
if (done) {
return;
}

// do more
return loop();
}());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ❌
function factorial(n) {
if (n < 2) {
return 1;
}

return n * factorial(n - 1);
}

// ❌
const value = func();
return value;

// ❌
return function () {};

// ✔️
function factorial(n, result = 1) {
if (n < 2) {
return result;
}

return factorial(n - 1, n * result);
}
// ✔️
return (function () {}());

문자열

0~65535 사이의 크기를 가지는 Immutable unsigned int16 배열이다.

유니코드

  • 모든 언어를 16bit 로 표시하는 것이 목적이였으나 후에 21bit로 바뀌었다.
  • 자바스크립트는 16비트로 표현하려던 시절에 설계되어서 간극이 있다.
  • 자바스크립트는 문자를 받아 코드 유닛코드 포인트 둘로 나눈다.
  • 유니코드는 1,114,112개의 코드 포인트를 정의하고 있으며 한 평면당 65,536 코드포인트, 총 17개의 평면으로 나뉜다.
  • 원래의 평면은 BMP (Basic Multilingual Plane)라고 불리우며 나머지 16개의 평면은 나중에 추가되었다.
  • BMP에 있는 코드 포인트는 하나의 코드 유닛으로 식별가능하므로 사용이 쉽지만 그 외의 문자는 써로게이트 페어를 사용해야한다.

써로게이트 페어

  • 써로게이트 페어는 두 개의 코드 유닛으로 구성되는데, 자바스크립트에서는 1024개의 상위 써로게이트 코드유닛과 1024개의 하위 써로게이트 코드유닛이 있다.
    • 상위 써로게이트 코드유닛: 0xD800 ~ 0xDBFF
    • 하위 써로게이트 코드유닛: 0xDC00 ~ 0xDFFF
  • 계산식은 SurrogatePair = ((High - 0xD800) * 0x400) + (Low - 0xDC00) + 0x10000;이다.
  • 따라서 😃는 0x1F603 이며 D83D + DE03 이다.
    • "\uD83D\uDE03" === "\u{1F603}"
    • String.fromCharCode(55357, 56835) === String.fromCodePoint(128515);
  • 비트 연산자로도 계산이 가능한데, 코드 포인트에서 0x10000 (65,536)을 빼고 상위 10bit에 0xD800 을 더한 값과 하위 10bit에 0xDC00을 더하면 된다.
  • 비트 연산의 계산식은 다음과 같다.
1
2
3
4
SurrogatePair = SurrogatePair - 0x10000;
High = 0xD800 + (SurrogatePair >> 10);
Low = 0xDC00 + (SurrogatePair & 0x3FF);
return String.fromCharCode(High, Low);

전세계 사람들과 대화하기 위해 필요한 문자 대부분을 BMP에서 찾을 수 있지만, 유니코드 프로그램을 만들 경우에는 써로게이트 페어를 항상 염두해야한다. Unicode-aware 로 적혀지는 듯 하다.

정규화

  • 유니코드 문자에서는 악센트나 기타 문자를 수정할 수 있는 조합 및 수정 문자, 쓰기 방향 제어 문자 등이 포함되어있다.
  • 동일한 문자처럼 보이더라도 실제로는 다를 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 예시 1
"S\u0307" === "Ṡ"
"S\u0307\u0323" === "Ṩ"

let s1 = "S\u0307\u0323"; // Ṩ, S + 윗 점 + 아랫 점
let s2 = "S\u0323\u0307"; // Ṩ, S + 아랫 점 + 윗 점

s1 === s2; // false
s1.normalize() === s2.normalize(); // true

// 예시 2
let umlaut = "\u00FC"; // ü
let u_diaeresis = "u\u0308"; // u + 분음부호 ü

umlaut === u_diaeresis; // false
umlaut.normalize() === u_diaeresis.normalize(); // true

항상 WeakMap, WeakSet을 어떤 방식으로 쓸까 많이 고민되었다.
참조가 없는 경우 메모리를 반환하는데에 이점이 있는데, 여러 활용방안이 있었다.

WeakMap

Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 📁 cache.js
let cache = new WeakMap();

// calculate and remember the result
function process(obj) {
if (!cache.has(obj)) {
let result = /* calculate the result for */ obj;

cache.set(obj, result);
}

return cache.get(obj);
}

// 📁 main.js
let obj = {
/* some object */
};

let result1 = process(obj);
let result2 = process(obj);

// ...later, when the object is not needed any more:
obj = null;

Sealer

값을 봉인하고 box를 반환해 box object 전체가 와야만 내부값을 알 수 있게 해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function sealerFactory() {
const weakMap = new WeakMap();
return {
sealer(obj) {
const box = Object.freeze(Object.create(null));
weakMap.set(box, object);
return box;
},

unsealer(box) {
return weakMap.get(box);
},
};
}

WeakSet

Circular references

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Execute a callback on everything stored inside an object
function execRecursively(fn, subject, refs = null) {
if (!refs) {
refs = new WeakSet();
}

// Avoid infinite recursion
if (refs.has(subject)) {
return;
}

fn(subject);
if (typeof subject === 'object') {
refs.add(subject);
for (let key in subject) {
execRecursively(fn, subject[key], refs);
}
}
}

const foo = {
foo: 'Foo',
bar: {
bar: 'Bar',
},
};

foo.bar.baz = foo; // Circular reference!
execRecursively((obj) => console.log(obj), foo);

참조

  • MDN WeakSet
  • The Modern JavaScript Tutorial - WeakMap
  • How JavaScript Works

qs 모듈과 querystring 모듈 비교

대부분의 포스트에서는 qs.stringifyqs.parse 는 확장된 쿼리스트링 변환이 가능하다고 나온다.
기본 모듈인 querystring.stringify, querystring.parse 대비 쿼리스트링의 중첩을 가능하게한 모듈인데, 다른 변경점이 하나 더 있다.

rfc3986

rfc3986!, ', (, ), * 문자에 대해 추가적으로 엔티티화 한다.
자바스크립트에서 이 스펙을 준수하려면 다음과 같이 encodeURIComponent 함수를 합성해야한다.
노드에서도 이는 마찬가지이며 querystring 모듈은 이 스펙을 준수하지 않았다.

1
2
3
4
5
function fixedEncodeURIComponent(str) {
return encodeURIComponent(str).replace(/[!'()*]/g, function (c) {
return '%' + c.charCodeAt(0).toString(16);
});
}

하지만 qs 모듈은 이 스펙을 기본으로 준수한다.

RFC3986 used as default option and encodes ’ ’ to %20 which is backward compatible.
In the same time, output can be stringified as per RFC1738 with ’ ’ equal to ‘+’.

따라서 언어 간 호환성 및 표준을 맞추기 위해는 qs 모듈을 사용하는 것이 마음이 편하다.

참조

  • ljharb/qs
  • MDN encodeURIComponent
  • Node.js querystring

Production ready nodejs dockerfile

구글링으로 때워버린 nodejs 이미지라면 취약점, 실행 권한, 파드 배치에 있어 치명적이다.
그렇다면 실제환경에서 사용할 수 있을만한 이미지는 무엇일까?

린팅

  • hadolint 를 사용하자.

취약점 분석

  • clair 를 사용하자.

소스

  • 모든 조건을 만족시킨 이미지는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 컨테이너를 위해 만들어진 alpine 이미지를 사용한다.
# 외부 레파지토리에 의존적인 모듈에 대해 latest 버전은 사용하지 않아야한다.
FROM node:13.14.0-alpine
# 최신버전 사용시
# FROM node:14.4.0-alpine3.12

# tini 는 nodejs 파드를 PID 1 로 실행시켜 정상적인 종료를 가능하게 한다.
RUN apk add --no-cache tini=0.18.0-r0 \
# alpine:3.12 버전 사용시
# apk add --no-cache tini=0.19.0-r0 \
&& mkdir -p YOUR_APP_PATH \
&& chown node:node -R YOUR_APP_PATH

WORKDIR YOUR_APP_PATH
# 패키지 의존성을 먼저 설치한다.
COPY --chown=node:node package*.json ./

USER node
# node 권한으로 설치한다.
RUN npm install && npm cache clean --force

# 앱 소스를 복사한다.
COPY --chown=node:node . .
# 앱 빌드와 후처리 쉘에 권한을 준다.
RUN npm run build \
&& chmod a+x "bin/entrypoint.sh"

EXPOSE 3000
ENTRYPOINT [ "/sbin/tini", "--", "./bin/entrypoint.sh" ]

여담

  • 빌드 환경과 실행 환경을 분리시켜 실제 환경에서는 src 폴더 없이 실행시킬 수 있다.
  • 아예 단일실행파일(vercel/pkg) 로 만들 수도 있다.
  • deno 로 더 간단한 dockerfile 을 만들 수 있을 것 같다.

참조

  • hadolint/hadolint
  • quay/clair

One true layout

Header, Navigation, Aside, Section, Footer 로 이루어진 레이아웃을 만들 시에
float 을 사용해서 구성할 경우 틀어짐을 잡는 방법에 대한 내용이다.

원문으로 보이는 링크에서는 진정한 하나의 레이아웃을 찾는 과정 중 하나이며 Eqaul Height Columns - revisited로 소개되고 있다.

이슈

아래와 같은 레이아웃에는 footer 영역이 섹션에 붙어 올라온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<style>
body {
width: 1000px;
margin: 0 auto;
}
#aside {
float: left;
width: 200px;
}
#section {
float: left;
width: 800px;
}
</style>
<body>
<div id="header">header</div>
<div id="nav">nav</div>
<div id="wrap">
<div id="aside">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sed
sollicitudin mauris. Aliquam faucibus facilisis vulputate. Curabitur
condimentum placerat mattis. Duis at metus at tellus volutpat ultrices.
Cras lorem eros, cursus et risus sit amet, gravida feugiat libero.
Nullam id faucibus ipsum. Nulla a leo sed eros mattis bibendum. Nullam
et sapien in orci tempus elementum eu sed augue. Pellentesque eu
vestibulum arcu. Pellentesque vel finibus libero. Nulla facilisi.
Quisque dolor enim, ornare eget elit ac, pharetra porta ex. Vivamus
eleifend eu arcu nec consequat.
</p>
</div>
<div id="section">
<p>
Nam pulvinar dictum nibh id ullamcorper. Suspendisse justo eros, tempor
vel faucibus in, pellentesque congue enim. Proin non eleifend turpis,
vel commodo purus. Fusce vitae nisl dapibus, tincidunt elit at, cursus
lacus. Maecenas varius imperdiet sollicitudin. Nunc pharetra fringilla
enim ut facilisis. Curabitur maximus nibh non rhoncus semper. Duis
porta, purus ut tincidunt convallis, sem purus pharetra erat, eu
vestibulum tellus mi id eros. Fusce congue, erat at blandit mollis,
tellus ex semper velit, dapibus commodo ante turpis a neque. Fusce vel
ex id sem auctor accumsan. Maecenas finibus nunc sem, ut gravida felis
efficitur at. Pellentesque lobortis dui non ligula condimentum, at
auctor dui blandit.
</p>
</div>
</div>
<div id="footer">footer</div>
</body>

해결방안

overflow: hidden

래퍼에 이 속성을 넣는 것으로 해결 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
body {
width: 1000px;
margin: 0 auto;
}
#aside {
float: left;
width: 200px;
}
#section {
float: left;
width: 800px;
}
#wrap {
overflow: hidden;
}

clear: both

또는 래퍼를 삭제하고 구획을 나누는 부분에 sibling 노드로 clear: both 속성을 주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<style>
body {
width: 1000px;
margin: 0 auto;
}

.clear {
clear: both;
}

#aside {
float: left;
width: 200px;
}
#section {
float: left;
width: 800px;
}
</style>
<body>
<div id="header">header</div>
<div id="nav">nav</div>
<div id="aside">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sed
sollicitudin mauris. Aliquam faucibus facilisis vulputate. Curabitur
condimentum placerat mattis. Duis at metus at tellus volutpat ultrices.
Cras lorem eros, cursus et risus sit amet, gravida feugiat libero. Nullam
id faucibus ipsum. Nulla a leo sed eros mattis bibendum. Nullam et sapien
in orci tempus elementum eu sed augue. Pellentesque eu vestibulum arcu.
Pellentesque vel finibus libero. Nulla facilisi. Quisque dolor enim,
ornare eget elit ac, pharetra porta ex. Vivamus eleifend eu arcu nec
consequat.
</p>
</div>
<div id="section">
<p>
Nam pulvinar dictum nibh id ullamcorper. Suspendisse justo eros, tempor
vel faucibus in, pellentesque congue enim. Proin non eleifend turpis, vel
commodo purus. Fusce vitae nisl dapibus, tincidunt elit at, cursus lacus.
Maecenas varius imperdiet sollicitudin. Nunc pharetra fringilla enim ut
facilisis. Curabitur maximus nibh non rhoncus semper. Duis porta, purus ut
tincidunt convallis, sem purus pharetra erat, eu vestibulum tellus mi id
eros. Fusce congue, erat at blandit mollis, tellus ex semper velit,
dapibus commodo ante turpis a neque. Fusce vel ex id sem auctor accumsan.
Maecenas finibus nunc sem, ut gravida felis efficitur at. Pellentesque
lobortis dui non ligula condimentum, at auctor dui blandit.
</p>
</div>
<!-- clear:both 노드 추가 -->
<div class="clear"></div>

<div id="footer">footer</div>
</body>

CSS3

사용할 수 없는 브라우저에서 신규 문법은 무시된다.

BFC

Block Formatting Context이며 아래 조건에서 생성된다.

  • 루트 요소
  • float: right, left
  • position: absolute
  • display: inline-block
  • overflow 값이 visable 외에 다른 값일 때
  • flex item
  • grid item
  • table cell

layout

float

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<style>
.box {
float: left;
}
/* 다음 세 가지 방법으로 container 안에 box 를 넣어줄 수 있다. */
.container {
overflow: hidden;
}
.container {
float: left;
}
.container {
display: flow-root;
}
</style>
<div class="container">
<div class="box">
<p>플로팅</p>
</div>
</div>
  • overflow: hidden 으로 플로팅 요소를 잡는 것은 box-shadow가 잘리는 등의 문제가 있다.
  • display: flow-root는 모던 브라우저에서만 지원한다.

position

  • static: 기본 값이다. 코드상 노출된 순서대로 표시된다.
  • relative: 오프셋(top, left…)와 함께 사용된다.
    • 새로운 컨테이너 블록이 되며 하위 absolute 를 가둘 수 있다 .
  • absolute: 흐름에서 벗어나며 자신이 포함된 컨테이너 블록의 가장자리를 기준으로 오프셋만큼 이동한다.
    • 별도의 컨테이너 블록이 선언되지 않았을 경우, viewport가 된다.
  • fixed: viewport를 기준으로 고정된다. 스크롤해도 변하지 않는다.
  • sticky: static + fixed 로 문서와 함께 스크롤 되다가 설정한 위치가 되면 고정된다.
    • 모던 브라우저에서만 지원한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style>
.container {
width: 400px;
height: 400px;
}
/* 이 박스는 맨 위(viewport)에서 10 10 씩 떨어져있다. */
.box {
position: absolute;
top: 10px;
right: 100px;
width: 200px;
}

/* 컨테이너 블록으로 만들면 박스가 들어온다. */
/* .container { position: relative; width: 400px; height: 400px; } */
</style>
<div class="container">
<div class="box">
<p>absolute</p>
</div>
</div>

multi-column

column-count, column-width로 단 효과를 낼 수 있다.

1
2
3
4
5
6
7
8
9
10
<style>
.columes {
column-width: 200px;
column-count: 2;
}
</style>
<div class="columns">
<p>첫 번째 단</p>
<p>두 번째 단</p>
</div>

axis

  • 주축교차축이 있다.
    • 기본은 flex-direction: row이며 교차축은 수직이다.
    • flex-direction: column이면 교차축은 수평이다.
  • 아이템의 배치는 항상 교차축(cross axis)에서 이뤄진다.
  • 교차축 === 블럭축(block axis) 이다.
  • 그리드에서는 컬럼 축(column axis)이라고도 한다.

flexbox

1차원 레이아웃

  • display: flex 설정시 자식은 flex item이 된다.
  • flex item은 min-content로 설정한 너비보다 작아질 수 없으므로 컨테이너를 벗어난다.
    • min-content는 아이템 내부 단어 중 가장 긴 것을 기준으로 설정된다.
  • flex-wrap: wrap; 속성 설정 시 여러 줄에 걸쳐 표현된다.
  • 줄이 넘어가면 넘어간 줄이 flex container 가 된다.

플렉스 아이템 배치

  • align-items
    • stretch: 기본값으로 늘어난다.
    • flex-start: 요소가 컨테이너 상단에 붙는다.
    • flex-end: 바닥에 붙는다.
    • center: 중앙에 배치된다.
  • align-self: flex item 에서 위 속성을 덮는다.

플렉스 아이템 정렬

  • justify-content
    • flex-direction: row면 가로줄, column이면 세로줄에서 동작한다.
    • flex-start: 기본값
    • flex-end: 플렉스 컨테이너 끝에서부터 추가된다.
    • space-between: 아이템 사이의 공간을 똑같은 간격으로 설정한다.
    • space-around 모든 아이템 양쪽에 똑같은 간격의 마진을 설정한다.
    • space-evenly 모든 공백을 똑같이 설정한다.
      • 아이템-아이템 간 컨테이너-아이템 간의 간격이 똑같다.
    • center: 가운데 설정한다.
  • align-content
    • 교차축 위에서 동작한다.
    • flex-wrap: wrap이고 아이템 배치 공간보다 컨테이너가 길 때 사용할 수 있다.
    • 초기값은 start이다.
    • 나머지 동작은 justify-content와 같다.
  • margin-left: auto; 를 사용하면 원하는 아이템을 반대방향에 배치할 수 있다.

반응형 플렉스박스

  • flex-grow: flex-basis에 설정한 값보다 커질 수 있는지 설정한다.
  • flex-shrink: flex-basis에 설정한 값보다 작아질 수 있는지 설정한다.
    • 500px 컨테이너에 200px 플렉스 아이템이 3개 있다면 활성화시 영역 안에 들어올 것이다.
  • flex-basis: flex-direction 에 따라 너비나 높이의 기본값을 지정한다.
    • flex-basis: content: 주축의 컨텐츠 크기로 설정된다.
    • flex-basis: auto: 플렉스 아이템에 width 속성이 있다면 그 값을 flex-basis 로 사용한다. 특별한 경우가 아니라면 auto가 권장된다.
  • 보통 세 속성을 합쳐서 flex: 0 0 200px; 처럼 적는다.

플렉스박스 방향

플렉스박스와 그리드는 dir=ltr dir=rtl 속성에 좌,우 영향을 받는다.

  • flex-direction: row-reverse
  • flex-direction: column-reverse

grid

2차원 레이아웃

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<style>
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
/* grid-column-gap, grid-row-gap 축약 */
grid-gap: 20px;
/* 가장 최근 명세에서는 `grid-` prefix 가 빠졌다 */
gap: 20px;
}
</style>
<div class="container">
<div class="item">
<h2>grid 1</h2>
</div>
<div class="item">
<h2>grid 2</h2>
</div>
<div class="item">
<h2>grid 3</h2>
</div>
<div class="item">
<h2>grid 4</h2>
</div>
<div class="item">
<h2>grid 5</h2>
</div>
<div class="item">
<h2>grid 6</h2>
</div>
</div>

fr

  • fraction
  • 유연한 너비를 나타내는 단위이다.

그리드 트랙

  • 그리드의 열과 행을 나타낸다.

그리드 배치

프로그래머의 수는 0부터 시작이지만 그리드 배치에서는 1부터여야한다.

  • LTR 의 경우는 왼쪽 끝이 1 이다.
  • RTL 의 경우는 오른쪽 끝이 1 이다.
  • 반대편 끝은 -1 이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
}

.item1 {
grid-column: 1 / 3;
grid-row: 1;
}
.item2 {
grid-column: 3;
grid-row: 1;
}
.item3 {
grid-column: 1;
grid-row: 2 / 4;
}
.item4 {
grid-column: 2 / 4;
grid-row: 2;
}
.item5 {
grid-column: 2 / 4;
grid-row: 3;
}

grid-column: auto / span 2; 처럼 시작 위치를 auto로 잡고 끝 위치를 span 2로 잡으면 자동 배치 기능에 의해 그리드 아이템 위치는 자동으로 정해지고 너비는 항상 컬럼 두 개만큼 확장한다.

그리드 정렬

  • justify-items로 설정하며 각 영역 안에서 정렬된다.
  • 초기값은 stretch이다.

named area

  • 그리드에 이름을 직접 지정할 수도 있다.
  • .은 공백을 나타낸다.
  • 영역은 반드시 사각형이여야한다.
  • align-items, justify-items 속성 변경시 반복이 무시된다. (stretch가 아니므로)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
grid-template-areas:
'a a b'
'. d d'
'c e e';
}

.item1 {
grid-area: a;
}
.item2 {
grid-area: b;
}
.item3 {
grid-area: c;
}
.item4 {
grid-area: d;
}
.item5 {
grid-area: e;
}

반응형 그리드

  • auto-fill: 너비가 허용하는만큼 최대한 많이, 다만 아이템 갯수가 부족하면 빈 공간을 남긴다.
  • auto-fit: 아이템 갯수가 부족하면 남은 공간은 균등하게 분배된다.
  • minmax: 너비의 최소, 최대크기를 지정할 수 있다.
    • 아래 예시라면 200px 의 컬럼이 몇 개 들어가는지 계산한 뒤, 남은 공간을 컬럼에 균등하게 분배된다.
1
2
3
4
5
6
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
/* 컨텐츠에 따라 그리드가 길어진다. */
grid-auto-rows: minmax(150px, auto);
}

그리드 흐름

  • grid-auto-flow 속성으로 조절한다.
    • row: 기본값이며 왼쪽 위부터 오른쪽으로 칸을 채워나간다.
    • column: 줄을 채우고 다음 컬럼을 채운다.
    • dense: 빈 공간에 채워넣는다.
    • sparse: 기본값이며 빈 공간을 내버려둔다.

이 외에 order 속성으로 직접 순서 제어가 가능하다.
다만 denseorder는 탭 순서까지 변경해주지 않아 접근성을 벗어난다.

supports

구 브라우져와의 호환을 위해 @supports (display: grid) 처럼 서포트 피쳐쿼리를 사용할 수 있다.

arg

zeit/arg 패키지를 이용하면 된다.

사용법

arg 함수 하나로 파싱이 가능하다.

1
2
3
4
5
6
7
const arg = require('arg');

// `options` is an optional parameter
const args = arg(
spec,
(options = { permissive: false, argv: process.argv.slice(2) })
);

세부적인 사용방법은 다음과 같다.

  1. 타입을 정하고
  2. 옵션과 축약 옵션을 정하고
  3. 검증을 넣는다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 계정정보를 받는 스크립트라면
const help = () => {
console.log(`usage => ...`);
};

let args = {};
try {
args = arg({
'--help': Boolean,
'--user': String,
'--password': String,
'--verbose': arg.COUNT,
'--test': Boolean,

'-h': '--help',
'-u': '--user',
'-p': '--password',
'-v': '--verbose',
});
} catch (err) {
if (err.code === 'ARG_UNKNOWN_OPTION') {
help();
process.exit(1);
}
}

if (!(args['--user'] && args['--password'])) {
help();
process.exit(1);
}

if (args['--test'] === true) {
process.env.TEST = 1;
}

Workbox5 버전

잘 돌던 workbox-cli 가 5버전 릴리즈 후부터 돌지 않아서 확인해보았다.
전체 문서는 여기서 확인 가능하다.

injectManifest

self.__WB_MANIFEST 를 주입받는 방식으로 변경되었다.

1
2
3
4
5
// v4:
precacheAndRoute([]);

// v5:
precacheAndRoute(self.__WB_MANIFEST);

blacklist, whitelist 에서 denylist, allowlist 로 키 명이 변경되었다.

BroadcastChannel

broadcast-update 가 자체 API 에서 postMessage()로 변경되었다.
이벤트 리스너가 많아져 복잡해지고, 기존 API에서는 메세징의 버퍼 기능이 없었기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// v4:
const updatesChannel = new BroadcastChannel('api-updates');
updatesChannel.addEventListener('message', (event) => {
const {cacheName, updatedUrl} = event.data.payload;
// ... your code here ...
});

// v5:
// This listener should be added as early as possible in your page's lifespan
// to ensure that messages are properly buffered.
navigator.serviceWorker.addEventListener('message', (event) => {
// Optional: ensure the message came from workbox-broadcast-update
if (event.meta === 'workbox-broadcast-update') {
const {cacheName, updatedUrl} = event.data.payload;
// ... your code here ...
}
});