- CF
- 모던자바스크립트DeepDive
- JavaScript
- composition api
- 프로그래머스
- Algorithm
- fetchpriority
- preload
- programmers
- 백준
- S3
- html
- 캐시무효화
- 자바스크립트
- SSH
- amplify
- IntersectionObserverAPI
- AWS
- nuxt
- Prefetch
- TypeScript
- github
- javascirpt
- eslint
우주선
모던 자바스크립트 Deep Dive ― (6) 본문
모던 자바스크립트 Deep Dive ― (6)
24장 클로저
클로저는 자바스크립트 고유의 개념이 아니라서 ECMAScript 사양에 등장하지 않는다.
MDN에서는 클로저에 대해 다음과 같이 정의한다.
클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
우선 '함수가 선언된 렉시컬 환경'이 무엇인가?
자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정하고, 이를 렉시컬 스코프(정적 스코프) 라고 한다.
함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다. 함수 객체는 내부 슬롯에 저장한 렉시컬 환경의 참조(=상위 스코프)를 자신이 존재하는 한 기억한다.
다음 예제에서 외부 함수보다 중첩 함수의 생명 주기가 긴 경우를 볼 수 있다.
const x = 1
function outer(){
const x = 10
const y = 20
const inner = function (){ console.log(x)}
return inner
}
// outer 함수를 호출하면 중첩 함수 inner를 반환한다.
// outer 함수의 실행 컨텍스트는 스택에서 제거되어 사라진다 (생명주기 종료)
const innerFunc = outer();
innerFunc() // 결과값 10
외부 함수(예제의 outer)보다 중첩 함수(예제의 inner)가 더 오래 유지되는 경우이다. 중첩 함수는 이미 생명 주기가 종료한 외부 함수에 대한 변수(예제의 x)를 참조할 수 있다. 이런 중첩 함수를 클로저 라 부른다.
예제의 outer 함수는 생명 주기가 종료되어 실행 컨텍스트 스택에서 제거되지만, outer 함수의 렉시컬 환경까지 소멸하는 것은 아니다. outer 함수의 렉시컬 환경은 inner 함수의 [[Environment]] 내부 슬롯에 의해 참조되고 있고 , 또한 전역 변수 innerFunc에 의해 참조되고 있으므로 가비지 컬렉션의 대상이 되지 않는다.
자바스크립트의 모든 함수는 상위 스코프를 기억한다.
그 중에서도 1. 중첩 함수가 상위 스코프의 식별자를 참조 2. 중첩 함수가 외부함수보다 오래 생존 하는 조건을 가진 함수를 일반적으로 클로저라고 한다.
클로저에 의해 참조되는 상위 스코프의 변수를 자유 변수 ( 예제에서는 x ) 라고 부른다. 클로저는 자유 변수에 묶여있는 함수다. 참고로 자바스크립트 엔진 최적화를 위해 클로저가 참조하고 있지 않은 식별자는 기억하지 않는다. ( 예제에서는 y )
24.4 클로저의 활용
클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다.
상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 호용하기 위해 사용한다.
아래 예제를 보면 상태값인 num에 대한 다른 함수의 변경을 방지하고 은닉시키며 클로저 함수로서만 변경을 가능케 한다.
const increase = ((function(){
let num = 0
return function (){ return ++num }
})())
console.log(increase()) // 1
console.log(increase()) // 2
console.log(increase()) // 3
그러나 생성자 함수로 클로저를 만들 경우, 생성자 함수를 호출할 떄 자신만의 독립된 렉시컬 환경이 생긴다. 생성자 함수를 여러 번 호출하여 같은 변수를 변경할 수 없다. 독립된 변수가 아닌 다른 렉시컬 환경의 변수로서 연동이 불가능하기 때문이다.
24.5 캡슐화와 정보 은닉
캡슐화는 객체의 상태를 나타내는 프로퍼티와, 프로퍼티를 조작하는 메서드를 하나로 묶는 것을 말한다.
정보 은닉은 구현의 일부를 외부에 공개하지 않도록 낮춘다. 객체 간의 상호 의존성(결합도)를 낮춘다.
자바스크립트가 public, pricate, protected와 같은 접근 제한자를 제공하지 않아 기본적으로 객체의 모든 프로퍼티와 메서드는 public이다. 클로저를 통해 자유 변수로 private를 흉내낼 수는 있지만 본질적으로는 private를 똑같이 재현할 수는 없다.
24.6 자주 발생하는 실수
클로저를 사용할 때 자주 발생할 수 있는 실수가 있다.
for 문과 같은 반복문을 사용할때 조건에 var 키워드를 사용한다면 함수 레벨 스코프 특성으로 인해 var 키워드로 선언한 변수가 전역 변수가 되기 때문에 중첩 함수에서 자유 변수로 사용할 수 없다. let 키워드를 사용하면 for문의 코드 블록이 반복 실행될 때마다 for문 코드 블록의 새로운 랙시컬 환경이 생성된다. 이때 함수의 상위 스코프는 for문의 코드 블록이 반복 실행될 때마다 식별자의 값을 유지한다.
이 게시글을 참고하면 좋을 듯 하다.
25장 클래스
자바스크립트는 프로토타입 기반의 객체지향 언어다. 프로토타입 기반 객체지향 언어라는 말은 클래스가 필요 없는 객체지향 프로그래밍 언어라는 것이다. ES5에서는 클래스 없이도 생성자 함수와 프로토타입을 통해 상속을 구현했다.
ES6에서 도입된 클래스는 기존 프로토타입 기반 객체지향 프로그래밍보다 자바나 C#과 같은 클래스 기반 객체지향 프로그래밍 언어와 흡사한 객체 생성 메커니즘을 제공한다. 새로운 클래스 기반 객체지향 모델을 제공하지만 사실 클래스는 함수이다.
클래스와 생성자 함수의 다른 점
- 클래스는 new 연산자 없이 호출하면 에러 / 생성자 함수는 일반 함수로서 호출
- 클래스는 상속을 지원하는 extend 와 super 키워드를 제공
- 클래스는 호이스팅이 발생하지 않는 것처럼 동작 / 함수 선언문으로 정의된 생성자 함수는 함수 호이스팅이, 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅이 발생
- 클래스 내의 모든 코드에는 암묵적으로 strict mode가 지정되어 있다.
- 클래스의 constructor, 프로토타입 메서드, 정적 메서드는 [[Enumerable]]이 false로 열거 불가능하다.
25.2 클래스 정의
클래스는 class 키워드를 사용하여 정의한다. 일반적으로 파스칼 케이스를 사용한다.
클래스 몸체에는 0개 이상의 메서드만 정의할 수 있다.
몸체에서 정의할 수 있는 메서드는 constructor(생성자), 프로토타입 메서드, 정적 메서드 세 가지가 있다.
클래스 선언문의 예시는 아래와 같다.
class Person {
construnctor(name){ // 인스턴스 생성 및 초기화
this.name = name
}
sayHi(){ // 프로토타입 메서드
console.log(`my name is ${this.name}`)
}
static sayHello(){ // 정적 메서드
console.log('hello')
}
}
클래스 호이스팅
- 클래스는 함수로 평가된다. 클래스 선언문도 호이스팅이 발생한다. let, const로 선언한 변수처럼 호이스팅된다.
인스턴스 생성
- 클래스는 생성자 함수이며 new 연산자와 함께 호출되어 인스턴스를 생성한다.
25.5 메서드
몸체에서 정의할 수 있는 메서드는 constructor(생성자), 프로토타입 메서드, 정적 메서드 세 가지가 있다.
1) constructor
인스턴스를 생성하고 초기화하기 위한 메서드로, 이름 변경이 불가능하다. ES11에 따르면 인스턴스 프로퍼티는 반드시 constructor 내부에서 정의해야 한다. ( 현재 클래스 몸체에 프로퍼티를 정의할 수 있는 사양이 제안되어 있다고 한다.)
특징
- 클래스 내에 최대 한 개만 존재할 수 있다.
- 생략이 가능하다. 생략하면 클래스에 빈 counstrucor가 암묵적으로 정의되며 빈 객체를 생성한다.
- 별도의 반환문을 갖지 않아야 한다. 암묵적으로 this(인스턴스)를 반환하기 때문이다.
2)프로토타입 메서드
클래스 몸체에서 정의한 메서드는 기본적으로 프로토타입 메서드가 된다.
인스턴스의 프로토타입 체인의 일원이 된다.
인스턴스 프로퍼티를 참조할 수 있다. 인스턴스로 호출한다.
3)정적 메서드
인스턴스를 생성하지 않아도 호출할 수 있는 메서드이다.
인스턴스 프로퍼티를 참조할 수 없다.
정적 메서드는 인스턴스로 호출할 수 없다. 정적 메서드가 바인딩된 클래스는 인스턴스의 프로토타입 체인 상에 존재하지 않기 때문이다. 인스턴스로 클래스의 메서드를 상속받을 수 없다는 것이다.
그래서 애플리케이션 전역에서 사용할 유틸리티 함수를 전역 함수로 정의하지 않고 메서드로 구조화할 때 유용하다.
ex. ES6에서 빌트인 전역 함수인 isNaN, parseInt등을 Number의 정적 메서드로 추가했다. 정적 메서드는 빌트인 전역 함수보다 엄격하다.
25.6 클래스의 인스턴스 생성 과정
클래스는 다음과 같은 과정을 거쳐 인스턴스가 생성된다.
1) 인스턴스 생성과 this바인딩 : new 연산자와 함께 클래스를 호출하면 암묵적으로 빈 객체가 생성된다. 빈 객체(인스턴스)는 this에 바인딩된다.
2) 인스턴스 초기화 : constructor 내부 코드가 실행되며 this에 바인딩되어 있는 인스턴스를 초기화한다. 인수가 있다면 전달받은 초기값으로 초기화한다.
3) 인스턴스 반환 : 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
25.7 프로퍼티
1) 인스턴스 프로퍼티 : constructor 내부에서 정의한 프로퍼티
2) 접근자 프로퍼티 : 자체적으로는 값을 가지지 않고 다른 프로퍼티 값을 읽거나 저장한다. (setter, getter)
3) 클래스 필드 정의 제안 : 자바스크립트에도 인스턴스 프로퍼티를 클래스 필드(ex.자바)처럼 정의할 수 있는 표준 사양이 제안되어 있다. 이 제안으로 인스턴스 프로퍼티를 정의하는 방식은 두 가지가 되었다. 다만 인스턴스를 생성할떄 외부 초기값으로 클래스 필드를 초기화할 필요가 있다면 constructor 방식을 사용한다.
4) private 필드 정의 제한 : 자바스크립트는 private, public같은 접근제한자를 지원하지 않아 언제나 public이다. private필드를 정의할 수 있는 새로운 표준 사양이 제안되어 있다. private필드의 선두에는 #를 붙여주는 방법이다.
25.8 상속에 의한 클래스 확장
기존 클래스를 상속받아 새로운 클래스를 확장할 수 있다. 클래스는 상속 확장 문법이 기본적으로 제공된다.
상속은 아래와 같은 방법으로 할 수 있다.
class Animal{
constructor(age){
this.age = age
}
}
class Bird extend animal {
constructor(age, weight){
super()
this.weight = weight
}
fly(){ return 'fly'}
}
1) expend 키워드 : extend 키워드를 사용하여 상속받을 클래스를 정의할 수 있다.
서브클래스에서 constructor 를 생략하면 클래스에 super와 같은 construct가 암묵적으로 정의된다.
2) super 키워드 : 함수처럼 호출할 수도 있고, this와 같이 식별자처럼 참조할 수 있는 키워드이다.
- super 호출 : 수퍼클래스의 constructor를 호출한다.
서브클래스에서 constructor를 생략하지 않는 경우 서브클래스의 constructor에서는 super를 반드시 호출해야 한다.
서브클래스의 constructor에서 super를 호출하기 전까지는 this 를 참조할 수 없다.
super는 반드시 서브클래스의 constructor에서만 호출한다.
- super 참조
메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다. ( 프로토타입 메서드, 정적 메서드 둘 다 )
상속 클래스의 인스턴스 생성 과정
서브클래스는 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임한다. 서브클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용한다. 서브클래스 내부에 super 호출을 반드시 해야 하는 이유이다. super를 호출해야 인스턴스를 생성할 수 있기 때문이다. 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없는 이유가 이 때문이다.
서브 클래스의 super 호출 -> 수퍼클래스의 인스턴스 생성 -> 수퍼클래스 인스턴스 초기화 -> 서브클래스 constructor에서 this바인딩 -> 서브클래스 인스턴스 초기화 -> 인스턴스 반환 순서로 진행된다.
표준 빌트인 생성자 함수 확장
표준 빌트인 객체도 [[Construct]] 내부 메서드를 갖는 생성자 함수이므로 extend로 확장할 수 있다. 그러나 확장한 클래스의 인스턴스를 반환하지 않는다면 확장한 클래스의 메서드와 메서드체이닝이 불가능하다.
26장 ES6 함수의 추가 기능
ES6 이전의 모든 함수는 일반 함수로서 호출할 수 있는 것은 물론 생성자 함수로서 호출할 수 있다.
호출 방식에 제약이 없어서 ES6에서는 함수를 세 가지 종류로 구분했다.
일반 함수: 함수 선언문, 함수 표현식으로 정의한 함수 (ES6이전과 차이 없음)
메서드: constructor 없음, 프로토타입 없음, super 있음, arguments 있음
화살표 함수: constructor 없음, 프로토타입 없음, super 없음, arguments 없음
26.2 메서드
메서드 축약 표현으로 정의된 함수를 메서드라고 한다.
foo(){ return 0 }
인스턴스를 생성할 수 없는 non-constructor이다.
자신을 바인딩한 객체를 가리키는 내부 슬롯 [[HomeObject]]를 갖는다. 이 내부 슬롯을 사용하여 수퍼클래스의 메서드를 참조할 수 있다.
26.3 화살표 함수
function키워드 대신 화살표 => 를 이용해 간략하게 함수를 정의한다.
콜백 함수 내부에서 this가 전역 객체를 가리키는 문제를 해결하기 위한 대안으로 유용하다.
함수 표현식으로 정의해야 한다. (ex. const add = (x,y) => x+y )
인스턴스를 생성할 수 없는 non-construct이다.
중복된 매개변수 이름을 선언할 수 없다.
함수 자체의 this, arguemetns, super, new.target 바인딩을 갖지 않는다. (상위 스코프를 참조한다.)
화살표 함수의 this는 일반 함수의 this와 다르게 동작한다. 화살표 함수는 함수 자체의 this 바인딩을 갖지 않는다. 화살표 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조한다. 화살표 함수에 this가 없으니 일반적인 식별자처럼 스코프 체인을 통해 상위 스코프에서 this를 탐색하기 때문이다.
super, arguments도 마찬가지로 화살표 함수는 함수 자체로 갖지 않는다. 상위 스코프를 참조한다.
26.4 Rest 파라미터
매개변수 이름 앞에 세개의 점 ... 을 붙여서 정의한 매개변수를 의미한다. 함수에 전달된 인수들의 목록을 배열로 전달받는다.
function foo(...rest){console.log(rest)} // [1,2,3]
foo(1,2,3)
특징
- rest 파라미터는 마지막 파라미터여야 한다.
- 단 하나만 선언할 수 있다.
- 매개변수 개수를 나타내는 함수 객체의 length프로퍼티에 영향을 주지 않는다.
26.5 매개변수 기본값
다음과 같은 방법으로 매개변수 기본값을 지정할 수 있다. Rest파라미터에는 기본값을 설정할 수 없다.
function sum(x=0,y=0){ return x+y }
27장 배열
27.1 배열이란?
배열은 여러 개의 값을 순차적으로 나열한 자료구조다.
배열이 가지고 있는 값을 요소라고 부른다. 자바스크립트의 모든 값은 배열의 요소가 될 수 있다.
배열의 요소는 자신의 위치를 나타내는 0이상의 정수인 인덱스index를 갖는다.
요소에 접근할 때는 대괄호 표기법 someArray[0] 을 사용한다.
길이를 나타내는 length 프로퍼티를 갖는다.
for문으로 순차적으로 접근할 수 있다.
자바스크립트에 배열이라는 타입은 존재하지 않는다. 배열은 객체 타입이다.
배열은 객체지만 일반적인 객체와는 차이가 있다. 배열은 값의 순서와 length프로퍼티를 갖는다.
자바스크립트의 배열 특징
- 자바스크립트는 자료구조에서 말하는 밀집 배열(동일 크기의 메모리 공간이 연속적으로 나열된)과는 다르다.
- 자바스크립트의 배열은 요소를 위한 각각의 메모리 공간은 동일한 크기가 아니어도 되고, 연속적으로 이어져 있지 않을 수도 있다. 즉 자바스크립트의 배열은 일반적인 배열의 동작을 흉내 내는 특수한 객체다.
- 배열의 요소가 연속적이 아니고 일부가 비어 있는 희소 배열을 문법적으로 허용하지만 쓰지 않는 것이 좋다.
27.4 배열 생성
배열 리터럴 ex. let arr = [1,2,3] // [1,2,3]
Array 생성자 함수 ex. let arr2 = new Array(1,2,3) // [1,2,3] , ex2. let arr3 = new Array(2) // [empty*2]
Array.of ex. Array.of(1,2,3) // [1,2,3] ex2. Array.of(1) // [1]
Array.from : 유사 배열 객체,이터러블 객체를 배열로 변환 ex. Array.from({length:2}, 0:'a', 1:'b')
27.8 배열 메서드
배열 메서드는 원본 배열을 직접 변경하는 메서드와 원본 배열을 변경하지 않고 새 배열을 생성하는 메서드가 있다.
- Array.isArray : 전달된 인수가 배열이면 true, 아니면 false를 반환한다.
- Array.prototype.isdexOf : 인수로 전달된 요소를 원본 배열에서 검색하여 인덱스를 반환한다.
- Array.prototype.push : 인수로 전달받은 값을 원본 배열의 마지막 요소로 추가한다. 원본 배열을 변경한다.
- Array.prototype.pop : 원본 배열의 마지막 요소를 제거한다. 원본 배열을 변경한다.
- Array.prototype.unshift : 인수로 받은 모든 값을 원본 배열의 선두에 요소로 추가한다. 원본 배열을 변경한다.
- Array.prototype.shift : 원본 배열에서 첫번쨰 요소를 제거하고 제거한 요소를 반환하다. 원본 배열을 변경한다.
- Array.prototype.concat : 인수로 전달된 값을 원본 배열의 마지막 요소로 추가한 새 배열을 반환한다. 원본 배열 변경하지 않는다.
- Array.prototype.splice : 원본 배열 중간에 요소를 추가하거나 제거한다. 원본 배열을 변경한다. 매개변수는 (start, deletecount, items ) 이다.
- Array.prototype.slice : 인수로 받은 범위의 요소들을 복사하여 배열로 반환한다. 원본 배열 변경하지 않는다.
- Array.prototype.join : 원본 배열의 모든 요소를 구분자로 연걸하여 반환한다. 기본적으로는 콤마다.
- Array.prototype.reverse : 배열의 순서를 뒤집는다. 원본 배열을 변경한다.
- Array.prototype.fill : 인수로 받은 값을 배열의 처음부터 끝까지 요소로 채운다. 원본 배열을 변경한다.
- Array.prototype.includes : 배열 내에 특정 요소가 포함되었는지 true, false로 반환한다.
- Array.prototype.flat : 인수로 전달한 깊이만큼 배열을 평탄화한다.
배열 고차 함수
함수를 인수로 전달받거나 반환하는 함수를 말한다. 함수형 프로그래밍은 로직 내 조건문,반복문을 제거하여 복잡성을 해결하려 한다. 순수 함수를 통해 변수의 사용 및 부수 효과를 억제하고자 한다.
- Array.prototype.sort : 배열 요소를 정렬한다. 원본 배열을 변경한다. 인수로 정렬 순서를 정의하는 비교 함수를 전달 가능하며 기본적으로 오름차순이다.
- Array.prototype.forEach : 자신의 내부에서 반복문을 실행한다. 원본 배열을 변경하지 않으며 반환값은 언제나 undefined이다. break문을 사용할 수 없고 배열의 모든 요소를 순회하는 중간에는 중단할 수 없다.
- Array.prototype.map : 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 호출한다. 그리고 새 배열을 반환한다. 원본 배열 변경되지 않는다.
- Array.prototype.filter : 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 받은 콜백함수를 호출하고, 콜백함수가 true인 구성 요소로만 구성된 새 배열을 반환한다. 원본 배열 변경되지 않는다.
- Array.prototype.reduce : 자신을 호출한 배열의 모든 요소를 순회하며 인수로 받은 콜백함수를 실행하고, 하나의 결과값을 만들어 반환하다. 원본 배열 변경되지 않는다.
- Array.prototype.some : 자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백함수를 실행하고, 콜백 함수가 단 한번이라도 참이면 true를 , 아니라면 false를 반환한다. 빈 배열이면 false를 반환한다.
- Array.prototype.every : 자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백함수를 실행하고, 콜백 함수의 반환값이 모두 참이면 true, 하나라도 거짓이면 false를 반환한다. 빈 배열이면 true를 반환한다.
- Array.prototype.find : 자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백함수의 반환값이 true인 첫 번째 요소를 반환한다.
- Array.prototype.findIndex : 자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백함수의 반환값이 true인 첫 번째 요소의 인덱스를 반환한다. true인 요소가 없다면 -1을 반환한다.
주말에 올리려고 했는데 카카오 서버가 맛 가는 바람에 티스토리도 못 쓰게 되어 이제서야 올린다.
티스토리야 아프지 말자 ^_ㅠ
'JavaScript' 카테고리의 다른 글
모던 자바스크립트 Deep Dive ― (8) (1) | 2022.10.31 |
---|---|
모던 자바스크립트 Deep Dive ― (7) (0) | 2022.10.28 |
Intersection Observer API (0) | 2022.10.21 |
모던 자바스크립트 Deep Dive ― (5) (1) | 2022.10.05 |
모던 자바스크립트 Deep Dive ― (4) (0) | 2022.09.28 |