10장 객체 리터럴
10.1 객체란? Object
자바스크립트는 객체기반의 프로그래밍 언어다.
자바스크립트를 구성하는 거의 모든 것이 객체다. (함수, 배열, 정규 표현식 ...)
원시 타입 | 객체 타입 |
단 하나의 값만 나타냄. | 다양한 타입의 값을 하나의 단위로 구성한 복합적인 자료구조다. |
원시 타입의 값(원시 값)은 변경 불가능한 값 immutable value |
객체는 변경 가능한 값 mutable value |
객체는 0개 이상의 프로퍼티로 구성된 집합이다.
프로퍼티 구성요소
- 키 key
- 값 value
여기서 특별하게, 값이 함수인 경우 프로퍼티 대신 메서드 method 라고 부른다.
- 프로퍼티 : 객체의 상태를 나타내는 값 (data)
- 메서드 : 프로퍼티(상태 데이터)를 참조하고 조작할 수 있는 동작(behavior)
객체지향 프로그래밍 : 객체의 집합으로 프로그램을 표현하는 프로그래밍 패러다임
자바스크립트는 프로토타입 기반 객체 지향 언어
따라서 다양한 객체 생성 방법을 지원한다.
- 객체 리터럴
- Object 생성자 함수
- 생성자 함수
- Object.create 메서드
- 클래스(ES6)
10.2 객체 리터럴에 의한 객체 생성
*리터럴: 사람이 이해할 수 있는 문자, 또는 약속된 기호로 값을 생성하는 표기법
객체 리터럴은 중괄호 {...} 내에 0개 이상의 프로퍼티를 정의한다.
변수에 할당되는 시점에 자바스크립트 엔진은 객체 리터럴을 해석해 객체를 생성함.
var person = {
name: "Lee",
sayHello: function () {
console.log(`hello! my name is ${this.name}`);
},
};
console.log(typeof person); // object
console.log(person); // { name: 'Lee', sayHello: [Function: sayHello] }
// 만약 중괄호 내에 프로퍼티를 정의하지 않으면 빈 객체가 생성된다.
var empty = {}; // 빈 객체
객체 리터럴의 중괄호는 코드 블록을 의미하지 않는다.
코드 블록의 중괄호 뒤에 세미콜론을 붙이지 않지만, 객체 리터럴은 값으로 평가되는 표현식이기 때문에 세미콜론을 붙인다.
객체 리터럴에 프로퍼티를 포함시켜 객체를 생성함과 동시에 프로퍼티를 만들 수 있다.
객체 생성 후 프로퍼티를 동적으로 추가할 수 있다.
10.3 프로퍼티
객체는 프로퍼티의 집합이며, 프로퍼티는 키와 값으로 구성된다.
var person = {
name: "Lee", // 키: name, 값: 'Lee'
age: 20, // 키: age, 값: 20
};
프로퍼티 키와 값으로 사용할 수 있는 값은 다음과 같다.
- 프로퍼티 키: 빈 문자열을 포함하는 모든 문자열 또는 심벌 값
- 프로퍼티 값: 자바스크립트에서 사용할 수 있는 모든 값 (숫자, 문자열, boolean, null, undefined, symbol, 객체)
프로퍼티 키는 값에 접근할 수 있는 이름으로서 식별자 역할을 한다.
이때 키는 문자열이므로 따옴표로 묶어야한다.
하지만 식별자 네이밍 규칙에 준수하는 이름은 따옴표를 생략할 수 있다.
var person = {
firstName: "희", // 식별자 네이밍 규칙을 준수한 키
"last-name": "강", // 식별자 네이밍 규칙을 준수하지 않은 키 => 따옴표 작성
};
문자열, 문자열로 평가할 수 있는 표현식을 사용해 프로퍼티 키를 동적으로 생성할 수 있다.
이 경우 프로퍼티 키로 사용할 표현식을 대괄호 [...]로 묶어야한다.
var obj = {};
var key = "hello";
obj[key] = "world"; // ES5 프로퍼티 키 동적 생성
console.log(obj); // { hello: 'world' }
프로퍼티 키에 문자열이나 심벌 값 외의 값을 사용하면 암묵적 타입 변환을 통해 문자열이 된다.
프로퍼티 키로 숫자 리터럴을 사용하면 따옴표를 붙이지 않아도 되지만, 내부적으로 문자열로 변환된다.
var foo = {
0: 1, // 0 => "0"
1: 2, // 1 => "1"
2: 3, // 2 => "2"
};
var, function과 같은 예약어로 프로퍼티 키로 사용해도 에러가 발생하지 않는다.
이미 존재하는 프로퍼티 키를 중복 선언하면 나중에 선언한 프로퍼티가 먼저 선언한 프로퍼티 키를 덮어쓴다. 에러가 발생하지 않는다.
10.4 메서드
- 프로퍼티 값 : 자바스크립트에서 사용할 수 있는 모든 값
함수는 객체(일급 객체)다. 따라서 함수는 값으로 취급할 수 있어서 프로퍼티 값으로 사용할 수 있다.
프로퍼티 값이 함수일 경우, 해당 함수를 메서드라고 부른다.
즉 메서드는 객체에 묶여있는 함수를 의미한다.
var circle = {
radius: 5, // 프로퍼티
// 메서드
getDiameter: function () {
return 2 * this.radius; // this는 circle을 가리킴
},
};
console.log(circle.getDiameter());
this는 객체 자신을 가리키는 참조 변수로, 22장에서...
10.5 프로퍼티 접근
프로퍼티에 접근하는 방법은 다음 두 가지다.
마침표 표기법 dot notation | 대괄호 표기법 bracket notation |
마침표 프로퍼티 접근자(.)를 사용. 키의 경우에 따옴표를 사용한 문자열 안된다! object.key ex) person.name |
대괄호 프로퍼티 접근자([...])를 사용. 키의 경우엔 무조건 따옴표를 사용한 문자열 또는 문자열로 평가될 수 있는 표현식이여야함! object[key] ex) person['name'] |
프로퍼티 키가 식별자 네이밍 규칙을 준수한 경우에만! | 프로퍼티 키가 식별자 네이밍 규칙을 준수한 경우와, 그렇지 않은 경우에 모두 사용 가능함. *네이밍 규칙을 준수하지 않았다면, 무조건 대괄호 표기법으로 따옴표와 함께 작성하자.(숫자 제외) |
프로퍼티 접근 연산자의 좌측에는 객체로 평가되는 표현식을,
프로퍼티 접근 연산자 우측 또는 대괄호 내부에는 프로퍼티 키를 지정한다.
var person = {
name: "Lee",
};
console.log(person.name); // Lee
console.log(person["name"]); // Lee
// 문자열로 평가될 수 있는 표현식
let x = "name";
console.log(person.x); // undefined
console.log(person[x]); // Lee
- 대괄호 프로퍼티 접근 연산자
대괄호 안에 넣는 프로퍼티 키는 반드시 따옴표로 감싼 문자열 또는 문자열로 평가될 수 있는 표현식이여야한다.
var person = {
myname: "Lee",
};
console.log(person['myname']);
console.log(person[myname]); // ReferenceError: myname is not defined
let x = "name";
console.log(person[`my${x}`]); // "Lee"
let y = "myname";
console.log(person[y]); // "Lee"
- 객체에 존재하지 않는 프로퍼티에 접근
ReferenceError가 발생하지 않고 undefined를 반환
var person = {
myname: "Lee",
};
console.log(person.age); // undefined
- 프로퍼티 접근의 예제
var person = {
"last-name": "Lee",
1: 10,
};
console.log(person.'last-name'); // SyntaxError: Unexpected string
console.log(person.last - name);
// node.js: ReferenceError: name is not defined
// 브라우저: NaN
console.log(person[last - name]); // ReferenceError: last is not defined
console.log(person["last-name"]); // Lee
console.log(person.1); // SyntaxError: Unexpected string
console.log(person.'1'); // SyntaxError: Unexpected string
console.log(person[1]);
console.log(person["1"]);
두번째 console.log(person.last - name);
- node.js: person객체에 last가 있는지 확인. 없어서 undefined => name 식별자가 있는지 확인. 없어서 ReferenceError
- 브라우저: person객체에 last가 있는지 확인. 없어서 undefined => name 식별자가 있는지 확인. window의 name을 가리키는 전역변수고 빈 문자열이라서 undefined - ' '는 NaN이 된다.
프로퍼티 키가 숫자로 이루어진 문자열인 경우 따옴표를 생략할 수 있다. 하지만 마침표 표기법은 가능하지않다.
10.6 프로퍼티 값 갱신
이미 존재하는 프로퍼티에 값을 할당하면 프로퍼티 값이 갱신된다.
var person = {
name: "Lee",
};
person.name = "Kim"; // {name: "Kim"}
10.7 프로퍼티 동적 생성
존재하지 않는 프로퍼티에 값을 할당하면 프로퍼티가 동적으로 생성되어 추가되고, 프로퍼티 값이 할당된다.
var person = {
name: "Lee",
};
person.age = 20;
console.log(person); // { name: 'Lee', age: 20 }
10.8 프로퍼티 삭제
delete 연산자는 객체의 프로퍼티를 삭제한다.
delete 연산자의 피연산자는 프로퍼티 값에 접근할 수 있는 표현식이어야한다.
존재하지 않는 프로퍼티를 삭제하면 아무런 에러 없이 무시된다.
var person = {
name: "Lee",
};
person.age = 20; // 동적 할당
delete person.age; // 존재하는 프로퍼티 삭제
delete person.address; // 존재하지 않는 프로퍼티는 삭제가 안됨. 하지만 에러는 발생하지 않음
10.9 ES6에서 추가된 객체 리터럴의 확장 기능
10.9.1 프로퍼티 축약 표현
객체 리터럴의 프로퍼티 값은 변수에 할당된 값, 즉 식별자 표현식일 수도 있다.
ES6에서 프로퍼티 값으로 변수를 사용하는 경우, 변수 이름과 프로퍼티 키가 동일한 이름일 때 프로퍼티 키를 생략할 수 있다.
이때 프로퍼티 키는 변수 이름으로 자동 생성된다.
var x = 1,
y = 2;
// ES5
var obj = {
x: x,
y: y,
};
console.log(obj); // { x: 1, y: 2 }
// ES6
const obj2 = { x, y };
console.log(obj); // { x: 1, y: 2 }
10.9.2 계산된 프로퍼티 이름
문자열 또는 문자열로 타입 변환할 수 있는 값으로 평가되는 표현식을 사용해 프로퍼티 키를 동적으로 생성할 수도 있다.
단, 프로퍼티 키로 사용할 표현식은 대괄호[...]로 묶어야 한다.
이를 계산된 프로퍼티 이름 computed property name 이라 한다.
- ES5
// ES5
var prefix = "prop";
var i = 0;
var obj = {};
obj[prefix + "-" + ++i] = i;
obj[prefix + "-" + ++i] = i;
obj[prefix + "-" + ++i] = i;
console.log(obj); // { 'prop-1': 1, 'prop-2': 2, 'prop-3': 3 }
- ES6
// ES5
const prefix = "prop";
let i = 0;
// 객체 리터럴 내부에서 계산된 프로퍼티 이름으로 프로퍼티 키 동적 생성 가능
const obj = {
[`${prefix}-${++i}`]: i,
[`${prefix}-${++i}`]: i,
[`${prefix}-${++i}`]: i,
};
console.log(obj); // { 'prop-1': 1, 'prop-2': 2, 'prop-3': 3 }
10.9.3 메서드 축약 표현
ES6에서 메서드를 정의할 때 function 키워드를 생략한 축약 표현을 사용할 수 있다.
ES6의 메서드 축약표현으로 정의한 메서드는 프로퍼티에 할당한 함수와 다르게 동작한다. 26.2절에서...
- ES5
// ES5
var obj = {
name: "Lee",
sayHi: function () {
console.log("HI " + this.name);
},
};
obj.sayHi();
- ES6
// ES6
const obj = {
name: "Lee",
sayHi() {
console.log("HI " + this.name);
},
};
obj.sayHi();
11장 원시 값과 객체의 비교
원시 타입과 객체 타입은 세 가지 측면에서 근본적으로 다르다.
원시 값 | 객체 |
원시 타입의 값 변경 불가능한 값 (immutable value) |
객체(참조) 타입의 값 변경 가능한 값(mutable value) |
원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에 실제 값이 저장된다. |
객체를 변수를 할당하면 변수(확보된 메모리 공간)에는 참조 값이 저장된다 *참조 값은 프로퍼티가 저장된 메모리 주소임 |
원시 값을 갖는 변수를 다른 변수에 할당하면 원시 값이 복사되어 전달된다. 값에 의한 전달 (pass by value) |
객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 참조에 의한 전달 (pass by reference) |
11.1 원시 값
11.1.1 변경 불가능한 값 immutable value
원시 타입의 값, 즉 원시 값은 변경 불가능한 값이다.
즉 한번 생성되면 읽기 전용으로 값을 변경할 수 없다.
변수와 값을 구분해서 생각하자.
변수는 하나의 값을 저장하기 위해 확보된 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해 붙인 이름
값은 변수에 저장된 데이터로서 표현식이 평가되어 생성된 결과.
변경 불가능하다는 것은 변수가 아니라 값에 대한 진술이다.
원시 값은 변경 불가능하다
=> 원시 값 자체를 변경할 수 없다.
=> 하지만 변수 값은 변경 가능하다! 변수는 언제든 재할당 할 수 있다.
상수
재할당이 금지된 변수
상수랑 변경 불가능한 값은 다르다.
상수도 값을 저장하기 위한 메모리 공간이라 변수다. 하지만 상수는 한번만 할당이 허용되므로 변수 값은 변경할 수 없다.
값의 할당
원시 값을 할당한 변수에 새로운 원시 값을 재할당 하면,
메모리 공간에 저장되어 있는 재할당 이전의 원시 값을 변경하는 것이 아니라,
새로운 메모리 공간을 확보하고 재할당한 원시 값을 저장한 후, 변수는 새롭게 재할당한 원시 값을 가리킨다.
var score; // 변수 선언
score = 80; // 값의 할당
score = 90; // 값의 재할당
변수가 참조하던 메모리 공간의 주소가 할당/재할당 될 때 마다 변경된다.
그 이유는 할당된 원시 값이 변경 불가능한 값이기 때문이다.
만약 원시값이 변경 가능한 값 이라면, 새로운 메모리 공간을 확보할 필요가 없고, 원시 값 자체를 변경하면 된다.
하지만 원시 값은 변경 불가능한 값이라서 직접 변경할 수 없다.
변수 값을 변경하기 위해 원시 값을 재할당 하면
새로운 메모리 공간을 확보하고 재할당한 값을 저장한 뒤,
변수가 참조하던 메모리 공간의 주소를 변경한다.
=> 값의 불변성 immutability
불변성을 갖는 원시 값을 할당한 변수는, 변수 값을 변경하기 위해서는 재할당 이외의 방법은 없다.
만약 존재한다면, 변수 값은 예기치 않게 바뀔 수 있다는 것을 의미하며, 상태 변경을 추적하기 어렵게 한다.
11.1.2 문자열과 불변성
문자열은 몇 개의 문자로 이뤄졌느냐에 따라 필요한 메모리 공간의 크기가 결정된다.
10개로 이뤄진 문자열은 문자 한개당 2바이트씩 10개, 즉 20바이트가 필요하다.
자바스크립트의 문자열은 원시 타입이며, 변경 불가능하다.
문자열이 생성된 이후에 변경할 수 없다는 것을 의미한다.
var str = 'Hello';
str = 'world';
식별자 str은 문자열 'Hello'가 저장된 메모리 공간의 첫 번째 메모리 셀 주소를 가리킨다.
두 번째 문이 실행되면, 새로운 문자열 'world'를 메모리에 생성하고 식별자 str는 이것을 가리킨다.
'Hello', 'world'는 모두 메모리에 존재한다.
문자열은 유사 배열 객체이며, 이터러블이므로 배열과 유사하게 각 문자에 접근할 수 있다.
*이터러블: 반복 가능한(iterable, 이터러블) 객체는 배열을 일반화한 객체입니다. 이터러블 이라는 개념을 사용하면 어떤 객체에든 for..of 반복문을 적용할 수 있습니다.
*유사 배열 객체: 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체
- 문자열의 한 문자 변경 예제
var str = "string";
str[0] = "S";
console.log(str); // string, 바뀌지 않음
문자열 일부를 변경해도 반영되지 않는다.
11.1.3 값에 의한 전달
- 변수에 변수를 할당한 경우
copy = score에서 score는 변수값 80으로 평가되므로 copy 변수에도 80이 할당된다.
이를 값에 의한 전달 이라고 한다.
변수에 원시 값을 같는 변수를 할당하면, 원시 값이 복사되어 전달된다.
var score = 80;
var copy = score; // copy 변수에 score 변수 값 80이 복사되어 할당
console.log(score, copy); // 80 80
console.log(score === copy); // true 원시 값 80, 80은 동일함.
score = 100;
console.log(score, copy); // 100 80
console.log(score === copy); // false
score 변수와 copy변수의 값은 다른 메모리 공간에 저장된 별개의 값이다.
score 변수 값을 변경해도 copy 변수의 값에는 어떤 영향도 주지 못한다.
하지만 위처럼 동작하지 않고, var copy = score; 문에서는 score와 copy가 같은 메모리 주소공간을 가리키고 있다가,
재할당이 일어났을 때 서로 다른 메모리 주소공간을 가지게 될 수도 있다.
이는 자바스크립트 엔진 제조사마다 동작 방식의 미묘한 차이가 잇을 수 있다. 참고로 파이썬은 이와 같이 동작한다.
실은 엄격하게 말하면 변수에는 값이 전달되는 것이 아니라 메모리 주소가 전달된다.
이는 변수와 같은 식별자는 값이 아니라 메모리 주소를 기억하고 있기 때문이다.
이처럼 값의 의한 전달도 사실은 값을 전달하는 것이 아니라 메모리 주소를 전달한다.
단, 전달된 메모리 주소를 통해 메모리 공간에 접근하면 값을 참조할 수 있다.
11.2 객체
객체는 프로퍼티의 개수가 정해져 있지 않고, 동적으로 추가되고 삭제할 수 있다.
따라서 객체는 원시 값과 같이 확보해야 할 메모리 공간의 크기를 사전에 정해 둘 수 없다.
(원시값 숫자는 8바이트, 문자는 2바이트씩..)
객체는 원시 값과는 다른 방식으로 동작하도록 설계되어있다.
자바스크립트 객체 관리 방식
대부분의 자바스크립트 엔진은 해시 테이블과 유사하지만 높은 성능을 위해 일반적으로 해시 테이블보다 더 나은 방법으로 객체를 구현한다. 히든 클래스 hidden class라는 방식을 사용해 프로퍼티에 접근한다.
11.2.1 변경 가능한 값 mutable value
객체(참조) 타입의 값, 즉 객체는 변경 가능한 값이다.
원시 값을 할당받은 변수는 원시 값 자체를 값으로 갖는다.
객체를 할당받은 변수는 참조 값을 값으로 갖는다.
객체를 할당한 변수에는 생성된 객체가 실제로 저장된 메모리 공간의 주소(참조 값)가 저장되어 있다.
변수는 이 참조 값을 통해 객체에 접근할 수 있다.
// 변수에 할당이 이뤄지는 시점에 객체 리터럴이 해석되고, 그 결과 객체가 생성된다.
var person = {
name: "Lee",
};
// person 변수에 저장되어 있는 참조값으로 실제 객체에 접근한다.
console.log(person);
변수 person은 객체 { name 'Lee' }를 가리키고 있다.
변수 person에 들어있는 값은 참조값이다.
객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있다.
재할당 없이 프로퍼티를 동적으로 추가, 갱신, 삭제 가능
var person = {
name: "Lee",
};
// 프로퍼티 값 갱신
person.name = "Kim";
// 프로퍼티 동적 생성
person.address = "Seoul";
console.log(person); // { name: 'Kim', address: 'Seoul' }
원시 값과 다르게 한 이유는 메모리의 효율성과 성능 때문이다.
객체의 크기는 매우 클 수 있고, 크기가 일정하지도 않고, 프로퍼티 값이 객체일 수도 있어서 비용이 크다...
얕은 복사와 깊은 복사
얕은 복사: 객체를 한 단계 까지만 복사
깊은 복사: 객체에 중첩되어 있는 객체까지 모두 복사
const o = { x: { y: 1 } };
//얕은 복사
const c1 = { ...o };
console.log(c1 === o); // false, 객체 {x: {y: 1}}가 복사되어 다른 주소(c1)에 값으로 저장됨
console.log(c1.x === o.x); // true, x가 참조하고 있는 y의 주소값은 동일함. (참조값 동일)
// 깊은 복사
const _ = require("lodash");
const c2 = _.cloneDeep(o);
console.log(c1 === o); // false
console.log(c2.x === o.x); // false, 참조값도 달라짐.
깊은 복사: 원시 값을 할당한 변수를 다른 변수에 할당 하는 것
얕은 복사: 객체를 할당한 변수를 다른 변수에 할당하는 것
const v = 1;
// 깊은 복사
const c1 = v;
console.log(c1 === v); // true, 원시 값이 동일함.
// 얕은 복사
const o = { x: 1 };
const c2 = o;
console.log(c2 === o); // true, 참조값이 동일함. c2의 주소와 o의 주소는 다름
11.2.2 참조에 의한 전달
여러 개의 식별자가 하나의 객체를 공유할 수 있다.
var person = {
name: "Lee",
};
// 참조 값을 복사 (얕은 복사)
var copy = person;
객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달 된다. 이를 참조에 의한 전달이라고 한다.
이것은 두 개의 식별자가 하나의 객체를 공유한다는 것을 의미함. 서로 영향을 주고 받는다.
var person = {
name: "Lee",
};
// 참조 값을 복사 (얕은 복사)
var copy = person;
console.log(copy === person); // true
copy.name = "Kim";
person.address = "Seoul";
// copy와 person은 동일한 객체를 가리켜서, 한쪽의 객체를 변경하면 서로 영향을 주고받음.
console.log(copy); // { name: 'Kim', address: 'Seoul' }
console.log(person); // { name: 'Kim', address: 'Seoul' }
- 퀴즈
var person1 = {
name: "Lee",
};
var person2 = {
name: "Lee",
};
console.log(person1 === person2); // false => 참조값 다름
console.log(person1.name === person2.name); // true => 원시값 같음
12장 함수
12.1 함수란?
함수는 자바스크립트에서 가장 중요한 핵심 개념이다.
스코프, 실행 컨텍스트, 클로저, 생성자 함수에 의한 객체 생성, 메서드, this, 프로토타입, 모듈화 ...
모두 함수와 깊은 관련이 있다.
수학에서 함수 f(x)와 프로그래밍 언어의 함수는 같은 개념이다.
입력을 받아 출력을 내보내는 일련의 과정을 정의한 것이다.
// f(x, y) = x + y
function add (x, y){
return x + y;
}
// f(2, 5) = 7
add(2, 5); // 7
- 매개 변수 parameter : 함수 내부로 입력을 전달받는 변수
- 인수 argument : 입력 값
- 반환값 return value : 출력 값
함수는 값이다.
함수는 여러개 존재할 수 있으므로 특정 함수를 구별하기 위해 식별자인 함수 이름을 사용한다.
함수 정의 function definition
함수는 함수 정의를 통해 생성한다.
함수의 정의란 함수를 호출하기 이전에 인수를 전달 받을 매개변수와 실행할 문들, 그리고 반환할 값을 지정하는 것을 말한다.
자바스크립트에서 함수는 다양한 방식으로 정의할 수 있다.
- 함수 선언문
// 함수 선언문으로 함수 정의
function add(x, y) {
return x + y;
}
함수 정의만으로 함수가 실행되는 것은 아니다.
함수 호출로 인수를 매개변수를 통해 함수에 전달하면서 함수 실행을 명시적으로 지시해야한다.
// 함수 호출
var result = add(2, 5); // 반환값 7
12.2 함수를 사용하는 이유
함수는 필요할 때 여러 번 호출할 수 있다.
함수 코드의 재사용을 가능하게 하여, 다음과 같은 장점이 있다.
- 유지보수의 편의성
- 코드의 신뢰성
- 코드의 가독성
12.3 함수 리터럴
자바스크립트의 함수는 객체 타입의 값이다.
- 숫자 값 : 숫자 리터럴로 생성
- 함수 : 함수 리터럴로 생성
// 변수에 함수 리터럴을 할당
var f = function add(x, y) {
return x + y;
};
함수 이름 (add)
- 함수 이름은 식별자다. 식별자 네이밍 규칙을 준수해야함.
- 함수 이름은 함수 몸체 내에서만 참조할 수 있다.
- 함수 이름은 생략 가능하다.
- 기명 함수(named function) vs 무명/익명 함수 (anonymous function)
매개변수 목록 (x, y)
- 0개 이상의 매개변수
- 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당된다. 순서에 의미가 있다.
- 매개변수는 함수 몸체 내에서 변수와 동일하게 취급한다. 식별자 네이밍 규칙을 준수해야함.
함수 몸체
- 함수가 호출되었을 때 일괄적으로 실행할 문들을 하나의 실행단위로 정의한 코드 블록
- 함수 호출에 의해 실행됨
일반 객체는 호출할 수 없지만 함수는 호출할 수 있다.
12.4 함수 정의
함수의 정의란 함수를 호출하기 이전에 인수를 전달 받을 매개변수와 실행할 문들, 그리고 반환할 값을 지정하는 것을 말한다.
함수 정의 방식 | 예시 |
함수 선언문 | function add(x, y) { return x + y; } |
함수 표현식 | var f = function add(x, y) { return x + y; }; |
Function 생성자 함수 | var add = new Function("x", "y", "return x+y"); |
화살표 함수 | var add = (x, y) => x + y; |
12.4.1 함수 선언문
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 참조
console.dir(add); // [Function: add]
// 함수 호출
console.log(add(2, 5)); // 7
함수 선언문은 함수 리터럴과 형태가 동일하다.
함수 선언문은 함수 이름을 생략할 수 없다.
함수 선언문은 표현식이 아닌 문이다. 따라서 변수에 할당할 수 없다.
함수 선언문 vs 함수 표현식
함수 선언문과 함수 리터럴은 형태가 동일하다. 그러면 자바스크립트는 어떻게 해석하는가?
문맥에 따라 함수 선언문이여야 하는지, 함수 리터럴이여야 하는지를 판단한다.
아래 예제를 보자.
// 함수 리터럴
var add = function add(x, y) {
return x + y;
}
// 함수 호출
console.log(add(2, 5)); // 7
위에서 function add를 함수 선언문이라고 가정하면, 할당 연산자에 의해서 변수에 값이 할당될 수 있어야 한다.
즉 함수 선언문은 값으로 평가될 수 있어야 한다. 하지만 함수 선언문은 표현식이 아닌 문, 즉 값으로 평가될 수 없으므로
해당 자리에는 함수 선언문이 올 수 없다.
따라서 값으로 평가될 수 있는 함수 리터럴이 와야하는 자리이므로, 자바스크립트는 function add를 함수 표현식으로 해석한다.
함수 선언문과 함수 표현식의 내부 동작 차이
// 함수 선언문
function foo() {
console.log("foo");
}
foo();
// 함수 리터럴
(function bar() {
console.log("bar");
});
bar(); // ReferenceError: bar is not defined
- 함수 선언문 foo
단독으로 사용되었으므로 함수 선언문으로 해석된다.
함수 이름으로는 함수 외부에서 함수를 참조할 수 없다.
함수 선언문의 경우엔 함수 객체를 가리키는 식별자를 암묵적으로 생성한다.
즉 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다.
따라서 함수 선언문으로 생성된 foo는 호출할 수 있다.
- 함수 표현식 bar
그룹 연산자 ()의 피연산자는 값으로 평가될 수 있는 표현식이여야한다. 따라서 함수 리터럴 표현식으로 해석된다.
함수 이름으로는 함수 외부에서 함수를 참조할 수 없다.
함수 리터럴로 생성된 함수는 함수를 가리키는 식별자가 없으므로 bar함수는 호출할 수 없다.
// f: 식별자, add: 함수 이름
var f = function add(x, y) {
return x + y;
};
// 식별자로 함수를 호출한다
console.log(f(2, 5)); // 7
console.log(add(2, 5)); // ReferenceError: add is not defined
정리하면 함수는 함수 이름으로 호출하는 것이 아니라, 함수 객체를 가리키는 식별자로 호출한다.
12.4.3 함수 생성 시점과 함수 호이스팅
함수 선언문과 함수 표현식은 호이스팅에서 차이점을 보인다.
// 함수 정의 전에
// 함수 참조
console.dir(add); // [Function: add]
console.dir(sub); // undefined
// 함수 호출
console.log(add(2, 5)); // 7
console.log(sub(2, 5)); // TypeError: sub is not a function
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 표현식
var sub = function (x, y) {
return x - y;
};
함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점이 다르다.
함수 선언문이 코드의 선두로 끌어 올려진 것처럼동작하는 자바스크립트의 고유의 특징을 함수 호이스팅이라고 한다.
- 함수 선언문
런타임 이전에 함수 호이스팅이 일어난다.
따라서 런타임에 이미 함수 객체가 생성되어 있고, 함수 이름과 동일한 식별자에 할당까지 완료된 상태다.
즉, 함수 선언문 이전에 함수 참조, 함수 호출 둘 다 가능하다.
- 함수 표현식
런타임 이전에 변수 호이스팅이 일어난다.
즉 먼저 식별자를 생성하지만 undefined로 초기화되고,
이후 런타임 때 변수 할당문이 실행되는 시점에 평가되어 함수 표현식의 함수 리터럴도 함수 객체가 된다.
함수 정의 전에 호출하면 undefined를 함수처럼 호출하는 것이므로 TypeError가 발생한다.
함수 선언문 보다 함수 표현식을 사용하는 것을 권장한다.
12.5 함수 호출
12.5.1 매개변수와 인수
매개변수 parameter
매개변수를 통해 인수를 함수 내부로 전달한다.
함수 몸체 내부에서 변수와 동일하게 취급된다. (undefined로 초기화 한 후, 할당)
매개변수는 함수 몸체 내부에서만 참조할 수 있고 함수 몸체 외부에서 참조할 수 없다. 즉 매개변수의 스코프는 함수 내부
인수argument
인수는 값으로 평가될 수 있는 표현식
개수와 타입에 제한이 없다.
매개변수 개수와 인수의개수가 일치하는지 체크하지 않는다.
- 인수가 부족한 경우 매개변수 값은 undefined
- 인수가 더 많은 경우 초과된 인수는 무시 => arguments 객체의 프로퍼티로 보관됨
12.5.2 인수 확인
함수를 정의할 때 적절한 인수가 전달되었는 지 확인해야한다.
typeof 로 타입 확인하기
function add(x, y) {
if (typeof x !== "number" || typeof y !== "number") {
throw new TypeError("Error!");
}
return x + y;
}
console.log(add(2)); // TypeError: Error!
console.log(add("a", "b")); // TypeError: Error!
단축 평가를 사용
function add(a, b, c) {
a = a || 0;
b = b || 0;
c = c || 0;
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
console.log(add(1, 2)); // 3
console.log(add(1)); // 1
console.log(add()); // 0
ES6의 매개변수 기본값
function add(a = 0, b = 0, c = 0) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
console.log(add(1, 2)); // 3
console.log(add(1)); // 1
console.log(add()); // 0
12.5.3 매개변수의 최대 개수
ECMAScript 사양에서 매개변수의 최대 개수는 명시적으로 제한하지 않고있다.
- 매개변수는 적을수록 좋다. 0개가 최고! 최대 3개
- 이상적인 함수는 한 가지 일만 해야하며 가급적 작게 만들어야 한다.
- 만약 3개 이상의 매개변수가 필요하다면, 객체로 인수를 전달하자
객체로 인수를 전달
매개변수 순서 신경 쓸 필요 없다.
코드의 가독성이 좋아지구 실수도 줄어든다.
하지만, 함수 외부의 객체가 변경되는 부수효과가 발생할 수 있다.
12.5.4 반환문
함수는 return 키워드와 표현식으로 이뤄진 반환문을 사용해 실행 결과를 함수 외부로 반환할 수 있다.
- 함수 호출은 표현식이다. 즉, return 키워드가 반환한 표현식의 평가 결과가 값이 된다.
- 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나간다.
- return 키워드 뒤에 오는 표현식을 평가해 반환한다. 없다면 undefined
- 반환문은 함수 몸체 내부에서만 사용할 수 있다.
12.6 참조에 의한 전달과 외부 상태의 변경
매개변수도 변수와 동일하게 타입에 따라 값에 의한 전달, 참조에 의한 전달 방식을 그대로 따른다.
function changeVal(primitive, obj) {
primitive += 100;
obj.name = "Kim";
}
// 외부 상태
var num = 100;
var person = { name: "Lee" };
console.log(num); // 100
console.log(person); // { name: 'Lee' }
// 원시 값은 값 자체가 복사되어 전달, 객체는 참조 값이 복사되어 전달.
changeVal(num, person);
console.log(num); // 100 원본이 훼손되지 않는다.
console.log(person); // {name: "Kim"} 원본이 훼손됐다.
원시 타입 인수
- 값 자체가 복사되어 매개변수에 전달
- 함수 몸체에서 그 값을 변경해도 원본은 훼손되지 않는다.
- 부수효과X
객체 타입 인수
- 참조 값이 복사되어 매개변수에 전달
- 함수 몸체에 참조 값을 통해 객체를 변경하면 원본이 훼손됨
- 부수효과 O
부수효과가 일어나는 것을 막기 위해서는 객체를 불변 객체로 만들어서 사용해야 한다.
12.7 다양한 함수의 형태
12.7.1 즉시 실행 함수
함수 정의와 동시에 즉시 호출되는 함수
단 한번만 호출되고 다시 호출할 수 없다.
함수 리터럴을 평가해 함수 객체를 생성해야 즉시 실행 함수를 사용할 수 있다.
즉시 실행 함수를 실행하기 위한 방법은 다음과 같다.
(function () {
// ...
}());
(function () {
// ...
})();
!function () {
// ...
}();
+function () {
// ...
}();
익명과 기명 즉시 실행 함수
// 익명 즉시 실행 함수
(function () {
var a = 3;
var b = 5;
return a * b;
})();
// 기명 즉시 실행 함수
(function foo() {
var a = 3;
var b = 5;
return a * b;
})();
foo(); // ReferenceError: foo is not defined
12.7.2 재귀 함수
함수가 자기 자신을 호출하는 것을 재귀 호출이라고 한다.
재귀 함수는 자기 자신을 호출하는 행위, 즉 재귀 호출을 수행하는 함수.
- 반복 처리를 반복문 없이 재귀함수로 구현할 수 있다.
- 함수 이름은 함수 몸체 내부에서만 유효 하므로, 함수 내부에서 함수 이름을 사용해 자기 자신을 호출할 수 있다. (식별자도 가능)
- 재귀 함수 내에 탈출 조건을 만들어야 한다. 그렇지 않으면 스탤 오버플로(stack overflow)에러가 발생한다.
12.7.3 중첩 함수
함수 내부에 정의된 함수를 중첩 함수, 또는 내부 함수라고 한다.
중첩 함수를 포함하는 함수는 외부 함수라 부른다.
중첩함수는 외부함수를 돕는 헬퍼 함수다.
ES6부터 함수 정의는 문이 위치할 수 있는 문맥이면 어디든 가능하다.
if문이나 for문등 코드 블록 내에서도 정의할 수 있다.
하지만 호이스팅으로 혼란이 발생할 수 있으니 if, for문 코드 블록 안에서 함수 선언문으로 함수 정의는 하지말자.
12.7.4 콜백 함수
콜백 함수
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수 callback function이라고 한다.
콜백 함수는 고차 함수에 의해 호출되며, 이때 고차함수는 필요에 따라 콜백 함수에 인수를 전달할 수 있다.
고차 함수
매개 변수를 통해 함수의 외부에서 콜백 함수를 전달 받은 함수를 고차 함수라고 한다.
고차함수는 콜백함수를 자신의 일부분으로 합성한다.
즉 콜백함수는 함수를 합성할 때 사용한다.
// 외부에서 전달받은 f를 n만큼 반복 호출
function repeat(n, f) {
for (var i = 0; i < n; i++) {
f(i); // i를 전달하며 f를 호출
}
}
// 콜백 함수
var logAll = function (i) {
console.log(i);
};
repeat(5, logAll); // 0 1 2 3 4
// 콜백 함수
var logOdds = function (i) {
if (i % 2) console.log(i);
};
repeat(5, logOdds); // 1 3
// 익명 함수이자 콜백 함수
// 익명 함수 리터럴은 repeat 함수를 호출할 때 마다 평가되어 함수 객체를 생성
repeat(5, function (i) {
if (i % 2) console.log(i);
}); // 1 3
콜백함수 사용
- 함수형 프로그래밍 패러다임
- 비동기 처리(이벤트 처리, Ajax 통신, 타이머 함수)
- 배열 고차 함수 (map, filter, reduce...)
12.7.5 순수 함수와 비순수 함수
순수 함수 pure function
함수형 프로그래밍에서 어떤 외부 상태에 의존하지도 않고 변경하지도 않는, 즉 부수효과가 없는 함수
var count = 0;
// 순수 함수
function increase(n) {
return ++n;
}
console.log(increase(count)); // 1
console.log(count); // 0 => 외부 상태 변경하지 않음
비순수 함수 impure function
외부 상태에 의존하거나 외부 상태를 변경하는, 즉 부수 효과가 있는 함수
var count = 0;
// 비순수 함수
function increase() {
return ++count;
}
console.log(increase()); // 1
console.log(count); // 1 => 외부 상태 변경됨
비순수 함수가 되는 경우
- 함수 내부에서 외부 상태를 직접 참조하는 경우
- 매개변수를 통해 객체를 전달받은 경우
함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해서
불변성을 지향하는 프로그램 패러다임이다.
- 조건문 반복문 제거하여 복잡성 해결
- 변수 사용 억제, 생명주기 최소화하여 상태 변경을 피함
- 가독성을 높이고 프로그램 안정성을 높임
'JavaScript > JavaScript 예복습' 카테고리의 다른 글
JavaScript 예습 | 16~18장 (0) | 2021.09.14 |
---|---|
JavaScript 예습 | [deep dive] 13 ~ 15장 (0) | 2021.09.12 |
JavaScript 예습 | [deep dive] 6장~9장 (0) | 2021.09.07 |
JavaScript 예습 | [Deep Dive] 04장 ~ 05장 (0) | 2021.09.06 |
JavaScript 예습 | [Deep Dive] 1장 ~ 2장 예습 (0) | 2021.09.06 |