일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- schema first
- enable_if
- 스타트업
- mistel키보드
- uuid-ossp
- 부동소수점
- GraphQL
- 창업
- Cloud Spanner
- 42seoul
- 동료학습
- 엣지컴퓨팅
- c++
- 어셈블리
- 프라이빗클라우드
- raycasting
- adminbro
- 텍스트북
- 도커
- 어셈블리어
- 스플릿키보드
- SFINAE
- 파이썬
- 레이캐스팅
- 이노베이션아카데미
- 정렬
- 42서울
- 쿠버네티스
- psql extension
- 자료구조
- Today
- Total
written by yechoi
[Modern JavaScript Deep Dive] 11~12장: 원시 값과 객체의 비교, 함수 본문
[Modern JavaScript Deep Dive] 11~12장: 원시 값과 객체의 비교, 함수
yechoi 2023. 2. 7. 21:5711장: 원시 값과 객체의 비교
- 원시 값: 변경 불가능한 값, 변수에 실제 값 저장
- 객체 값: 변경 가능한 값, 참조 값 저장
- 값에 의한 전달: 원시 값을 갖는 변수를 다른 변수에 할당하면, 원시 값이 복사되어 전달
- 참조에 의한 전달: 객체를 가리키는 변수를 다른 변수에 할당하면, 참조 값이 복사되어 전달
원시값
- 불변성: 원시 값을 재할당하면 새로운 메모리 공간 확보 -> 재할당한 값 저장 -> 변수가 참조하던 메모리 공간 주소 변경
문자열과 불변성
원시 값을 저장하려면 먼저 확보해야하는 메모리 공간의 크기를 결정해야
ECMAScript 사양에는 문자열 타입(2바이트) 숫자타입(8바이트) 이외 원시 타입 규정X
- 문자열
- 1 개 문자는 2바이트 메모리 공간에 저장
- 원시 타입이므로 변경 불가능
- 유사 배열 객체이면서 이터러블해 각 문자에 접근 가능(index 접근, for문, length)
값에 의한 전달
새 변수 = 원래 변수
별개의 메모리 공간에 값이 저장되므로, 할당 이후 원래 변수 값을 바꿔도 새 변수 값에는 영향X
값에 의한 전달은 오해가 있을 수 있음. 변수에는 값이 아니라 메모리 주소가 전달되기 때문.
평가 방식
- 새로운 값 생성해 메모리 주소 전달 -> 할당 시점에 두 변수의 메모리 주소 다름
- 기존 변수의 메모리 주소 그대로 전달했다가, 할당이 이뤄졌을 때 비로소 재할당된 값 저장 -> 할당 시점에 두 변수의 메모리 주소 같음
객체
프로퍼티 개수 정해져 있지 않고 동적 추가삭제 가능 -> 확보할 메모리 크기 사전에 정해둘 수 없음
자바스크립트 객체 관리 방식
자바스크립트 객체는 프로프티 키를 인덱스로 사용하는 해시테이블이라고 생각할 수 있다.
자바, C++ 과 달리 클래스 없이도 객체 생성 가능하고 동적으로 프로퍼티와 메서드 추가 가능 -> 더 비효율적
-> 히든 클래스: 동적 탐색 대신 프로퍼티에 접근하는 방법. 성능 향상.
변경 가능한 값
객체를 할당한 변수를 참조하면, 메모리에 저장된 참조값으로 실제 객체에 접근
프로퍼티 값을 갱신하거나 동적으로 생성하면, 기존 객체가 저장된 메모리에서 변경한다
왜? 객체를 변경할 때마다 복사하면 비용이 많이 든다. 객체 크기는 제각각이고 크기가 매우 클수도 있어서.
단점! 여러 개의 식별자가 하나의 객체를 공유할 수도 있음
얕은 복사와 깊은 복사
얕은 복사: 한 단계까지만 복사
깊은 복사: 객체에 중첩돼있는 객체도 복사
// 객체 변수 c1 = o라고 할 때
// 얕은 복사
c1 === o // false
c1.x === o.x // true
// 깊은 복사
c1 === o // false
c1.x === o.x // false
=== 일치 비교 연산자는 객체 할당 변수는 참조 값을 비교하고, 원시 값 할당 변수는 원시값을 비교한다.
참조에 의한 전달
원본과 사본의 메모리 주소는 다르지만, 값이 동일한 참조값 -> 두 개의 식별자가 하나의 객체를 공유
원본/사본 중 어느 한쪽에서 객체 변경 -> 다른 쪽에도 영향을 줌
자바스크립트에는 사실 값에 의한 전달만 존재한다
값에 의한 전달이나 참조에 의한 전달은 식별자가 기억하는 메모리 공간에 저장돼 있는 값을 전달하는 것 동일
다만 식별자가 기억하는 메모리 공간이 원시값, 참조 값의 차이만 있음
그러나 편의상 원시 값을 전달하면 '값에 의한 전달', 참조 값을 전달하면 '참조에 의한 전달'이라고 하자
12장: 함수
함수 리터럴
자바스크립트의 함수는 객체 타입의 값
function 키워드, 함수 이름, 매개 변수 목록, 함수 몸체로 구성
함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다. (외부에서 이름으로 호출 불가)
(function bar() {console.log('bar')};); // 피연산자는 함수 리터럴 표현식
bar(); // ReferenceError bar is not defined // 함수 리터럴에서 이름은 외부에서 호출 불가
함수 정의
변수 선언과 함수 정의(158p) 의미?
function add(x,y) {
return x + y;
}
console.log(add);
/*
출력 결과
ƒ add(x,y) {
return x + y;
}
*/
함수 선언문
함수 선언문은 표현식이 아닌 문이다 -> 변수에 할당할 수 없다
함수 이름을 생략할 수 없다는 점을 제외하면 함수 리터럴과 형태가 동일
function foo() {
console.log('foo');
}
foo(); // 식별자가 없어도 자바스크립트 엔진이 암묵적으로 식별자 생성해 호출 가능
기명 함수 리터럴은 함수 선언문 또는 함수 리터럴 표현식으로 해석될 수 있음
기명 함수 리터럴
- 단독으로 사용(피연산자 X) -> 함수 선언문으로 해석(호출 가능)
- 값으로 평가되는 문맥(변수에 할당 or 피연산자) -> 함수 표현식으로 해석(호출 불가)
함수는 함수 이름으로 호출 X, 함수 객체를 가리키는 식별자로 호출 O
const add = function add(x,y) {
return x + y;
}
console.log(add(2, 5)); // 함수 이름이 아닌 식별자를 호출해 7
함수 표현식
자바스크립트의 함수는 값의 성질을 갖는 객체인 일급 객체
함수 표현식 = 함수 리터럴로 생성한 함수 객체를 변수에 할당하는 함수 정의 방식
함수 선언문은 표현식이 아닌 문 이고 함수 표현식은 표현식인 문이다.
// 바로 이런 형식!
const add = function (x, y){
return x + y;
}
const add = function foo(x,y) {
return x + y;
}
console.log(foo(2, 5)); // ReferenceError, 기명 함수 리터럴이 값으로 평가되면 함수 표현식이라 호출 불가
함수 리터럴, 함수 선언문, 함수 표현식의 차이?
함수 리터럴은 함수를 표현하는 방식
함수 선언문은 함수에 식별자가 붙어있어 호출 가능한 함수
함수 표현식은 어떤 변수에 할당되는 함수 리터럴
함수 생성 시점과 함수 호이스팅
함수 선언문으로 정의한 함수, 함수 선언문 이전에 호출 O
- 모든 선언문이 그렇듯, 런타임 이전에 먼저 실행
- 함수 이름과 동일한 식별자를 암묵적으로 생성하고 함수 객체 할당
- 호이스팅: 함수 선언문이 코드 선두로 끌어 올려진 것 처럼 동작하는 JS 고유 특징
- 변수 호이스팅과의 차이점: 변수 선언문에서 변수는 undefined로 초기화되고, 함수 선언문은 함수 객체로 초기화
함수 표현식으로 정의한 함수, 함수 표현식 이전에 호출 X
- 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 것
- 런타임 이전에 변수는 undefined로 초기화됨
- 할당문이 실행되는 시점(런타임)에 함수 객체가 됨
- 변수 호이스팅이 되는 것!!!
함수 선언문 대신 함수 표현식을 사용하자.
함수 호이스팅은 함수 호출 전 함수를 선언해야한다는 당연한 규칙을 무시하니까!
Function 생성자 함수
var add = new Function('x', 'y', 'return x + y');
console.log(add(2, 5)); // 7
이렇게 생겼는데, 일반적이지 않고 바람직하지 않는 방법.
클로저를 생성하지 않는 등 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작하니까.
화살표 함수
const add = (x, y) => x + y;
console.log(add(2, 5)); // 7
표현과 내부동작 모두 간략화 됨(자세한 내용 26.3절)
- 화살표 함수는 생성자 함수로 사용 X
- 기존 함수와 this 바인딩 방식이 다름
- prototype 프로퍼티가 없음
- arguments 객체를 생성하지 않음
함수 호출
함수를 실행하기 위해 필요한 값을 함수 외부에서 내부로 전달할 필요가 있는 경우 매개변수(parameter, 인자)를 통해 인수(argument)를 전달한다.
// 여기서 x, y는 매개변수
function add(x, y) {
return x + y;
}
// 여기서 2, 5는 인수
let result = add(2, 5);
매개변수와 인수
- 매개변수
- 함수를 정의할 때 선언, 함수 몸체 내부에서 변수와 동일하게 취급(undefined로 초기화 -> 인수 할당)
- 스코프는 함수 내부(함수 몸체 내부에서만 참조)
- 인수
- 인수는 값으로 평가될 수 있는 표현식이어야 한다. 인수는 함수를 호출할 때 지정하며, 개수와 타입에 제한이 없다.
함수는 매개변수의 개수와 인수의 개수가 일치하는지 체크X
- 인수가 할당되지 않으면 undefined
- 매개변수가 인수보다 많으면 초과 인수 무시(사실은 암묵적으로 arguments 객체의 프로퍼티로 보관됨)
- c.f. arguments 객체는 가변 인자 함수를 구현할 때 유용함
자바스크립트는 동적 타입 언어, 함수 정의할 때 적절한 인수가 전달됐는지 확인해야
- 함수 내부에서 조건문으로 typeof 사용해 타입 확인(부적절한 호출을 런타임에 알 수 있다는 게 단점)
- arguments 객체를 통해 인수 개수 확인
- 단축평가로 매개변수에 기본값 할당 ex.
a = a || 0;
- ES6 도입 매개변수 기본값을 사용하면 인수 체크와 초기화 간소화 가능 ex.
function add(a = 0, b = 0, c = 0) {...}
이상적인 함수는 한가지 일만 해야하며 가급적 작게 만들어야 한다. 매개변수는 최대 3개 이상 넘지 않을 것.
객체를 인수로 사용하는 경우 프로퍼티 키만 정확히 지정하면 순서 상관 없음. but 함수 내부에서 객체 변경하면 함수 외부 객체가 변경되는 부수 효과 발생.
반환문
- 함수 호출은 표현식. 함수 호출 표현식은 return 키워드가 반환한 표현식의 평가 결과, 반환값으로 평가됨.
- return 뒤에 반환값으로 사용할 표현식 지정 안한다? 그럼 undefined.
- 반환문 생략한다? 암묵적으로 undefined 반환.
- return 에 세미콜론이 없고 다음 표현식에 줄바꿈이 있으면, 줄이 바뀌고 세미콜론 추가.
- 반환문은 함수 본체 내부에서 사용 가능. 전역에서 사용한다? SyntaxError: Illegal return statement
ex)<script>return;</script>
- Node.js 는 파일별로 독립적인 스코프라, 파일 가장 바깥 영역에 반환문을 사용해도 에러 X
어떤 이야기인지 이해 못했다
참조에 의한 전달과 외부 상태의 변경
원시 타입 인수
- 값 자체가 복사되어 매개변수에 전달 -> 함수 몸체에서 그 값을 변경해도 원본 훼손X (부수효과 X)
객체 타입 임수
- 참조 값이 복사되어 매개 변수에 전달 -> 함수 몸체에서 객체 변경하면 원본 훼손O (부수효과 O)
상태 변화 추척 어려움, 코드 복잡성 증가, 가독성 해침
옵저버 패턴 등으로 객체 참조 공유하는 모든 이들에게 변경 통지하고 대응해야
- 해결 방법: 불변 객체로 만들기. 객체 복사 비용 들여서 깊은 복사 -> 새로운 객체 생성
순수함수: 외부 상태를 변경하지 않고 외부 상태에 의존하지도 않는 함수
다양한 함수의 형태
- 즉시 실행 함수: 단한번만 호출되며 다시 호출X, 반드시 그룹 연산자 () 로 감싸야서 내부 함수를 피연산자로 만들어 함수 리터럴로 평가.
(function (){})()
,(function (){}())
의 형태
예제 12-41 세, 네 번째 예제에서 왜 함수 리터럴로 평가되는 걸까?
-
- 즉시 실행 함수는 언제 사용할까? https://jongminfire.dev/java-script-즉시실행함수-iife
- 재귀 함수: 함수 이름은 함수 몸체 내부에서만 유효하니, 자기 자신 호출 가능. 스택오버플로우 날 수 있어 탈출 조건 필요. 반복문을 쓰는 것 보다 이해하기 쉬울 때만 사용해야.
- 중첩 함수: 일반적으로 외부 함수를 돕는 헬퍼 함수의 역할.
- 콜백 함수: 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수.
- 고차 함수: 함수의 외부에서 콜백 함수를 전달받은 함수
- 고차 함수는 콜백 함수를 자신의 일부분으로 합성한다.
- 고차 함수는 콜백 함수의 호출 시점을 결정해 호출한다.
- 고차 함수는 필요에 따라 콜백 함수에 인수를 전달할 수 있다.
- 콜백 함수가 호출될 때마다 함수 객체가 생성되므로, 콜백 함수가 자주 호출된다면 함수 외부에서 콜백 함수를 정의한 후 함수 참조를 고차 함수에 정의하는 편이 효율적.
- 비동기 처리, 배열 고차 함수에서 사용.
- 고차 함수: 함수의 외부에서 콜백 함수를 전달받은 함수
- 순수 함수: 외부 상태에 의존 변경 않는, 부수 효과 X 함수
- 동일한 인수가 전달되면 언제나 동일한 값을 반환하는 함수
- 최소 하나 이상의 인수를 전달 받음(전달 받지 않으면 상수나 마찬가짐)
- 비순수 함수: 외부 상태에 의존 변경하는, 부수 효과 O 함수 -> 상태 변화를 추적하기 어려우니 지양해야.
- 함수형 프로그래밍: 순수 함수와 보조함수의 조합을 통해 부수 효과 최소화해 불변성 지향.