# 2023.05
# 5/6
# 일급함수 (first-class function)
- 일급함수는 함수를 값으로 다룰 수 있는 성질을 가진 함수.
- 함수를 변수에 할당하거나,
const sum = function add(a, b) {
return a + b;
};
// 화살표 함수로 하자면
const sum = (a, b) => a + b;
- 함수의 인자로 다른 함수를 전달하거나,
const calculate = (a, b, operation) => operation(a, b);
- 함수에서 새로운 함수를 반환할 수 있는 함수를 일급함수라고 한다.
const multiply = (x) => (y) => x * y;
const double = multiply(2);
console.log(double(5)); // 10
이런 일급함수는 함수형 프로그래밍에서 중요한 역할을 하는데, 함수를 값으로 다룰 수 있다는 성질 때문에 함수를 조합해서 새로운 함수를 만들거나, 함수를 추상화하여 코드의 재사용성을 높일 수 있다.
즉, 함수가 일급이라는 것은 함수의 결과값으로 함수를 사용할 수 있다는 걸 뜻하는데..
const foo = () => () => 'foo';
console.log(foo()); // () => 'foo' (함수의 결과가 함수.)
함수가 일급이라는 성질을 이용해서 많은 조합성을 만들어내고 추상화의 도구로 사용하고 있음.
# 고차 함수 (Higher-order function)
함수를 인자로 받거나, 함수를 반환하는 함수를 고차 함수라고 한다. 일급 함수의 특성을 가진 언어에서 함수형 프로그래밍을 할 때, 고차 함수를 사용하면 코드의 재사용성과 추상화 수준을 높일 수 있다.
어플리케이티브 프로그래밍
- 계산을 값과 함수의 조합으로 표현.
- 상태를 변경하는 명령형 프로그래밍과 달리, 값을 변환하고 반환하는 함수형 프로그래밍 방식을 채택.
- 함수형 프로그래밍의 일종으로 주로 고차 함수, 클로저, 람다식, 커링 등의 개념이 사용.
- 함수를 인자로 받아서 실행하는 함수
// 배열을 인자로 받아서 각 요소마다 함수를 실행하는 고차 함수
const map = (arr, fn) => arr.map(fn);
const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = map(numbers, (x) => x * x);
console.log(squaredNumbers); // [1, 4, 9, 16, 25]
- 함수를 만들어 리턴하는 함수 (클로저를 만들어 리턴)
// closer를 리턴하는 함수
const createMultiplier = (multiplier) => (number) => number * multiplier;
// createMultiplier 함수의 매개변수 multiplier를 내부 함수에서 참조하고 있기 때문에,
// 내부 함수는 클로저가 된다.
const double = createMultiplier(2);
console.log(double(5)); // 10
const triple = createMultiplier(3);
console.log(triple(5)); // 15
클로저
클로저란 함수가 자신이 생성될 당시의 환경(lexical environment)을 기억하고, 이 환경에 속한 변수들에 접근할 수 있는 함수
# 5/9
# ES6의 이터레이션 프로토콜
ES6 이전에는 이터레이션 프로토콜이 없었다. 그 대신에 배열의 경우 for문이나 while문으로 순회했고, 객체는 for-in문을 사용해서 순회했다.
하지만 각각의 타입에 따라 다르게 순회하던 방식은 이터레이션 프로토콜이 도입되면서 for-of 구문, 보다 선언적으로 순회하는 방식으로 간결하게 변경될 수 있었다.
복잡한 순회 방식을 더 간결하게 만들어준 것은 이 이터레이션 프로토콜에 있다. 이터레이션 프로토콜은 다양한 데이터 타입들이 단 하나의 순회 방식을 갖도록 함으로써 효율적으로 데이터를 사용할 수 있도록 하고, 요것이 바로.. 이터레이션 프로토콜의 존재 의미라고 할 수 있겠다.
예를 들어보자. Array인 경우에는 키로 접근하지만 Set이나 Map의 경우 키로 접근할 수 없다는 걸 확인할 수 있다. 따라서 for-of 구문은 내부적으로 기존의 배열에 사용되는 for문처럼 키와 키에 매핑이된 값을 찾아내는 방식이 아니라는 것. 즉, 이터레이션 프로토콜에 의한 방식이다.
이터레이션 프로토콜을 준수하기 위해서는 객체 내부에 Symbol.iterator
라는 메서드가 구현되어 있어야 하고, 요 메서드는 이터레이터를 반환해야 한다.
이터레이터는 next
메서드를 가지고 있는데, 이 메서드는 객체의 다음 값을 반환하고 값이 없는 경우 done: true
를 반환한다.
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
console.log(iterator.next().done); // true
이터레이션 프로토콜을 준수한 객체는 for-of 문으로 순회할 수 있을 뿐만 아니라 전개 연산자나 구조 분해 할당의 대상으로 사용할 수 있다.
ES6의 빌트인 이터러블
Array, String, Map, Set, TypedArray(Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array), DOM data structure(NodeList, HTMLCollection), Arguments
## 5/10
### Use undefined, do not use null.
자바스크립트에서 undefined를 할당하는 것이 좋은지에 대해 의견이 분분한데..
그동안 '값이 없음'을 나타낼 때에는 null을 사용해야 한다는 입장이었다.
내 생각에 undefined는 변수가 할당되기 전의.. '초기화되지 않은 값'이라면, null은 '빈 값'이라고 개발자가 명확하게 부여하는 값이기 때문에, 어떤 변수에 빈 값임을 표현하고 싶다면 null로 할당을 해야 한다고 생각했다.
가령 비동기로 받아온 데이터를 어떠한 변수에 할당한다고 했을 때를 예를 들어 보자.
비동기로 데이터를 받아오기 전까지 그 변수는 undefined 상태이고 실패한 경우에도 변수는 undefined 상태로 남겠지만, 성공해서 데이터에 할당된 이후 그 데이터를 초기화할 때에는 null로 초기화를 하는 것이다.
물론 null을 사용하게 되면 falsy한 값을 처리하기 위해 null과 undefined 이 둘을 동시에 체크해야 하는 단점도 있고, (하지만 요것도 lodash의 isNil 메소드를 이용하면 ok이고..) typeof null은 object이기 때문에 예외적으로 다뤄야할 상황도 생기긴 하겠다. (요것도 null을 구분할 때 typeof를 사용하지 않으면 그만이긴 하다..)
더글라스 크록포드가 <더글라스 크락포드의 자바스크립트 핵심 가이드> 책에서 undefined 만을 사용하자고 주장했는데, 그 주된 이유는 코드의 안정성과 일관성을 유지하기 위함이었다.
undefined는 자바스크립트 엔진에서 자동으로 할당되는 값이고, null을 사용하면 falsy한 값이 더 추가되는 것이기 때문에 null을 사용하지 않으면 더 일관성 있고 안정적으로 코드를 쓸 수 있다는 것.
사실 이제까지 null을 사용하지 말아야 한다는 생각 자체를 가져본 적이 없어서 혼란스러운데.. 다른 개발자들은 어떻게 생각하는지 의견도 구해보고 고민을 더 거쳐봐야겠다.
# 5/11
# 이터러블과 지연 평가(lazy evaluation)
이터러블은 JS에서 순회 가능한 데이터 구조를 말하고, 지연 평가는 계산이 필요할 때까지 값을 평가하지 않는 프로그래밍 기법을 말한다. 이 둘을 결합하면 비용이 많이 드는 작업이나 큰 데이터를 다룰 때 유용한데, 데이터가 필요한 경우에만 계산을 하기 때문에 비효율을 줄일 수 있다.
자바스크립트는 기본적으로 조급하게 평가(eager evaluation)하는 언어다. 즉, 모든 것을 미리 평가한다는 뜻인데, 표현식을 평가하는 과정에서 피연산자가 모두 준비되어 있다면 바로 계산을 수행한다.
자바스크립트에는 단축 평가라는 개념이 있다.
자바스크립트에서 &&
연산자는 왼쪽 피연산자가 false로 평가되면 바로 false를 반환하고, 오른쪽 피연산자는 평가하지 않는다.
또한 ||
연산자는 왼쪽 피연산자가 true로 평가되면 바로 true를 반환하고, 마찬가지로 오른쪽 피연산자는 평가하지 않는다.
따라서 자바스크립트에서 지연 평가를 구현하려면 제너레이터 함수와 이터레이터 객체를 이용할 수도 있겠으나 현실적으로는 Lodash나 RxJS와 같은 라이브러리를 사용해서 데이터를 처리하면 된다.
lodash의 lazy()
메서드를 이용하거나 RxJs의 Observable
객체를 사용해서 지연 평가를 구현할 수 있다.
# 5/12
# content-visibility와 contain-intrinsic-size
chrome 85에 추가된 content-visibility
프로퍼티로 페이지 로드 성능을 향상시킬 수 있다.
content-visibility는 브라우저에게 해당 요소의 내용이 뷰포트에 보이지 않으면 렌더링하지 않도록 지시하는 CSS 속성이다.
해당 요소의 레이아웃(layout), 스타일(style), 페인트(paint) 단계를 최적화하므로 페이지 로드 속도를 높일 수 있다.
content-visibility: auto
를 사용하면 브라우저가 요소가 화면에 나타날 때까지 해당 요소의 하위 요소를 렌더링하지 않는데.. 하위 요소의 스타일링, 레이아웃 및 페인팅 작업을 지연시켜 전체 페이지 로드 속도를 더욱 향상시킬 수 있다.
.lazy-image {
content-visibility: auto; // default: visible
contain-intrinsic-size: 300px 300px;
}
content-visibility: auto
는 브라우저가 해당 요소의 내용을 렌더링하지 않도록 하는데, 브라우저는 해당 요소의 내용을 렌더링하지 않고 레이아웃을 계산할 때 해당 요소의 크기를 고려하지 않는다.
따라서 브라우저는 해당 요소의 크기를 알 수 없기 때문에 레이아웃 계산을 건너뛰고 렌더 트리를 생성할 때 해당 요소의 크기를 0으로 설정한다.
이때 0으로 설정된 요소 때문에 브라우저가 해당 요소의 크기를 계산할 수 없어 레이아웃 처리에 문제가 생길 수 있겠다. 띠라서 contain-intrinsic-size
를 함께 적용해 intrinsic size를 지정해 주면 된다..!
contain-intrinsic-size
는 width와 height를 받는데, placeholder와 같이 브라우저가 해당 요소의 크기를 계산할 때 사용할 수 있는 최소 크기를 미리 지정한다. 따라서 페이지 로드 상황에서 해당 요소의 크기를 알 수 있기 때문에 레이아웃 처리 속도가 빨라질 수 있다.
# content-visibility: hidden
와 visibility: hidden
차이
둘 다 요소를 숨기는데 동작 방식에서 차이가 있다.
visibility: hidden
은 요소를 숨기지만, 여전히 요소가 문서의 레이아웃에 영향을 미치고 있으며, 브라우저는 이 요소의 레이아웃과 렌더링을 계산한다.
따라서 요소가 숨겨져 있더라도.. 렌더링 상태를 업데이트할 필요가 있을 때마다 브라우저는 다시 계산하게 된다.
content-visibility: hidden
은 요소를 숨기면서도 해당 요소의 레이아웃 및 렌더링 계산을 최적화할 수 있다. 브라우저가 해당 요소를 렌더링하지 않으므로, 초기 페이지 로드 혹은 스크롤 상황에서 성능이 향상될 수 있다. 요소가 변경이 없다면, content-visibility: hidden
이 설정된 요소는 렌더링 상태를 업데이트할 필요가 없기 때문에 렌더링 성능을 향상시킬 수 있다.
display: none
은 요소를 완전히 제거하기 때문에 렌더링 과정에서도 계산되지 않는다. 하지만 요소를 다시 표시하는 상황이라면 새 요소를 렌더링하는 것과 같이 많은 비용이 든다.
2022.11 →