프론트엔드에서도 입력자의 값을 검증하거나 API를 통해 가져온 값을 분석 또는 재구성해서 사용하고 싶을 때 정규표현식을 이용할 수 있다.

정규표현식(Regular Expression)이란?

특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어이다.
프로그래밍을 하는 과정에서 문자열을 검증하거나 문자열 일부를 추출하고 싶을 때 많이 사용한다.

Javascript에서 정규식 사용하기

일단 Javascript에서는 정규식 리터럴이나 RegExp 객체를 이용해 정규표현식을 선언할 수 있다.
생성된 정규식은 문자열이 아닌 정규식 객체이다.

const regexp1 = /a\d{1,}/i;
const regexp2 = new RegExp(/a\d{1,}/, 'i');
const regexp3 = new RegExp('a\\d{1,}', 'i');
// 'a' 또는 'A' 뒤에 숫자가 1개 이상 나열 되는지 체크

위 3가지 모두 같은 정규식을 의미한다.
하드코딩이 가능한 부분은 1번의 방식으로 사용자 입력에 따르거나 API통신을 통해 정규식을 생성해야 하는 경우는 3번의 경우를 주로 쓰게 된다.
이미 생성된 정규식에서 플래그만 다른 새 정규식이 필요한 경우 2번 같이 사용 가능

위 정규식을 하나씩 해석하면

  1. / a \d{1,}/i : 문자 'a'
  2. /a \d {1,}/i : 모든 숫자
  3. /a\d {1,} /i : {n, m} n개 이상 m개 미만 -> 1개 이상
  4. /a\d{1,}/ i : 대소문자 구분 X -> 1번의 문자 'a', 'A'둘 다 가능

==> 'a' 또는 'A' 뒤에 숫자가 1개 이상 나열 되는지 체크

위와 같이 해석이 가능합니다 그럼 자세히 알아보겠습니다.

정규식 구문

Groups and Ranges (그룹과 범위)

정규식에서 일부를 그룹으로 묶을 필요가 있을 때 사용
추후 match 라는 메서드와 주로 사용

 

구문 해석 예시
[abc] a 또는 b 또는 c /[012]/: 0, 1, 2 중 하나
[a-z] a 에서 z 중 하나 /[ㄱ-ㅎㅏ-ㅣ가-힣]/: 한글과 매치
[^a-z] a 에서 z 제외 /[^0-9]/: 숫제 제외
a | b a 또는 b [ab]와 유사
(abc) Capturing group /(abc)/: '012abcd'에서 'abc'를 그룹화하고 캡쳐링 ($1, $2 등으로 사용 가능)
(?<name>abc) Named capturing group /(?<name>abc)/: '012abcd'에서 'abc'를 그룹화하고 name으로 캡쳐링 함
(?:abc) Non-capturing group /(?:abc)/: '012abcd'에서 'abc'를 그룹화하고 캡쳐링 하지 않음
\n (\1, \2, \3 ...) n번 째 캡쳐된 그룹 재사용 /(ab)(c)\2\1/: 'abccabb'에서 'abccab'와 매치됨

Character classes (문자 클래스)

키보드로 입력하기 힘들거나 문자를 하나씩 입력하면 너무 길어질 수 있는 부분을 짧게 대체해서 사용하기 위해서 사용

구문 해석 예시
. 모든 문자
* \n(개행문자) 제외
/.a/: a 앞에 아무문자 존재 (a로 시작 X) 단, /\./이나 /[.]/.과 매치
\d 모든 숫자 /[0-9]/과 동일
\D 모든 숫자 제외 /[^0-9]/과 동일
\w 모든 알파벳, 숫자, 언더바 /[A-Za-z0-9_]/과 동일
\W 모든 알파벳, 숫자, 언더바 제외 /[^A-Za-z0-9_]/과 동일
\s 모든 single white space /[\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/과 동일
\S 모든 single white space 제외 /[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/과 동일
\t
\n \r
tab
line feed
carriage return
 

Assertions (어서션)

문자에 매칭하는 것이 아닌 앞뒤 상황에 따라 매칭, 어서션은 매칭 결과에 포함되지 않는다.

구문 해석 예시
^~ ~로 시작 /^abc/: 'abcd'의 abc와 매칭 '1abc'와 매칭 X
~$ ~로 끝 /xyz$/: 'vwxyz'의 abc와 매칭 'xyz012'와 매칭 X
\b 단어의 경계 매치 /\babc\b/: '0 abc d', 'abc d'의 abc와 매칭 '0abc'와 매칭 X
\B 단어의 경계 제외 /\Babc/: '0abc'의 abc와 매칭, 'a abc'의 'abc'와 매칭 X
x(?=y) 뒤에 y가 있을 경우 /xx(?=y)/: 'xxyz'의 'xx'와 매칭 'xxzy'와 매칭 X
x(?!y) 뒤에 y가 없을 경우 /xx(?!y)/: 'xxxy'의 'xx'와 매칭(앞 2개) 'xxyz'와 매칭 X
(?<=y)x 앞에 y가 있을 경우 /(?<=y)xx/: 'yxx'의 'xx'와 매칭 'vxx'와 매칭 X
(?<!y)x 앞에 y가 없을 경우 /(?<!y)xx/: 'vxx'의 'xx'와 매칭 'zxx'와 매칭 X

Quantifiers (수량자)

앞 문자를 반복해서 사용할지 결정

구문 해석 예시
{n} n번 반복 /abc{2}/: 'abccccccc'의 'abcc'와 매칭
{n,} n번 이상 반복 /abc{2,}/: 'abccccccc'의 'abccccccc'와 매칭
{n,m} n번 이상 m번 미만 반복 /abc{2,4}/: 'abccccccc'의 'abcccc'와 매칭
? 0 또는 1 ({0,1} 과 동일) /x?yz/: 'xxxyz'의 'xyz'와 매칭, 'vyz'의 'yz' 와 매칭
* 0개 이상 ({0,} 과 동일) /x*yz/: 'xxxyz'의 'xxxyz'와 매칭, 'vyz'의 'yz' 와 매칭
+ 1개 이상 ({1,} 과 동일) /x+yz/: 'xxxyz'의 'xxxyz'와 매칭, 'vyz'와 매칭 X

기본적으로 수량자는 탐욕적(Greedy)으로 동작하는데, 이는 매치되는 부분을 찾고도 더 길게 찾을 수 있으면 그걸 찾는 걸 말한다.
이때 ?를 뒤에 붙이면 탐욕적으로 찾는 것을 멈춘다. (x??,x{1,5}?)

Greedy: '<div>test</div>'.match(/<.*>/)[0] => <div>test</div>

Non-greedy: '<div>test</div>'.match(/<.*?>/)[0] => <div>

Flag (플래그)

Flag MDN설명 상세 설명
g 전역 탐색. 첫번째 매치결과 외 다른 모든 결과를 매치
i 대소문자를 구분하지 않음. A나 a를 구분하지않음
d 부분 문자열 일치에 대해 인덱스 생성. match나, exec 결과에 indices가 추가된다.
s 개행 문자가 .과 일치함. 문자 클래스 중 .이 \n도 포함
m 여러 줄에 걸쳐 탐색. 기본적으로 첫줄 외의 문자열은 탐색 X, 다만 이걸 켜면 \n으로 여려줄이 되어도 전부 탐색
u "unicode" 패턴을 유니코드 코드 포인트의 시퀀스로 간주함. \u{unicode}다음같은 방식으로 유니코드를 매치할 수 있다. \u{61} == 'a'
y "접착" 탐색, 대상 문자열의 현재 위치에서 탐색을 시작함. 한번 탐색 후 다음 탐색은 마지막 위치에서 다시 시작 (아래에 자세히 설명)
// y Flag
const regex = /test/y;
const str = 'testtesttest!';
regex.test(str);
// 첫번째 탐색 (testtesttest!) // true
regex.test(str);
// 두번째 탐색 (testtest!) // true
regex.test(str);
// 세번째 탐색 (test!) // true
regex.test(str);
// 네번째 탐색 (!) // false

1번에서는 정규식을 해석하고 작성하는 방법을 적었다면
2번에서는 Javascript에서 정규식을 활용하는 string method 또는 regexp method를 적을 예정이다.

 

해석이 어려운 정규식이 있다면 https://regexr.com/ 와 같은 페이지들을 활용할 수 있다.

 

참고

Javascript의 Class 문법은 기본적으로 Private 메서드나 변수를 제공하지 않는다. _(underscore) 등으로 이름을 시작하는 경우 명시적으로만 프라이빗하게 선언하려고 만들었다는 걸 알릴 수 있다. (다만 사용자가 해당 메서드나 변수에 직접적으로 접근이 가능하다.)

하지만 Javascript의 스코프 범위를 이용해 클로저(Closure)를 만들어 Private 한 메서드와 변수를 가질 수 있는 Class를 생성할 수 있다.

1. Closure(클로저)란?

Closure는 함수가 일급객체 취급받는 언어에서 선언 후 반환하게 되면 스코프가 닫히게 되어 접근할 수 없는데, 함수 스코프 내부에 영향을 줄 수 있는 함수를 반환해 해당 함수를 호출할 때만 스코프에 영향을 줄 수 있는 방식이다.

꼭 함수를 반환할 필요는 없고 이벤트나 비동기 함수의 Callback에 등록하는 등으로 사용 가능하다. (틀렸다면 댓글 남겨주세요.)

자바스크립트의 함수는 일급객체로 함수자체를 변수에 할당하거나 함수의 반환값으로 사용할 수 있는 등 자유로운 사용이 가능하다.

 

Closure의 주 사용 목적에는 여러 가지가 있다.

  1. 캡슐화: 외부에서 직접적인 변수 및 메서드 접근 차단
  2. 모듈화: 외부 변화에 영향 없이 독립적인 환경을 만들어 사용 가능
  3. 전역적이지 않은 변수 관리: 전역적으로 선언된 변수는 실수로 변형되기 매우 쉬움, 실수 방지 가능
  4. ...

위 3가지 목적은 어떻게 보면 다 같은 말이긴 하다.

하지만 제목처럼 Private 한 메서드와 변수 Class를 만든다는 느낌으로 함수를 담은 객체를 return 하는 방식으로 Closure를 구현해 본다.

2. 클로저 만들어보기

const closure_maker = function () {
    let private_variables = '프라이빗 변수';
    let public_variables = '퍼블릭 변수';
    function private_method () {
        console.log(private_variables);
    }
    function public_method () {
        private_method();
        console.log(public_variables);
    }
    return {
        public_variables,
        public_method,
    }
}

// 클로저 생성 
let closure = closure_maker();

// 프라이빗 메서드의 호출
closure.private_method();
// error: private_method is not function

// 퍼블릭 메서드의 호출 
closure.public_method();
// 콘솔 결과
// 프라이빗 변수
// 퍼블릭 변수

// 변수의 변경
closure.private_variables = '새 프라이빗 값';
closure.public_variables = '새 퍼블릭 값';

closure.public_method();
// 콘솔 결과
// 프라이빗 변수
// 퍼블릭 변수

class를 생성하는 느낌으로 함수를 작성하고 원하는 변수에 함수를 선언해 return 된 객체를 담는다.
이때 객체에 담은 method는 외부에서 사용 가능한 형태가 된다.

위의 경우 프라이빗 메서드는 호출 시 에러를 띄우지만 퍼블릭 메서드는 문제없이 사용이 가능하다.

 

문제는 같이 반환해 준 퍼블릭 변수는 새 값을 대입해도 똑같은 결과를 확인할 수 있다.

이는 반환된 객체에 해당 값이 담긴 것뿐이고, 새 값을 대입해도 함수 내부에 선언된 public_variables는 변화가 없다.

해당 부분은 setter, getter를 이용해 다음과 같이 해결할 수 있다.

const closure_maker = function () {
    let private_variables = '프라이빗 변수';
    let public_variables = '퍼블릭 변수';
    function private_method () {
        console.log(private_variables);
    }
    function public_method () {
        private_method();
        console.log(public_variables);
    }
    return {
        set public_variables(value) {
            public_variables = value;
        },
        get public_variables() {
            return public_variables;
        },
        public_method,
    }
}

// 클로저 생성 
let closure = closure_maker();

// 퍼블릭 메서드의 호출 
closure.public_method();
// 콘솔 결과
// 프라이빗 변수
// 퍼블릭 변수

// 변수의 변경
closure.private_variables = '새 프라이빗 값';
closure.public_variables = '새 퍼블릭 값';

closure.public_method();
// 콘솔 결과
// 프라이빗 변수
// 새 퍼블릭 값

setter를 이용해 값을 대입하는 경우 public_variables에 값이 들어가도록, getter를 이용해 public_variables를 반환하도록 처리 가능하다.

자세한 내용은 아래를 참조한다.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get

 

만약 단순히 캡슐화만 필요한 경우 IIFE를 이용해 익명함수로 한 번만 생성해 사용하는 Closure를 만들 수 있다.

3. Class 문법을 이용하기

// 함수형 문법
const closure_maker = function () {
    let private_variables = '프라이빗 변수';
    function private_method () {
        console.log(private_variables);
    }
    
    this.public_variables = '퍼블릭 변수';
    this.public_method = () => {
        private_method();
        console.log(this.public_variables);
    }
}
// 클래스 문법
class closure_maker {
    constructor() {
        let private_variables = '프라이빗 변수';
        function private_method () {
            console.log(private_variables);
        }

        this.public_variables = '퍼블릭 변수';
        this.public_method = () => {
            private_method();
            console.log(this.public_variables);
        }
    }
}

위처럼 Public 변수 및 메서드는 기본적인 방식으로 this에 넣어 사용이 가능하다.

class 문법처럼 사용하는 경우 constructor 스코프가 close 되어 closure를 생성하는 느낌이라 아래와 같이 사용하면 에러를 일으킨다.

// 잘못된 방식
class closure_maker {
    constructor() {
        let private_variables = '프라이빗 변수';
        function private_method () {
            console.log(private_variables);
        }

        this.public_variables = '퍼블릭 변수';
    }
    this.public_method() {
        private_method();
        console.log(this.public_variables);
    }
}

 

클로저를 이용해 객체처럼 이용하는 방식을 소개해보았다.

 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures

여기를 참고하면 좋으나 단어들이 어려워 제대로 이해하려면 변수의 스코프, 자바스크립트 함수의 특성 등을 알아야 이해가 쉬워지는 부분이 좀 있다. 최대한 어려운 단어 없이 작성하려 했는데 쉽지 않은 것 같다.

그래도 하나씩 배워가면 재미있다. 언젠가 모르는 단어 없이 이해 가능하지는 그날까지 파이팅

 

 

 

 

 

 

Throttling과 Debouncing는 불필요한 연산을 줄이는 방법으로 이벤트나 비동기 요청을 할 때 주로 사용한다.
예를 들어 DOM를 크게 변경하는 이벤트가 scroll이나 resize 이벤트 같은 곳에 묶여있을 경우, 수많은 함수가 호출되고
키보드 입력을 받는 경우 엔터없이 자동완성을 한다고 했을 때 한 글자 한 글자 입력 시마다 요청을 보내게 될 것이다.


즉, 함수의 호출 비용(시간이나 컴퓨팅 성능이 될 수도 있고, api같은 경우 이용료나 트래픽 비용이 될 수 있다.)이 큰 경우 비용 절감을 위해 성능을 조절하는 기법이다.

 

둘의 차이점은 아래와 같다.

  • Throttling: 함수 호출 후 정해진 시간동안 같은 함수 호출 X
  • Debouncing: 지연 호출되는 함수를 호출 시 이전 호출은 취소하고 마지막 함수만 호출

Throttling은 직관적으로 이해가 쉽지만 Debouncing는 이해가 어렵다. 예제를 통해 알아보자

1. Throttling

Throttling은 일반적으로 첫 함수 실행과 동시에 요청상태값을 변경 후 일정 시간, 행동 뒤에 다시 요청상태값을 변경해 다른 함수의 실행 여부를 판단한다.

 

Throttling을 Javascript로 구현해보자

const throttling = (callback, delay) => {
  let timer;
  let is_called = false;
  return function () {
    if (is_called) return;
    is_called = true;
    callback(...arguments);
    timer = setTimeout(() => is_called = false, delay);
  }
}
/*
주의사항
return 하는 함수에 화살표함수 `() => {}` 형식을 이용하면 arguments를 제대로 가져올 수 없다.
*/

이렇게 만들어주고 사용은 아래와 같이 할 수 있다.

const func = throttling(console.log, 1000);
func('test', 1);
func('test', 2);
func('test', 3);
setTimeout(() => func('test', 4), 900);
setTimeout(() => func('test', 5), 1000);
setTimeout(() => func('test', 6), 1100);

/*
출력 결과
test 1
test 5
*/

결과 해석

  1. func('test', 1); 실행 =>
    is_called는 false였으므로 true로 바뀌고 함수 실행
    1000ms 뒤 is_called = false 하도록 setTimeout 설정
    'test 1' 출력
  2. func('test', 2); 실행 =>
    is_called는 true이므로 return
  3. func('test', 3); 실행 =>
    is_called는 true이므로 return
  4. 약 900ms 뒤 func('test', 4); 실행 =>
    is_called는 true이므로 return
  5. 약 100ms 뒤 setTimeout으로 is_called는 false로 변경
    func('test', 5); 실행 =>
    is_called는 false였으므로 true로 바뀌고 함수 실행
    1000ms 뒤 is_called = false 하도록 setTimeout 설정
    'test 5' 출력
  6. 약 100ms 뒤 func('test', 6); 실행 =>
    is_called는 true이므로 return

2. Debouncing

Debouncing은 함수를 실행 시 해당 함수를 일정 지연시간 또는 행동 뒤에 실행한다.
해당 함수 실행 전 다른 함수가 실행되면 기존 함수는 실행 취소하고 새 함수를 일정 지연시간 또는 행동 뒤에 실행한다.

 

Debouncing을 Javascript로 구현해 보자

const debouncing = (callback, delay) => {
  let timer;

  return function () {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => callback(...arguments), delay);
  }
}
/*
주의사항
return 하는 함수에 화살표함수 `() => {}` 형식을 사용하지 않고
setTimeout callback에 화살표함수 형식을 사용해야 
arguments를 제대로 전달할 수 있다.
*/

이렇게 만들어주고 사용은 아래와 같이 할 수 있다.

const func = debouncing(console.log, 1000);
func('test', 1);
func('test', 2);
func('test', 3);
setTimeout(() => func('test', 4), 900);
setTimeout(() => func('test', 5), 1000);
setTimeout(() => func('test', 6), 1100);

/*
출력 결과
test 6
*/

결과 해석

  1. func('test', 1); 실행 =>
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  2. func('test', 2); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  3. func('test', 3); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  4. 900ms 뒤 func('test', 4); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  5. 약 100ms 뒤 func('test', 5); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  6. 약 100ms 뒤 func('test', 6); 실행 =>
    timer에 등록된 setTimeout 해제
    timer에 1000ms 뒤 실행하도록 setTimeout 설정
  7. 1000ms 뒤 'test 6' 출력

3. 사용 예시

  1. Throttling은 일단 실행 후 다음 실행 함수를 제어하는 것으로 delay가 길어져도 이용자는 일단 최초 실행된 결과를 볼 수 있다.
    Debouncing은 delay가 길어질 경우 이용자는 함수가 먹통이라고 느낄 수 있다.
    위 예시를 보면 Debouncing은 2100ms 만에 콘솔에 출력됨을 확인할 수 있다.
  2. 리스트 출력처럼 최초 출력이 중요한 경우 Throttling이
    키보드 입력처럼 최종 값이 중요한 경우 Debouncing이 적합하다.
  3. 거래목록을 출력하는 경우 스크롤이 끝에 닿을 때쯤 API를 요청한다.
    이때 Throttling을 사용하지 않는 경우 하단 부에서 스크롤이 발생할 때 지속적인 요청이 보내질 수 있다.
    요청 이후 일정 시간 또는 요청이 도착할 때까지 다음 요청을 보내지 않아야 한다.
  4. 검색창에서 디바운싱을 검색할 때 자동완성을 하여 아래에 보여주고자 한다.
    Debouncing을 사용하지 않는 경우 ,,,디바,디방,디바우,디바운,디바운ㅅ,디바운시,디바운싱 10번의 요청을 보내야 한다.
    만약 오타라도 난다면 더욱 불필요한 자동완성 결과를 요청해야 한다.
    그래서 어느 정도 검색어 입력이 완성되었다고 판단하였을 때 자동완성 목록을 요청해야 한다. 300~500ms의 지연시간 정도가 적절할 수 있다.

기타

Throttling과 Debouncing을 구현할 때 여러 방법이 있지만 위와 같은 형태로 만들 때
일반적인 익명함수 형태(function () {})와 화살표함수(() => {})의 차이를 잘 구분해야 한다.
익명함수 형태에서는 자신의 this, arguments, super 등을 가지지만
화살표함수 형태에서는 부모의 값을 상속받는다.

 

참조: MDN - Arrow_functions

 

 

읽어주셔서 감사합니다. 추가할 내용이나 잘못된 내용은 댓글을 남겨주세요

git stash는 git에서 변경사항을 임시로 저장하거나 롤백할 때 사용할 수 있는 명령어이다.
해당 명령어와 이를 적극적으로 활용할 수 있는 방법에 대해 작성하였다.

명령어 살펴보기

git stash 아래에는 수많은 명령어가 있지만 아래 8가지 명령어 만으로 stash를 완전 활용 가능하다.
1, 2, 3, 6번 만으로도 웬만한 활용은 가능하다.

  1. git stash (push)
  2. git stash list
  3. git stash pop
  4. git stash apply
  5. git stash drop
  6. git stash clear
  7. git stash show
  8. git stash branch

1. git stash

현재 변경사항들을 임시저장하는 명령어로 임시저장할 수 있는 공간에 스택으로 저장한다.
push를 생략해 git stash 만으로 처리 가능하다.
이때 -p 옵션을 주면 변경사항을 체크해 일부만 저장할 수 있다.
git stash -p 입력 시 변경사항과 함께 [y, n, q, a, d, j, J, g,/,s, e,?]를 입력할 수 있게 되는데 주어진 변경사항을 어떻게 처리할지 결정할 수 있다. 해당 명령어 중 일부만 나타날 수 있다.

  • y: 이 변경사항 반영
  • n: 이 변경사항 무시
  • q: 이 변경사항과 이후 모든 변경사항 무시
  • a: 이 변경사항 및 이 파일에 남은 변경사항 변경
  • d: 이 변경사항 및 이 파일에 남은 변경사항 무시
  • j: 건너뛰기 (전에 건너뛴 부분이 있었다면 해당 부분으로 이동)
  • J: 건너뛰기
  • g: 원하는 변경사항으로 이동
  • /: 정규직으로 원하는 변경사항 탐색
  • s: 이 변경사항을 쪼개기
  • e: 이 변경사항 수정하기
  • ?: 도움말 출력

2. git stash list

현재 저장된 stash 스택을 확인한다.
stash@{<stash id>}: WIP on <branch>: <commit id> <commit message> 형태로 저장되어 있다.

3. git stash pop <stash id>

주어진 <stash id>의 변경내역을 꺼낸다. 꺼내진 stash는 삭제된다. 생략한다면 0을 꺼낸다.
꺼내는 중 conflict가 발생하면 stash를 삭제하지 않는다.

4. git stash apply <stash id>

주어진 <stash id>의 변경내역을 적용한다. 생략한다면 0을 적용한다.
pop과는 다르게 꺼내는 동시에 삭제하지 않는다.
변경사항을 임시저장만 하고 다시 작업하고 싶을 때 git stash && git stash apply로 편하게 쓸 수 있다.
(한 번에 처리 가능한 방법이 있다면 댓글 남겨주세요)

5. git stash drop <stash id>

주어진 <stash id>의 변경내역을 제거한다. 생략하면 0을 제거한다.

6. git stash clear

모든 stash 스택을 비운다.

7. git stash show

주어진 <stash id>의 변경내역을 확인한다. 생략하면 0을 확인한다.
별도의 옵션이 없으면 변경된 파일이름과 줄 수를 확인할 수 있다.
추가 옵션

  • -p: 상세 변경내역 확인
  • --color-words: 변경 내역을 단어 단위로 확인

8. git stash branch <branch name> <stash id>

주어진 <branch name>으로 <stash id>의 변경사항이 적용된 브랜치를 만든다. <stash id>를 생략하면 0을 확인한다.
정확히는 stash를 생성할 때 상태로 브랜치를 생성하는 것으로 혼동주의
git checkout -b <branch name> && git stash apply <stash id>와 다를 수 있음

 

 

명령어 활용

1. 커밋을 남기지 않고 현재 변경사항을 임시저장할 때 (기본 사용 목적)

  1. 저장 후 기존 깨끗한 HEAD 상태로 돌아가고 싶은 경우
  2. $ git stash # 추후 다시 해당 변경 사항을 불러오고 싶을 때 $ git stash pop
  3. 저장 후 현재 상태를 유지하고 싶은 경우
  4. $ git stash && git stash apply # 추후 다시 해당 변경 사항을 불러오고 싶을 때 $ git stash pop

2. 변경사항을 각각 다른 branch 여러 개에 적용하고 싶을 때

branch1 $ git stash && git stash apply
branch1 $ git checkout branch2
branch2 $ git stash apply
branch2 $ git checkout branch3
branch3 $ git stash pop

3. 작업 중인 branch에 협업자가 forced push를 해, push 사용 시 abort 되는 상황

branch_name $ git push origin branch_name
... abort ...
# push 를 했으나 abort 발생
# reset 을 이용해 커밋을 남기기 전으로 이동 (마지막으로 push 한 지점)
branch_name $ git reset HEAD^ 
branch_name $ git stash
branch_name $ git fetch --all
branch_name $ git reset --hard origin/branch_name
branch_name $ git stash pop
# 변경사항 확인 후 커밋 & push 진행

 

잘못된 내용이나 오타는 댓글을 남겨주세요. 읽어주셔서 감사합니다!

 

참고

Javascript에서 순회를 사용할 때 주로 forEach를 사용합니다.
하지만 forEach는 반복도중 중단을 할 수 없다는 단점이 있습니다.
이를 for 문을 이용해 해결할 수 있지만 좀 더 깔끔하고 명확하게 쓸 수 있는 몇 가지 메서드를 알아보겠습니다

1. Array.prototype.some()과 Array.prototype.every()

some과 every는 각 배열을 순회하며 주어진 조건의 반환값에 참이 존재하는지, 모든 값이 참인지 알려주는 메서드입니다.
some은 참을 만나면 순회를 중단하고 참을 반환하며 순회를 마칠 때까지 참이 나오지 않으면 거짓을 반환합니다.
every는 거짓을 만나면 순회를 중단하고 거짓을 반환하며 순회를 마칠 때까지 거짓이 나오지 않으면 참을 반환합니다.

const arr = ["apple", "banana", 3, "orange"];

const hasApple = arr.some(item => item == "apple");
const allString = arr.every(item => typeof item == "string");

console.log(`hasApple? ${hasApple}`);
// hasApple? true
console.log(`allString? ${allString}`);
// allString? false

위와 같이 사용할 수 있습니다. 같은 방식으로 for문이나 forEach를 사용하면

const arr = ["apple", "banana", 3, "orange"];

let hasApple = false;
for (let i = 0; i < arr.length; i++) {
   if ( arr[i] == "apple") {
       hasApple = true;
       break;
   }
}

let allString = true;
arr.forEach(item => {
    if (typeof item == "string") allString = false;
});

console.log(`hasApple? ${hasApple}`);
// hasApple? true
console.log(`allString? ${allString}`);
// allString? false

위와 같이 쓸 수 있는데 forEach를 쓴 경우 타입이 문자열이 아닌 아이템을 찾아도 순회를 중단할 수 없는 단점이 있습니다. for문을 쓰는 것도 좋은 방식이지만 some이나 every를 사용하는 게 좀 더 명확하게 사용하는 방법이라고 생각합니다.
사실 값의 유무를 찾는 것뿐이라면 indexOf 나 includes를 사용하는 게 깔끔하지만 예시일 뿐입니다.

2. Array.prototype.map()

React를 많이 써보신 분들은 많이 써보셨을 map입니다.
이 메서드는 주어신 함수를 실행해 반환된 값을 배열에 담아 반환해 주는 함수입니다.

const arr = [1, 2, 3, 4];
console.log(arr.map(item => item * 2));
//  [2, 4, 6, 8]

만약 해당 함수를 for문이나 forEach를 이용하게 되면 새 배열을 담을 변수를 할당해야 합니다. react에서 jsx를 사용할 때처럼 굳이 새 변수에 할당할 필요가 없을 때 유용합니다.

3. Array.prototype.find()와 Array.prototype.findIndex()

단순히 인덱스를 찾은거라면 indexOf를 사용하는 것이 좋지만 해당 값을 사용하거나 객체가 담긴 배열을 순회하며 해당 객체에 값을 찾는 경우 유용하게 사용할 수 있습니다.

const arr = [
    {
        id: 1,
        en: 'apple',
        ko: '사과'
    },
    {
        id: 2,
        en: 'banana',
        ko: '바나나'
    },
    {
        id: 3,
        en: 'orange',
        ko: '오렌지'   
    }
];

const findApple = arr.find(item => item.en == 'apple');
const findOrangeIndex = arr.findIndex(item => item.ko == '오렌지');

console.log(findApple);
/*
{
    id: 1,
    en: 'apple',
    ko: '사과'
}
*/
console.log(findOrangeIndex);
// 2

4. Array.prototype.reduce()

배열 메서드의 끝판왕 reduce입니다.
array.reduce(callback, init);
callback함수는 (prev, curr, index, arr) => res 같은 형태로

  • prev: 이전 callback의 결과입니다.
  • curr: 현재 요소입니다.
  • index: 현재 요소의 인덱스입니다.
  • arr: 현재 탐색 중인 배열입니다.
  • res: 계산결과로 다음 callback의 prev에 들어갑니다.

init은 초기값으로 첫 callback의 prev값이 됩니다.
만약 init값을 설정하지 않으면 첫 번째 배열 요소를 prev로 사용하고 두 번째 배열부터 탐색합니다.

const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((prev, curr) => prev + curr);
// 1회: (1, 2) => 3
// 2회: (3, 3) => 6
// 3회: (6, 4) => 10
// 4회: (10, 5) => 15

console.log(sum);
// 15

위와 같이 누산이 필요한 경우 활용할 수 있습니다.
다만, 빈 배열에서 init이 없이 호출하는 경우 error를 일으키므로 init을 전달해 주는 게 안전합니다.


const  arr = [3, 2, -5,  5, 10, 7, -2];
const [sum, min, max] = arr.reduce(([sum, min, max], curr) => [sum + curr, Math.min(min, curr), Math.max(max, curr)], [0, Infinity, -Infinity]);

console.log(sum, min, max);
// 20, -5, 10

이렇게 사용하는 것도 가능합니다.
사실 이쯤되면 그냥 for문이나 forEach를 쓰는 게 가독성이 좋긴 하지만 웬만한 반복문을 3항 연산자와 reduce만 이용한다면 중괄호 없이 한 줄로 끝내버릴 수 있습니다.

가끔 코테를 풀 때
if문 -> 3항 연산자
반복문 -> 배열 메서드
+ spread 연산자, 구조분해할당 등등
을 이용해 극한의 한줄만들기를 하면 나름 재미있습니다.

오늘도 읽어주셔서 감사하고 문제있는 내용은 댓글로 알려주세요!!

참고

  • https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array
  • https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

 

1. _로 숫자 구분하기 (numeric separator)

가끔 큰 단위의 숫자를 적는 경우 한눈에 들어오지 않는 경우가 많은데,
이때 언더바(_)를 이용해 숫자를 쉽게 구분해 줄 수 있습니다.

let num1 = 1000000;
let num2 = 1_000_000;
let num3 = 100_0000;
// num1 == num2 == num3

언더바를 입력하는 위치는 꼭 3자리씩 끊어서 표시할 필요는 없습니다.
다만, 아래처럼 두 개를 이어서 나타내지는 못합니다.

let num_err = 100__000;
// Uncaught SyntaxError: Only one underscore is allowed as numeric separator

2. n진법

let num1 = 11; // 10진법: 11
let num2 = 0b11; // 2진법: 3
let num3 = 0o11; // 8진법: 9
let num4 = 011; // 8진법: 9
let num5 = 0x11; // 16진법: 17

위와 같이 n진법 숫자를 바로 입력할 수 있습니다.

반대로 숫자형을 n진법으로 표현하려면 아래와 같이 가능합니다.

let num = 30;
num.toString(); // '30'
num.toString(2); // '11110'
num.toString(8); //'36'
num.toString(16); // '1e'

숫자형을 n진법으로 바꿀 때는 2~36진법까지 원하는 숫자를 입력해 문자열로 변경가능합니다.

3. BigInt

Javascript에서는 숫자는 number 객체입니다. 이는 타 언어의 double 타입과 비슷하게 생각하면 됩니다.
이렇다 보니 123 이런 식으로 정수를 작성해도 Javascript에서는 부동소수점으로 판단합니다.
아무튼 이때 number가 가질 수 있는 가장 큰 값은 2^53 - 1Number.MAX_SAFE_INTEGER9007199254740991 과 같습니다. 이 값보다 큰 값은 연산결과를 신뢰할 수 없게 됩니다.

9007199254740992 == 9007199254740993 // true
9007199254740999 == 9007199254740992 // false

일반적으로는 이 값보다 큰 값을 사용할 일이 드문데, 코테같은걸 풀다 보면 더 큰 정수가 필요한 상황이 생길 수 있습니다.
이때 BigInt를 사용할 수 있습니다.
BigInt(9007199254740992)9007199254740992n 같은 방식으로 사용할 수 있습니다.
BigInt의 몇 가지 특징을 알아보겠습니다.

1. BigInt는 BigInt끼리만 연산 가능

+, *, -, /, %, **, >>, <<, 연산자들을 모두 이용할 수 있고 항상 부호가 있는 값이기에 >>>(unsigned right shift)를 사용할 수 없습니다. 이때 연산은 BigInt끼리만 할 수 있습니다.

console.log(1n + 3n); 
// 4n 
console.log(1n >> 1); 
// Cannot mix BigInt and other types, use explicit conversions 
let i = 1n; 
console.log(++i); 
// 2n

2. BigInt는 부호가 있는 정수값으로 부동소수점을 가지지 않음

let num1 = 23.1n;
// Uncaught SyntaxError: Invalid or unexpected token
let num2 = 5n / 2n;
console.log(num2);
// 2n (소수점 버림)

3. Number값과 대소구분은 가능

console.log(2n === 2);
// false
console.log(2n == 2);
// true

console.log(2n >= 2);  
// true  
console.log(3n < 2.9899291);  
// false

const numbers = [1n, -20n, 9, 0, 10n];  
numbers.sort();  
console.log(numbers);  
// [-20n, 0, 1n, 10n, 9]

numbers.sort((a, b) => a - b);  
// Cannot mix BigInt and other types, use explicit conversions

numbers.sort((a, b) => a > b ? 1 : a < b ? -1 : 0);  
console.log(numbers);  
// [-20n, 0, 1n, 9, 10n]

4. Number와 똑같이 _과 n진법 응용 가능

let num1 = 1_000_000n // 1000000n

let num2 = 11n; // 10진법: 11n
let num3 = 0b11n; // 2진법: 3n
let num4 = 0o11n; // 8진법: 9n
let num5 = 011n; // 8진법: 9n
let num6 = 0x11_11n; // 16진법: 4369n

let num7 = 30n;
num.toString(); // '30'
num.toString(2); // '11110'
num.toString(8); //'36'
num.toString(16); // '1e'

위처럼 Number와 다르지 않게 _과 n진법 활용이 가능합니다.

4. 이상한 자바스크립트?

javascript meme이라고 구글에 검색하면 자바스크립트의 자동형 변환 때문에 생기는 이상한 허점들이 많이 나오는데요

그중 숫자와 관련한 몇 가지를 알아보면

/* 
true는 1과 같지만 타입은 다르다
true를 세번 더하면 타입까지 3이랑 같아진다...
*/
true == 1 // true
true == 3 // false
true === 1 // false
true + true + true === 3 // true

/* 
문자열 'Infinity'는 Infinity와 같지만 타입은 다르다
문자열 'Infinity'에 -를 붙이면 타입까지 Infinity와 같아진다...
*/
'Infinity' == Infinity // true
'Infinity' === Infinity // false
-'-Infinity' === Infinity // true

/*
문자열끼리 연산할 때 더하면 붙이고 뺄땐 빼고...?
*/
2 + 2 - 2 // 2
'2' + '2' - '2' // 20

이 외에도 재미있는(?) 이슈들이 있으니 검색해 보세요. 직접 이런저런 계산하면서 찾아봐도 재미있습니다.

내용에 문제가 있거나 추가할 내용이 있다면 댓글 남겨주세요

참고

v8 - Numeric separators
mdn web docs - Number
mdn web docs - BigInt

Web API Javascript IntersectionObserver 사용해 보기

여러 Web API 중 변경사항을 감지하는 Observer들이 존재합니다.
이 중 매우 유용하게 쓰일 수 있는 IntersectionObserver, MutationObserver 등이 있는데 이 중에서 IntersectionObserver를 사용해보고자 합니다.

IntersectionObserver 란?

IntersectionObserver는 Web API 중 하나로 Viewport나 정해진 영역과 다른 영역이 겹치는지, 얼마나 겹치는지 감지하는데 이용하는 API입니다.
대부분 Modern browser에서 간단하게 사용가능합니다.

 

해당 API의 존재를 알기 전에는 주로 addEventListener('scroll', callback); 을 이용해 스크롤 위치와 요소의 상대위치 및 크기를 고려해 직접 계산해야 하는 고생을 해야 했는데, 해당 API를 사용하면 더 쉽고 최적화된 성능으로 개발이 가능합니다.

IntersectionObserver 사용해 보기

IntersectionObserver는 Viewport와 등록된 요소가 겹치는지 감지하는 Observer입니다.
즉, 등록된 Element가 노출이 될 때, 노출이 해제될 때 callback으로 등록된 함수가 실행된다고 생각하시면 됩니다.

 

다양한 활용이 가능하지만 가장 많이 사용되는 방식인 scroll에 따른 상호작용을 해보겠습니다.

<div class="wrap">
    <div class="div">Element</div>
    <div class="div">Element2</div>
</div>
<style>
    .wrap {
        padding: 400px 0;
    }
    .wrap > div {
        width: 200px;
        height: 80vh;
        border: 1px solid #000;
        margin-bottom: 20px;
        padding: 20px;
    }
    .div:nth-of-type(1) {
        background-color: #fafafa;
    }
    .div:nth-of-type(2) {
        background-color: #eaeaea;
    }
</style>
<script>
const options = {};
const observer = new IntersectionObserver(console.log, options);
// └ callback에 console.log를 넣어 감지된 요소를 console에 보여주는 observer 생성
document.querySelectorAll('.wrap > div').forEach(element => observer.observe(element));
// └ .wrap > div 에 해당하는 element들을 감지하도록 등록
</script>

위와 같이 세팅하고 테스트해보면 화면에서 스크롤 시 div가 사라지고 나타날 때마다 IntersectionObserverEntry 객체가 배열에 담겨 console에 찍히는 것을 확인할 수 있습니다.
또 위에서는 options에 빈 객체를 입력했지만 여러 option으로 Observer를 설정할 수 있습니다.


IntersectionObserverEntryoptions에 대해서는 아래에서 자세히 보겠습니다.

IntersectionObserverEntry 알아보기

  1. boundingClientRect
    Element.getBoundingClientRect()를 통해 얻을 수 있는 DOMRectReadOnly객체를 가져옵니다.
  2. intersectionRect
    DOMRectReadOnly 객체를 가져오나 겹치는 영역에 대해서만 값을 표시합니다.
    (intersection 밖의 영역이면 모든 값이 0으로 표시되고 잘린다면 보이는 부분의 Height나 Width만 나타내줌)
    아래 이미지를 보시면 더 쉽게 이해 가능합니다.
  3. intersectionRatio
    intersection에 겹치는 영역의 비율입니다.
    조금이라도 겹치지 않으면 0, Viewport에 완전히 들어간다면 1로 0 ~ 1의 정수값을 가집니다.
  4. isIntersecting
    intersectionthreshold 이상의 영역이 겹치는지 여부입니다.
    observer를 생성할 때 option으로 threshold값을 명시해줄 수 있습니다. threshold의 기본 값은 0.0으로 0.0일 때는 1px이라도 영역이 겹치면 true가 됩니다. 만약 threshold값이 여러 개일 경우 최솟값을 기준으로 값을 보여줍니다.
  5. rootBounds
    감지영역의 DOMRectReadOnly를 가져옵니다.
    기본적으로 Viewport이나 observer를 생성할 때 입력한 options에 따라 달라질 수 있습니다.
  6. target
    관찰한 Element입니다.
    style을 조정하거나 텍스트를 수정하는 등으로 이용할 수 있습니다.
  7. time
    time origin으로부터 해당 감지가 발생한 시점까지의 시간차를 ms단위로 나타낸 값입니다.
    time origin는 일반적으로 페이지가 호출된 시점으로 생각하면 됩니다. (Window 객체가 생성된 시점)

Options 알아보기

  1. root
    root로 지정한 Element를 Viewport로 사용합니다.
    이때 document는 cross browsing 문제로 사용을 지양하는 것이 좋습니다. (Safari, Android Firefox 에서 사용 불가)
    기본값은 null로 기본 Viewport를 사용합니다.
  2. rootMargin
    입력된 값으로 Viewport를 확장합니다.
    css margin입력하는 것처럼 입력 가능하고 px이나 %를 사용해야 합니다.
    기본값은 '0px 0px 0px 0px'입니다.
  3. threshold
    0.0 이상, 1.0 이하의 숫자 단일 값이나 해당 값으로 이루어진 배열로 감지하는 Element의 원본크기와 Viewport에 보이는 영역 크기 비가 해당 값일 때 callback을 실행합니다.
    즉, 해당 값보다 작아질 때 커질 때 각각 감지하게 되며 값이 여러 개일 경우 (배열 안에 나열) 각각의 값에 도달할 때마다 callback이 실행됩니다.
    기본값은 [0.0]입니다.

IntersectionObserver 응용하기

위의 예시는 Scroll event로도 쉽게 구현이 가능한 예시였습니다.
하지만 Scroll event로는 영역이 얼마나 겹치는지 자세하게 계산이 필요한 경우 점점 복잡한 코드를 요구합니다.


아래 예제들을 통해 IntersectionObserver가 얼마나 간단하게 구현 가능한지 확인해 보겠습니다.

<div class="wrap">
    <section class="section1">
        <h2>title1</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
        <p>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p>
        <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris</p>
        <p>nisi ut aliquip ex ea commodo consequat</p>
    </section>
    <section class="section2">
        <h2>title2</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
        <p>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p>
        <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris</p>
        <p>nisi ut aliquip ex ea commodo consequat</p>
    </section>
    <section class="section3">
        <h2>title3</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
        <p>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p>
        <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris</p>
        <p>nisi ut aliquip ex ea commodo consequat</p>
    </section>
</div>
<style>
    body {
      margin: 0;
      }
    .wrap > section {
        width: 100%;
        height: 80vh;
        border: 1px solid #000;
        padding: 20px;
        box-sizing: border-box;
    }
    .wrap > section > * {
        position: relative;
        opacity: 0;
        top: 10px;
        transition: all 500ms ease;
    }
    .wrap > section.on > * {
        opacity: 1;
        top: 0;
    }
</style>
<script>
const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            entry.target.classList.add('on');
            // 영역 겹침이 50% 이상 발생하면 -> class 'on' 추가
        } else {
            entry.target.classList.remove('on');
            // 영역 겹침이 50% 미만으로 줄어들면 -> class 'on' 삭제
        }
    });
}, {threshold: [0.5]});
document.querySelectorAll('.wrap > section').forEach(element => observer.observe(element));
</script>

각 세션의 50% 영역이 화면에 노출될 때 하위 요소들이 fade-in 하면서 아래에서 위로 떠올라 자연스럽게 노출되는 것을 확인할 수 있습니다.


조금 더 복잡하게 조건을 주게 되면

<div id="pos"><!-- 화면 내 세션이 몇 번 세션인지 --></div>
<div class="wrap">
    <section class="section1" data-tab="1">
        <h2>title1</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
        <p>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p>
        <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris</p>
        <p>nisi ut aliquip ex ea commodo consequat</p>
    </section>
    <section class="section2" data-tab="2">
        <h2>title2</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
        <p>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p>
        <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris</p>
        <p>nisi ut aliquip ex ea commodo consequat</p>
    </section>
    <section class="section3" data-tab="3">
        <h2>title3</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
        <p>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p>
        <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris</p>
        <p>nisi ut aliquip ex ea commodo consequat</p>
    </section>
</div>
<style>
    body {
        margin: 0;
    }
    .wrap {
        padding: 60px 0 0;
    }
    .wrap > section {
        width: 100%;
        height: 80vh;
        border: 1px solid #000;
        padding: 20px;
        box-sizing: border-box;
    }
    .wrap > section > * {
        position: relative;
        opacity: 0;
        top: 10px;
        transition: all 500ms ease;
    }
    .wrap > section.on > * {
        opacity: 1;
        top: 0;
    }
    #pos {
        position: fixed;
        top: 0;
        z-index: 1;
        width: 100%;
        height: 60px;
        padding: 20px;
        background-color: #d9d9d9;
        border-bottom: 1px solid #222;
        box-sizing: border-box;
    }
</style>
<script>
const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        if (entry.intersectionRatio >= 0.5) {
            entry.target.classList.add('on');
            // 영역 겹침이 50% 이상 발생하면 -> class 'on' 추가
        }
        if (entry.intersectionRatio <= 0.3) {
            entry.target.classList.remove('on');
            // 영역 겹침이 30% 이하로 줄어들면 -> class 'on' 삭제
        }
        if (entry.intersectionRatio >= 0.7) {
            document.getElementById('pos').innerText = `This is session ${entry.target.dataset.tab}`;
            // 영역 겹침이 70% 이상 발생하면 -> #pos의 텍스트 변경
        }
    });
}, {threshold: [0.3, 0.5, 0.7], rootMargin: '-60px 0px 0px'});
document.querySelectorAll('.wrap > section').forEach(element => observer.observe(element));
</script>

세션의 intersectionRatio가 0.5에 도달 시 자연스럽게 콘텐츠를 노출, 0.3미만으로 떨어질 시 컨텐츠 숨기기
현재 세션을 알려주는 부분을 만든 뒤 0.7에 도달 시 갱신시켜 주기
이런 식으로 좀 더 세세하게 변화를 적용시켜줄 수 있습니다.

 

좀 더 극단적으로 threshold를 늘려주면

<div class="wrap">
    <section class="section1" data-tab="1">
        <h2>title1  [<span class="ratio"></span>]</h2>
    </section>
    <section class="section2" data-tab="2">
        <h2>title2  [<span class="ratio"></span>]</h2>
    </section>
    <section class="section3" data-tab="3">
        <h2>title3  [<span class="ratio"></span>]</h2>
    </section>
</div>
<style>
    body {margin: 0;}
    .wrap > section {
        width: 100%;
        height: 80vh;
        border: 1px solid #000;
        padding: 20px;
        box-sizing: border-box;
    }
</style>
<script>
const threshold = [];
for (let i = 0; i < 1; i += 0.01) {
    threshold.push(i);
}
threshold.push(1);
// threshold를 0 에서 1까지 0.01 단위로 threshold 설정
// 정수로 1을 push 해준 이유는 float 0.01을 100번 더했을 때 1이 아니기 때문
const observer = new IntersectionObserver(entries => {
    entries.forEach(({target, intersectionRatio}) => {
        target.getElementsByClassName('ratio')[0].innerText = `${Math.round(intersectionRatio * 100)}%`;
        // 각 세션의 화면 노출 비율을 %단위로 표시
    });
}, {threshold});
document.querySelectorAll('.wrap > section').forEach(element => observer.observe(element));
</script>

위와 같은 사용도 가능합니다.


Web API 중 하나인 IntersectionObserver를 사용해 보았습니다.
Scroll event를 사용하는 것보다 성능면에서도 우수하고 코드도 직관적이며 짧습니다.
여러모로 반응형 인터페이스를 구성하는데 매우 효과적인 API입니다.

 

글에 오류가 있거나 추가할 내용이 있다면 언제라도 댓글을 남겨주세요!

 

다음에는 다른 Observer인 MutationObserver를 소개하는 글을 작성해 보겠습니다.

참고

mdn web docs - Intersection Observer API

HTML Object overflow 감지

HTML 내 요소에서 자식요소가 부모요소보다 클 때 css 상에서 overflow 속성을 정의해 처리를 할 수 있습니다.
하지만 이를 javascript에서 동적으로 처리를 하고 싶다면 다른 방법을 이용해야 합니다.
이때 2가지 방법을 이용할 수 있습니다.

1. scrollWidth 이용

위 이미지를 참고하면 clientWidthscrollWidth를 비교하여 scrollWidthclientWidth보다 크다면 overflow가 발생했음을 알 수 있습니다.


아래의 예제를 따라 해보면 문제없이 결과를 얻을 수 있습니다.

<div class="wrap">
    <!-- short는 200px 미만으로 overflow가 발생하지 않는다. -->
    <div id="short">Lorem ipsum dolor sit amet</div>
    <div id="long">consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
</div>
<style>
    .wrap > div {
        width: 200px;
        white-space: nowrap;
    }
</style>
const short = document.getElementById('short');
const long = document.getElementById('long');
// └ getElementById를 이용해 Element 객체 가져오기

const isOverflow = element => {
    const {clientWidth, scrollWidth} = element;
    // └ 구조분해 할당으로 clientWidth, scrollWidth 값 가져오기
    return clientWidth < scrollWidth;
}

console.log(`is overflow? ${isOverflow(short)}`); 
// 출력: is overflow? false
console.log(`is overflow? ${isOverflow(long)}`);  
// 출력: is overflow? true

좀 더 응용해 보면

const short = document.getElementById('short');
const long = document.getElementById('long');

const getDiff = element => {
    const {clientWidth, scrollWidth} = element;
    const diff = scrollWidth - clientWidth;
    return diff ? `overflow ${diff}px` : 'not overflow';
}

console.log(getDiff(short)); 
// 출력: not overflow
console.log(getDiff(long));  
// 출력: overflow 439px

이렇게 얼마나 콘텐츠가 넘치는지 체크할 수 있습니다.

2. cloneNode 이용

두 번째 방법은 Node객체의 instance methods 중 하나인 cloneNode를 이용한 방법입니다.

Element객체는 Node객체를 상속받은 객체로 Node객체의 instance methods를 이용 가능합니다.
cloneNode는 한 Node객체를 그대로 복사해오는 method로 parameter로 boolean을 받아 자식요소들도 복제할지 선택할 수 있습니다.

 

복제해온 Node의 style에 길이를 max-content로 지정해 길이를 구하고 제거하는 방식으로 overflow를 감지할 수 있습니다.

<div class="wrap">
    <!-- short는 200px 미만으로 overflow가 발생하지 않는다. -->
    <div id="short">Lorem ipsum dolor sit amet</div>
    <div id="long">consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
</div>
<style>
    .wrap > div {
        width: 200px;
        white-space: nowrap;
    }
</style>

바로 1번 방식의 응용 예제를 따라 해보면

const short = document.getElementById('short');
const long = document.getElementById('long');
// └ getElementById를 이용해 Element 객체 가져오기

const getCloneWidth = element => {
    const clone = element.cloneNode(true);
    // └ parameter로 true를 전달해 자식요소를 함께 복사
    clone.style.width = 'max-content';
    // └ 콘텐츠가 최대로 커질 수 있는 너비로 변경
    element.parentNode.appendChild(clone);
    // └ 원본 요소의 부모요소에 append해 상속받는 style도 적용하기
    const cloneWidth = clone.clientWidth;
    // └ 복제된 요소의 길이 구하기
    element.parentNode.removeChild(clone);
    // └ 복제된 요소 제거
    return cloneWidth;
}


const getDiff = element => {
    const width = element.clientWidth;
    const cloneWidth = getCloneWidth(element);
    const diff = cloneWidth - width;
    return diff ? `overflow ${diff}px` : 'not overflow';
}

console.log(getDiff(short)); 
// 출력: overflow -15px
console.log(getDiff(long));  
// 출력: overflow 439px

long의 결과는 1번 방식과 같은 결과를 보이나 short의 경우가 -15px로 나타남을 볼 수 있습니다.

 

차이는 콘텐츠의 최대 너비를 구하는 과정에서 발생하는데 scrollWidth는 요소가 아무리 작아도 clientWidth보다 작아지지 않습니다.

그러나 cloneNode방식을 이용했을 때는 복제된 Node의 너비 자체를 콘텐츠의 크기로 변경한 것으로 getCloneWidth에서 반환된 크기는 콘텐츠의 크기가 됩니다.

 

즉, Lorem ipsum dolor sit amet의 길이가 185px일 때 scrollWidth는 200px이 되는것이고 getCloneWidth의 반환 값은 185px이 되는 것입니다.


추가로 콘텐츠 크기를 구할 수 있는 cloneNode 방식을 Element에 바인딩할 수 있습니다.

Object.defineProperty(Element.prototype, 'contentWidth', {get() {
    const clone = this.cloneNode(true);
    clone.style.width = 'max-content';
    this.parentNode.appendChild(clone);
    const cloneWidth = clone.clientWidth;
    this.parentNode.removeChild(clone);
    return cloneWidth;
}});

위와 같이 작업해두면 아래처럼 간단하게 이용 가능합니다.

const element = document.getElementById('someId');
console.log(element.contentWidth);
const elements = document.querySelectorAll('someSelector');
elements.forEach(element => console.log(element.contentWidth));

마무리로 두가지 방법을 요약하자면

감지방식 scrollWidth cloneNode
콘텐츠 크기 < clientWidth clientWidth 콘텐츠 크기
성능 빠름(scrollWidth값을 읽기만 하면 됨) 느림(DOMcloneNode를 그려야함)
추천 상황 일반적으로 사용 정확한 콘텐츠 크기를 비교해야할 때

로 볼 수 있습니다.

위 방법들을 이용해 Javascript에서 HTML Element에 Overflow가 발생했는지 간단히 알아볼 수 있습니다.

+ Recent posts