🔰객체 리터럴
객체란?
객체는 프로퍼티(객체의 상태를 나타내는 값)와 메서드(프로퍼티를 참조하고 조작하는 동작)로 구성된 집합체다.
객체지향 프로그래밍은 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임이다.
자바스크립트는 객체 기반의 프로그래밍 언어이며, 원시 값을 제외한 나머지 값은 모두 객체다.
객체 리터럴에 의한 객체 생성
객체 리터럴, Object 생성자 함수, 생성자 함수, Object.create 메서드, ES6 클래스의 다양한 객체 생성 방법을 지원한다.
객체 리터럴은 변수에 할당되는 시점에 자바스크립트 엔진이 해석해 객체를 생성한다. 자바스크립트의 유연함을 대표하는 객체 생성 방식이다.
객체 리터럴 외엔 모두 함수를 사용해 객체를 생성한다.
프로퍼티
객체는 프로퍼티의 집합이며, 프로퍼티는 키와 값(value)으로 구성된다.
키는 모든 문자열 또는 심벌 값, 값은 모든 값이 될 수 있다. 키에 다른 타입을 사용하면 암묵적 타입 변환으로 문자열이 된다.
키는 프로퍼티 값에 접근할 수 있는 식별자 역할을 한다. 식별자 네이밍 규칙을 따르지 않는 키는 반드시 따옴표를 사용해야 자바스크립트 엔진이 적절히 해석한다.
프로퍼티 키를 중복 선언하면 나중에 선언한 프로퍼티가 먼저 선언한 프로퍼티를 덮어쓴다. 에러가 발생하지 않는다.
메서드
프로퍼티 값이 함수일 경우 메서드라 부른다. 즉, 객체에 묶여 있는 함수를 의미한다.
프로퍼티 접근
dot notation, bracket notation으로 접근할 수 있다.
객체에 존재하지 않는 프로퍼티에 접근하면 undefined를 반환한다. ReferenceError가 발생하지 않는다.
프로퍼티 값 갱신
이미 존재하는 프로퍼티에 값을 할당하면 갱신된다.
프로퍼티 동적 생성
존재하지 않는 프로퍼티에 값을 할당하면 프로퍼티가 동적으로 생성되어 추가되고 값이 할당된다.
프로퍼티 삭제
delete 연산자는 객체의 프로퍼티를 삭제한다. 존재하지 않는 프로퍼티를 삭제하면 아무런 에러 없이 무시된다.
ES6에서 추가된 객체 리터럴의 확장 기능
프로퍼티 값으로 변수를 사용하는 경우, 변수 이름과 프로퍼티 키가 동일한 이름일 때 프로퍼티 키를 생략할 수 있다. 프로퍼티 키는 변수 이름으로 자동 생성된다. 이를 프로퍼티 축약 표현(shorthand property)이라 한다.
계산된 프로퍼티 이름(computed property name)을 이용하면 대괄호로 프로퍼티 키를 동적으로 생성할 수 있다.
메서드를 정의할 때 function 키워드를 생략한 축약 표현을 사용할 수 있다. 메서드 축약 표현으로 정의한 메서드는 프로퍼티에 할당한 함수와 다르게 동작한다.
🔰원시 값과 객체의 비교
원시 값 | 객체 |
immutable value | mutable value |
원시 값을 변수에 할당하면 변수에는 실제 값이 저장된 메모리 주소가 저장된다. | 객체를 변수에 할당하면 변수에는 참조 값이 저장된 메모리 주소가 저장된다. |
원시 값이 할당된 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달된다(pass by value). | 객체가 할당된 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다(pass by reference - 엄밀히는 좀 다름). |
원시 값
한번 생성된 원시 값은 read only 값으로서 변경 불가능하다.
🍎 상수와 불변값
상수는 재할당이 금지된 변수를 의미한다.
변경 불가능하다는 것은 변수가 아니라 값에 대한 것이다. 원시 값은 변경 불가능하다는 것은 원시 값 자체를 변경할 수 없다는 것이지, 원시 값이 할당된 변수에 재할당할 수 없다는 의미가 아니다.
변수에 새로운 원시 값을 재할당하면 이전의 원시 값을 변경하는 것이 아니라, 새로운 메모리 공간을 확보하고 재할당한 값을 저장한 후 변수가 참조하던 메모리 공간의 주소를 변경한다.
불변값이 할당된 변수는 재할당 이외에 변수 값을 변경할 수 있는 방법이 없다. 상태 변경 추적이 쉬우므로 불변값은 데이터의 신뢰성을 보장한다.
자바스크립트는 다른 언어와 달리 개발자의 편의를 위해 원시 타입인 문자열을 제공한다. 문자열 또한 변경 불가능한 값이다. 문자열은 유사 배열 객체이면서 이터러블이므로 배열과 유사하게 각 문자에 접근할 수 있다. 다만, 이미 생성된 문자열의 일부 문자를 변경해도 반영되지 않는다(read only value).
🍎 유사 배열 객체
마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고, length 프로퍼티를 갖는 객체를 말한다.
문자열은 원시 값이지만, 원시 값을 객체처럼 사용하면 원시 값을 감싸는 래퍼 객체로 자동 변환되므로 가능하다.
원시 값이 할당된 변수를 다른 변수에 할당하면 원시 값이 복사되어 전달되고, 이를 값에 의한 전달이라 한다. 기존 변수와 새로운 변수에 할당된 값은 서로 다른 메모리 공간에 저장된다. 즉, 기존 변수의 값을 변경해도 새로운 변수의 값에는 어떠한 side effect도 없다.
객체
원시 값과 달리, 확보할 메모리 공간의 크기를 사전에 정할 수 없다. 또한 객체는 크기가 매우 클 수 있고, 객체를 생성하고 프로퍼티에 접근하는 것도 원시 값에 비해 비용이 많이 든다.
객체를 변경할 때마다 원시 값처럼 동작한다면 신뢰성은 확보할 수 있지만 복사해서 생성하는 비용이 많이 든다. 메모리의 효율적 소비가 어렵고 성능이 나빠진다.
이러한 문제로 객체는 변경 가능한 값으로 설계되었다. 객체를 할당한 변수는 재할당 없이 객체를 직접 변경(프로퍼티 추가, 갱신, 삭제)할 수 있다. 변경 가능하기 때문에 원시 값과는 달리 여러 개의 식별자가 하나의 객체를 공유할 수 있다.
🍎 자바스크립트 객체 관리 방식
자바스크립트 객체는 프로퍼티 키를 인덱스로 사용하는 해시 테이블(hash table, associative array, map, dictionary, lookup table)과 유사하다. 대부분의 자바스크립트 엔진은 일반적인 해시 테이블보다 나은 방법으로 객체를 구현한다.
자바스크립트는 클래스 없이 객체를 생성할 수 있고, 객체 생성 후에도 동적으로 추가 및 삭제가 가능하므로 사용성은 좋지만 성능 면에서는 일반적으로 클래스 기반 객체지향 프로그래밍 언어보다 비효율적이다.
이런 단점때문에 V8 자바스크립트 엔진에서는 프로퍼티 접근을 위해 동적 탐색(dynamic lookup)대신 히든 클래스(hidden class) 방식을 사용한다.
객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달이라 한다. 참조 값은 생성된 객체가 저장된 메모리 공간의 주소다.
어느 한쪽 변수에서 객체를 변경하면 변경된 값을 공유하는 side-effect가 발생한다.
🔰 함수
함수란?
수학의 함수는 입력(input)을 받아 출력(output)하는 과정을 정의한 것이다.
프로그래밍 언어의 함수는 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다. 입력을 전달받는 변수를 매개변수(parameter), 입력을 인수(argument), 출력을 반환값(return value)라 한다.
자바스크립트 핵심 개념인 스코프, 실행 컨텍스트, 클로저, 생성자 함수에 의한 객체 생성, 메서드, this, 프로토타입, 모듈화 등과 깊은 관련이 있다.
자바스크립트에서 함수는 값이며, 식별자인 함수명을 사용한다. 자바스크립트 함수는 함수 정의(function definition)를 통해 생성한다. 인수를 매개변수를 통해 함수에 전달하고 함수의 실행을 지시하는 것을 함수 호출(function call/invoke)이라 한다. 함수를 호출하면 반환값이 반환된다.
함수를 사용하는 이유
함수는 실행 시점을 개발자가 결정할 수 있고, 몇 번이든 재사용이 가능하다는 점에서 유용하다.
함수는 유지보수의 편의성을 높이고 실수를 줄여 코드의 신뢰성을 높인다.
적절한 함수명은 코드의 가독성을 향상시킨다.
함수 리터럴
함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성된다.
함수 이름 | 함수 몸체 내에서만 참조할 수 있는 식별자다. 생략할 수 있다. 이름이 있는 함수를 기명 함수(named function), 없는 함수를 익명 함수(anonymous function)이라 한다. |
매개변수 목록 | 매개변수를 통해 인수를 함수 외부에서 내부로 전달한다. 함수를 호출할 때 지정한 인수 순서대로 할당된다. 매개변수 목록은 순서에 의미가 있다. 매개변수는 함수 몸체 내에서 변수와 동일하게 취급된다. 함수가 호출되면 일반 변수와 마찬가지로 호이스팅을 거친다. 함수 몸체 내부에서만 참조할 수 있고 외부에서는 참조할 수 없다. 즉, 스코프가 함수 내부다. |
함수 몸체 | 함수 호출에 의해 실행되는 실행 단위인 코드 블록이다. |
자바스크립트 함수는 객체지만, 호출할 수 있다는 점에서 일반 객체와 다르다. 또한 일반 객체에는 없는 함수 객체만의 고유한 프로퍼티가 있다.
함수 정의
함수 호출 전에 인수를 전달받을 매개변수와 실행할 문들, 그리고 반환값을 지정하는 것을 말한다. 정의된 함수는 자바스크립트 엔진에 의해 평가되어 함수 객체가 된다.
🍎 변수 선언과 함수 정의
변수는 선언(declaration)이고 함수는 정의(definition)이라 표현한다.
함수 선언문이 평가되면 식별자가 암묵적으로 생성되고 함수 객체가 할당되므로 '정의'다. C에서 선언과 정의는 '실제로 메모리 주소를 할당하는가'로 구분한다. 단순히 컴파일러에게 식별자의 존재를 알리는 것은 선언이고, 실제로 컴파일러가 변수를 생성해서 식별자와 메모리 주소가 연결되면 정의로 구분한다. 자바스크립트에서 var 변수 선언은 암묵적으로 할당까지 이뤄지므로 선언과 정의의 구분이 모호하다.
함수 선언문, 함수 표현식, Function 생성자 함수, 화살표 함수로 정의할 수 있다.
함수 선언문 | 함수 리터럴과 형태가 동일하나, 함수 선언문은 함수 이름을 생략할 수 없다. 또한, 함수 선언문은 표현식이 아닌 문이다. 자바스크립트 엔진이 함수 선언문을 해석해 함수 객체를 생성할 때, 암묵적으로 함수 이름과 동일한 이름의 식별자를 생성하고 그 식별자에 함수 객체를 할당한다. 함수 이름은 함수 몸체 내에서만 사용할 수 있기 때문이다. 함수 선언문은 런타임 전에 이미 함수 객체 생성과 식별자 할당이 일어난다(함수 호이스팅). 함수 호이스팅이란, 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 것을 말한다. |
함수 표현식 | 자바스크립트의 함수는 값의 성질을 갖는 객체이므로 일급 객체다. 함수 리터럴로 생성한 함수 객체를 변수에 할당하는 함수 정의 방식을 함수 표현식이라 한다. 함수명을 생략하는 것이 일반적이다. 표현식인 문이다. 함수 표현식은 함수 호이스팅이 발생하지 않고, 변수 호이스팅이 발생한다. 즉, 할당문이 실행되는 시점(런타임)에 평가되어 함수 객체가 된다. |
Function 생성자 함수 | 자바스크립트 빌트인 함수인 Function 생성자 함수에 매개변수 목록과 함수 몸체를 문자열로 전달하면서 new 연산자와 함께 호출하면 함수 객체를 생성해서 반환한다. new 연산자 없이 호출해도 결과는 동일하다. Function 생성자 함수로 생성한 함수는 클로저를 생성하지 않는 등, 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작한다. 권장하지 않는 방법이다. |
화살표 함수(ES6) | function 키워드 대신 화살표 => 를 사용해 간략한 방법으로 함수를 선언한다. 항상 익명 함수다. 내부 동작도 축소된 형태다. 생성자 함수로 사용할 수 없고, this 바인딩 방식이 다르고, prototype 프로퍼티가 없으며 arguments 객체를 생성하지 않는다. |
자바스크립트 엔진은 중의적인 코드를 코드의 문맥에 따라 해석한다. 함수 선언문과 함수 표현식도 마찬가지다.
함수는 함수 이름으로 호출하는 것이 아니라, 함수 객체를 가리키는 식별자로 호출한다.
함수 호출
함수를 가리키는 식별자와 함수 호출 연산자'()'로 호출한다.
함수를 호출하면 현재의 실행 흐름을 중단하고 호출된 함수로 실행 흐름을 옮긴다. 매개변수에 인수가 순서대로 할당되고 함수 몸체의 문들이 실행된다.
매개변수보다 인수의 개수가 적으면 인수가 할당되지 않은 매개변수의 값은 undefined가 되고, 많으면 초과된 인수는 무시된다. 다만 버려지는 것이 아닌 arguments 객체의 프로퍼티로 보관된다. 에러는 발생하지 않는다.
자바스크립트 함수는 매개변수와 인수의 개수가 일치하는지 확인하지 않고, 자바스크립트가 동적 타입 언어이므로 함수를 정의할 때 적절한 인수가 전달되었는지 확인할 필요가 있다. 타입 확인을 한다고 해도 자바스크립트 에러는 런타임에 발생하므로 이를 방지하기 위해 타입스크립트와 같은 정적 타입 시스템을 사용하여 컴파일 시점에 에러를 방지하는 것도 방법이다.
매개변수는 순서에 의미가 있으므로 개수가 늘어나면 유지보수성이 나빠진다. 이상적인 매개변수 개수는 0개이고, 적을수록 좋다. 3개 이상의 매개변수가 필요하다면 객체를 인수로 전달하는 것이 좋다. 다만, 객체를 함수 내부에서 수정하면 side effect가 발생한다.
return 키워드와 표현식으로 이뤄진 반환문을 사용해 실행 결과를 함수 외부로 반환할 수 있다. 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나간다. 반환문을 생략하거나 return 키워드 뒤에 표현식을 명시하지 않으면 undefined가 반환된다.
참조에 의한 전달과 외부 상태의 변경
매개변수 또한 타입에 따라 값에 의한 전달, 참조에 의한 전달 방식을 그대로 따른다.
객체 타입 인수는 원본이 훼손될 가능성이 있으므로, 객체를 불변 객체로 만들거나 deep copy르 통해 새로운 객체를 생성하는 것이 좋다.
다양한 함수의 형태
즉시 실행 함수(IIFE)는 함수 정의와 동시에 즉시 호출되는 함수다. 단 한 번만 호출되고 다시 호출할 수 없다.
재귀 함수는 자기 자신을 호출하는, 즉 재귀 호출을 수행하는 함수다. 재귀 함수에 탈출 조건을 만들지 않으면 함수가 무한 호출되어 스택 오버플로우 에러가 발생한다. 대부분의 재귀 함수는 반복문으로 구현 가능하다.
중첩 함수 또는 내부 함수는 함수 내부에 정의된 함수를 의미한다. 중첩 함수를 포함하는 함수를 외부 함수라 한다. 중첩 함수는 외부 함수 내부에서만 호출할 수 있고, 일반적으로 외부 함수를 돕는 헬퍼 함수 역할을 한다.
콜백 함수는 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수다. 비동기 처리뿐 아니라, 배열 고차 함수에도 사용된다.
고차 함수는 매개변수를 통해 함수의 외부에서 함수를 전달받거나 반환값으로 함수를 반환하는 함수다. 고차함수는 콜백 함수의 호출 시점을 결정해서 호출한다.
콜백 함수로 함수 리터럴을 전달한다면, 고차 함수가 호출될 때마다 함수 리터럴이 평가되어 함수 객체가 생성되므로, 해당 콜백 함수를 다른 곳에서도 호출할 필요가 있거나 고차 함수가 자주 호출된다면 외부에 콜백 함수를 정의하는 것이 좋다.
모든 콜백 함수가 고차 함수에 의해 호출되는 것은 아니다. setTimeout의 콜백 함수는 setTimeout 함수가 호출하지 않는다.
순수 함수는 어떤 외부 상태에 의존하지 않고 변경하지 않는, side effect가 없는 함수다. 동일한 인수가 전달되면 언제나 동일한 값을 반환해야 한다.
비순수 함수는 이와 반대 개념이다. 외부 상태(전역 변수, 서버 데이터, 파일, 콘솔, DOM)에 따라, 또는 내부 상태에만 의존한다 해도 내부 상태가 호출될 때마다 반환값이 달라진다면 순수 함수가 아니다.
함수형 프로그래밍은 순수 함수와 보조 함수의 조합으로 side effect를 최소화해서 불변성을 지향하는 프로그래밍 패러다임이다. 로직 내 조건문과 반복문을 제거해서 복잡성을 해결하고, 변수 사용을 억제하거나 생명주기를 최소솨해서 상태 변경을 피해 오류를 최소화하는 것을 목표로 한다.
'독서 > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
모던 자바스크립트 Deep Dive (16장 ~ 18장) (2) | 2023.03.29 |
---|---|
모던 자바스크립트 Deep Dive (13장 ~ 15장) (0) | 2023.03.28 |
모던 자바스크립트 Deep Dive (7장 ~ 9장) (0) | 2023.03.15 |
모던 자바스크립트 Deep Dive (4장 ~ 6장) (0) | 2023.03.10 |
모던 자바스크립트 Deep Dive (1장 ~ 3장) (0) | 2023.03.09 |