++ 2023.08.28
모던 자바스크립트 Deep Dive 책을 혼자서 읽던 중 우연히 스터디에 가입할 기회가 생긴 관계로 앞으로는 Github와 블로그에 동일하게 정리한 내용을 업로드할 생각이다. 기존 내용들은 정리가 끝나면 지울 계획이다.
사실 책의 내용을 정리한다는 것 자체가 과연 좋은 효율을 낼 수 있을지 의문이긴 하다. 책으로 배운 내용을 실전에 활용하는 것이 가장 좋지만 제가 정리를 시작하게 된 계기는 스스로를 귀찮게 하기 위해서다. 이 두꺼운 책을 읽으면서 아이패드에 적고 블로그에 정리하는 과정에서 매우 많은 시간이 소모될 것으로 예상되지만, 이 과정에서 득이 되는 부분이 더 많다고 생각하기 때문이다. 하지만 너무 시간이 소모되면 안 되기 때문에 적절한 시간 배분이 중요할 것 같다.
변수 - 변하는 값 <-> 상수
- 하나의 값을 저장하기 위해 확보한 메모리 공간 또는 메모리 공간 식별을 위해 붙인 이름
- 변수 이름으로 참조를 요청하면 JS 엔진은 변수 이름과 매핑된 메모리 주소를 통해 저장된 값을 반환
- 개발자의 의도를 나타내는 명확한 네이밍이 필요하다.
- 변수의 이름은 식별자의 역할을 수행하며, 값이 아닌 메모리 주소를 기억한다.
예시 - var name = hamin
| 주소 | 값 |
|---|---|
| 0x00000000 | 0000 0000 |
📌 var 키워드의 변수 선언은 선언 단계와 초기화 단계가 동시에 진행
호이스팅 - 변수 선언문이 코드 선두로 끌어 올려진 것처럼 동작하는 JS 고유의 특징
console.log(hamin); // undefined -> 런타임 전에 변수 hamin을 undefined로 초기화
var hamin = 'man';
값과 리터럴
📍 값
- 식(표현식)이 평가되어 생성된 결과
📍 리터럴
- 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기법
- 아라비아 숫자, 알파벳, 한글 또는 약속된 기호 ('', "", [], {} 등)
- 값으로 평가됨
var score = 100; // JS엔진에 의해 리터럴 100이 값으로 평가된다.
var score = 50 + 50; // 50 + 50도 평가되어 숫자 100을 생성하는 표현식
score; // 식별자 참조 -> 값은 없지만 값으로 평가
📌 0개 이상의 문을 중괄호로 묶은 코드 블록({...}) 뒤에는 세미콜론 X
변수 선언문
var x;
할당문
x = 5;
함수 선언문
function foo () {}
조건문
if()
반복문
for()
var x; // 표현식이 아닌 선언문
var foo = var x // SyntaxError: Unexpected token var 즉, 선언문은 값처럼 사용 불가
완료 값 - feat. 개발자 도구
- 크롬 개발자 도구에서 표현식 문은 완료 값을, 아닌 문은 undefined를 출력한다.
📌 선언문, 조건문
📌 표현식인 문
📍 데이터 타입 === 값의 종류
| 구분 | 데이터 타입 | 설명 |
|---|---|---|
| 원시 타입 | 숫자 | 숫자, 정수, 실수 |
| 문자열 | 문자열 | |
| 불리언 | 참(true)와 거짓(false) | |
| undefiend | var 키워드로 선언된 변수에 암묵적 할당되는 값 | |
| 문자열 | 문자열 | |
| 문자열 | 문자열 | |
| -------- | ---------- | ---------------------------------------- |
| 객체 타입 | 객체, 배열, 함수 |
1과 '1'
- 목적과 용도가 다른 엄연히 다른 값이다.
- 저장 방식의 차이가 존재
🔍 확보해야 할 메모리 공간의 크기, 저장되는 2진수도 다르다.
📌 숫자 타입의 값은 배정밀도 64비트 부동소수점 형식을 따른다.
모든 수를 실수로만 처리한다.
-> 정수로 표시된다 해도 실수이다.
-> 정수로 표시되는 수끼리 나눠도 실수가 나올 수 있다.
정수, 실수, 2진수, 8진수, 16진수 리터럴은 메모리에 배정밀도 64비트 부동소수점 형식으로 저장
2진수, 8진수, 16진수 표현을 위한 데이터 타입 존재 x -> 참조 시 10진수로 해석
📌 세 가지 특별한 값
Infinitity: 양의 무한대
-Infinitity: 음의 무한대
NaN: 산술 연산 불가
var name = `ha
min`
var name = 'hamin';
console.log(`My name is ${name}`) // My name is hamin
console.log(`1 + 2 = ${1 + 2}`) // 3
📌 자주 사용되는 이스케이프 시퀸스
- \n: 줄바꿈
- \t: 탭(수평)
- \': 작은 따옴표
- \": 큰 따옴표
- \: 백슬래시
다른 값과 중복되지 않는 유일한 값
-> 이름 충돌 위험이 적어 객체의 유일한 프로퍼티 키를 만들 때 사용
함수를 호출해서 생성된다.
-> 생성된 값은 외부에 노출 x
-> 다른 값과 중복 x
var key = Symbol('key');
console.log(typeof key); // symbol
var score = 100;
값을 메모리에서 참조할 때
📍 한 번에 읽어 들어야 할 메모리 공간의 크기, 즉 메모리 셀의 개수(바이트 수)가 필요하다
📍 JS는 숫자 타입을 값으로 가지기 때문에 숫자 타입으로 인식되고
📍 숫자 타입의 경우 8바이트 단위로 저장되기 때문에 참조 시 8바이트 단위로 메모리에 공간에 저장된 값을 읽어 들인다.
참조한 값(2진수)를 해석할 때
📍 score에 저장된 값의 2진수 형태 = 0100 0001이라고 가정하면,
문자열로 해석하면 'A', 숫자로 해석하면 65가 된다.
📍 이 경우에도 score 변수의 타입을 통해 어떤 타입으로 해석할지 결정한다.
📌 데이터 타입의 필요성 정리
1. 저장할 때 메모리 공간 크기 결정을 위해
2. 참조할 때 읽어 들일 메모리 공간 크기 결정을 위해
3. 읽어들인 2진수의 메모리 값 해석 결정을 위해
| 정적 타입 언어 | 동적 타입 언어 |
|---|---|
| C, 자바, 코틀린, Go | 자바스크립트, 파이썬 |
📍 변수 타입 선언은 하지 않는다.
📍 다른 데이터 타입의 값도 할당 가능
📍 JS는 var, let, const 키워드와 함께 변수 선언
📍 변수 선언이 아닌 할당에 의해 타입 결정(타입추론)됨.
📍 편리할수도 있지만, 변화하는 변수 값 추적이 어려운 위험성이 있다
-> 유연성은 높지만 신뢰성이 낮다.
📌 typeof 연산자
- 변수에 할당된 값의 데이터 타입 반환
console.log(typeof 변수명) // 변수의 타입 출력
📌연산자
하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입, 지수 연산등을 수행하여 하나의 값을 만든다.
+, -, *, /, %
📍 1개의 피연산자를 사용
📍 증가/감소 연산자는 피연산자의 값을 변경하는 부수 효과가 있다.
++, --, +, -
📍 암묵적 타입 변환(타입 강제 변환)
var x = '1';
문자열을 숫자로 타입 변환
console.log(+x); // 1
x = true;
불리언 타입도 변환
console.log(+x); // 1
x = false;
console.log(+x); // 0
x = 'hamin';
console.log(+x); // NaN
console.log(-(-10)); // 10
'1' + 2 = '12'
1 + true = 2
1 + undefined // NaN
| 할당 연산자 | 예 | 동일 표현 |
|---|---|---|
| = | x = 5 | x = 5 |
| += | x += 5 | x = x + 5 |
| -= | x -= 5 | x = x - 5 |
| *= | x *= 5 | x = x * 5 |
| /= | x /= 5 | x = x / 5 |
| %= | x %= 5 | x = x % 5 |
a = b = c = 0;
console.log(a, b, c); // 0 0 0
동등 비교 (==) vs 일치 비교 (===)
📍 일치 비교와 동등 비교는 연산자는 좌항과 우항의 피연산자를 비교는 동일하다.
📍 동등 비교는 암묵적 타입 변환을 통해 타입을 일치 시킨 후 값을 비교한다. 정확한 타입과 값을 비교하기 위해서는 반드시 일치 비교를 사용해야 한다.
5 == 5 // true
5 == '5' // true
false == 'false' // true
5 === 5 // true
5 === '5' // false
false === 'false' // false
📌 NaN - 자신과 일치하지 않는 유일한 유형
📍NaN을 조사하기 위해서는 Number.isNaN을 사용해야 한다.NaN === NaN; // false Number.isNaN(NaN); // true Number.isNaN(10) // false
📌 0 - 양의 0과 음의 0이 존재하고 이둘을 비교하면 true를 반환
0 === -0 // true 0 == -0 // true
|| - 논리합(OR)
&& - 논리곱(AND)
! - 부정(NOT
var x, y, z;
x = 1, y = 2, z = 3 // 3
📍 JS 첫 번째 버전의 버그 null타입 확인을 위해서는 일치 연산자 === 필요
typeof null // object
📍 배열, 객체, new Date()의 경우 typeof 연산자를 사용하면 object를 반환한다. 이 부분 때문에 정밀한 타입 검증을 위해서는 다른 방법이 필요하다.
📌 선언하지 않은 식별자에 typeof 연산자를 사용하면 ReferenceError가 발생하지 않고 undefined 반환
📍 좌항의 피연산자가 null or undefined이면 undefined 반환 아니면 우항 반환
📍 단 null과 undefined만 잡고 false 값은 정상 작동되어 우항의 값 출력됨
📍 좌항이 null or undefined 이면 우항 반환 아니면 좌항 반환
📍 단 null과 undefined만 잡고 false 값은 정상 작동되어 우항의 값 출력됨
📍 변수 선언에 사용되기도 함
📍 할당 연산자(=), 증가/감소 연산자(++/--)는 변수의 값이 변하거나 피연산자의 값을 변경한다.
📍 delete 연산자의 경우는 객체 프로퍼티를 삭제하는 부수효과가 있다.
📍 우선순위를 모두 기억하기에는 어려움이 많다. 상위그룹 연산자들을 기억해 두고, 우선 순위가 가장 높은 연산자를 사용하여 우선순위를 명시적으로 조절할 필요가 있다.
10 * (2 + 3); // 50
조건에 따라 코드 블록을 실행(조건문)하거나 반복 실행(반복문)할 때 사용
📍 0개 이상의 문을 중괄호로 묶은 것, 코드 블록 or 블록이라고 부른다.
📍 JS는 블록문을 하나의 실행 단위로 취급
📍 단독으로 사용할 수도 있으나 일반적으로 제어문 or 함수 정의할 때 사용
📍 블록문은 언제나 문의 종료를 의미(자체 종결성을 가진다.) -> ⭐️세미콜론을 붙이지 않는다.⭐️
// 블록문
{
var name = 'hamin';
}
// 제어문
var age = 24;
if(age === 24){
age++;
}
// 함수 선언문
function sum(a, b){
return a + b;
}
📍 주어진 조건식의 평가 결과에 따라 코드 블록의 실행을 결정
📍 조건에 따라서 값을 결정하여 변수를 할당하는 경우 삼항연산자를 사용하면 가독성이 좋다.
📍 조건에 따라서 실행해야 할 내용이 복잡하여 여러 줄의 문이 필요하다면, if ... else 문이 적합하다.
📍 else if 문과 else문은 옵션이다.
📍 코드 블록 내의 문이 하나라면 중괄호 생략 가능
if(조건식 1) {
// 조건식1이 참이면 실행
} else if(조건식2){
// 조건식2가 참이면 실행
} else{
조건식1과 조건식2가 모두 거짓이면 실행
}
📍 표현식을 평가하여 그 값과 일치하는 표현식을 갖는 case 문을 실행한다.
📍 case문 실행 뒤 break 코드가 없다면, 다음 case가 실행된다.
📍 조건이 많은 경우 if ... else 문 보다 가독성이 좋을 수 있다.
switch (표현식) {
case 표현식1:
switch 문의 표현식과 표현식1이 일치하면 실행될 문;
break;
case 표현식2:
switch 문의 표현식과 표현식1이 일치하면 실행될 문;
break;
default:
switch 문의 표현식과 일치하는 case 문이 없을 때 실행될 문;
}
📍 조건식의 평과 결과가 참인 경우 코드 블록 실행하며, 조건식이 거짓일 때까지 반복
📍 선언문, 조건식, 증감식은 모두 옵션 단, 어떤 식도 선언하지 않으면 무한루프가 된다.
📍 중첩 사용이 가능하다.
for (변수 선언문 or 할당문; 조건식; 증감식) {
조건식이 참인 경우 반복 실행될 문
}
=========================ex=========================
for(var i = 0; i < 2; i++) {
console.log(i);
}
// 0
// 1
📍 반복횟수가 불명확할 때 주로 사용
📍 주어진 조건식의 평과 결과가 참이라면 계속해서 반복 실행된다.
📍 주어진 평과 결과가 언제나 참이면 탈출 문이 필요하다. (ex. break)
📍 코드 블록을 먼저 실행하고 조건식 평가 -> 무조건 한 번 이상 코드가 실행된다.
📍 레이블 문, 반복문, switch 문에서 코드 블록을 탈출한다.
📍 불필요한 반복을 회피할 수 있다.
📍 반복문의 코드 블록 실행을 현 지점에서 중단하고 반복문의 증감식으로 실행 흐름을 이동시킨다.
(반복문 진행 중 continue 문을 만나면 아래 코드를 진행하지 않고 증감식으로 이동한다.)
📌 JS의 모든 값은 타입이 있고 개발자의 의도에 따라 타입 변환이 가능하다. 이를 명시적 타입 변환 혹은 타입 캐스팅이라 한다. ( keyword -> 의도적인 변환 )
🟢 명시적으로 타입을 변경하는 방법 🟢
1. 표준 빌트인 생성자 함수(String, Number, Boolean)를 new 연산자 없이 호출
2. 빌트인 메서드를 사용
3. 암묵적 타입 변환을 이용

숫자로 타입 변경하는 방법

불리언 타입으로 변환

📌 개발자 의도와는 상관없이 표현식 평가 도중 JS 엔진에 의해 암묵적으로 타입이 자동 변경되는 경우가 있다. 이를 암묵적 타입 변환 혹은 타입 강제 변환이라 한다.

1 - '1' // 0
'1' > 0 // true
📍 Truthy(참으로 평가되는 값) ex. 1, true
📍 Falsy(거짓으로 평가되는 값) ex. false, 0, -0, null, undefined, NaN, ' '(빈 문자열)
(💡 false가 되는 값들을 유의하자 특히 undefined, null 앞으로 데이터 검증 등 자주 사용된다.)
📌 단축 평가
논리 연산의 결과를 결정하는 피연산자를 타입 변환 없이 그대로 반환하는 것
/* 예제 */
'Cat' && 'Dog' // "Dog"
📍 논리곱(&&)
/* 예제 */
'Cat' || 'Dog' // "Cat"
📍 논리합(||)
📍 객체를 가리키기를 기대하는 변수가 null or undifined인 경우 이 변수를 참조하면 오류를 발생시키고 프로그램이 종료된다. 이 경우 단축 평가를 사용하면 Falsy 값일 경우와 Truthy 값일 경우의 분기를 주면 에러가 발생되지 않는다
/* 예제 */
var elem null;
var value = elem && elem.value;
📍 함수 호출 시 인수를 전달하지 않으면 매개변수가 undefined로 할당된다. 이때 단축 평가를 사용해 매개변수의 기본값을 설정하면 에러를 방지할 수 있다.
function getStringLength(str){
str = str || '';
return str.length;
}
getStringLength(); // 0
getStringLength('hi'); // 2
📌 좌항의 피연산자가 null or undefined인 경우 undefined 반환, 그렇지 않으면 우항 프로퍼티 참조를 이어감.
var elem null;
var value = elem?.value; // elem이 null or undefined이면 elem의 값을 반환 아니면 value 값 반환
console.log(value); // null
📍 즉 null or undefined 값만 잡기 위해 사용해야한다.
📌 좌항의 피연산자가 null or undefined인 경우 우항의 피연산자를 반환하고, 그렇지 않으면 좌항 피연산자 반환
var foo = null ?? 'default string';
console.log(foo); // "default string"
📍 null or undefined 값 평가만 위해서 사용하자
var counter = {
num = 0; // 프로퍼티
increase: functin () { // 메서드
tihs.num++;
}
}
객체의 상태를 나타내는 값
JS에서 사용하는 모든 값은 프로퍼티 값이 될 수 있다.
JS 함수는 일급 객체 -> 값 취급 가능 -> 함수도 프로퍼티 값으로 사용 가능
📍 프로퍼티 키
var foo = {
0: 1,
1: 2,
2: 3
}
console.log(foo) // {0: 1, 1: 2, 2: 3}
📍 프로퍼티 접근
🟢 마침표 프로퍼티 접근 연산자(.)를 사용하는 마침표 표기법
🟢 대괄호 프로퍼티 접근 연산자([ ... ] )를 사용하는 대괄호 표기법
var person = {
'last-name': 'Lee'
1: 10
};
person.'last-name'; // SyntaxError: Unexpected string
person.last-name; // NaN
/* 브라우저 환경: undefined - '' -> NaN
-로 인해 last가 없으니까 undefined
name은 전역 객체 ''가 되어 NaN이 된다.
*/
person[last-name]; // ReferenceError: last is not defined
person['last-name'] // Lee
/* 프로퍼티 키가 숫자로 이루어진 문자열인 경우 생략 가능 */
person.1; // SyntaxError: Unexpected number
person.'1'; // SyntaxError: Unexpected string
person[1]; // 10 : person[1] -> person['1']
person['1'] // 10
person.last-name //
📌 브라우저 환경에서는 name이라는 전역 변수가 암묵적으로 존재한다.
전역 변수 name은 창(name)의 이름을 가리키고 기본값은 빈 문자열이다
📌 식별자 규칙을 미준수 case
var person = { firstName: 'hi', // 올바른 형식 last-name: 'Lee' // -을 연산자로 해석해 에러 발생 }
📍 프로퍼티 값 갱신
var person = {
name: 'Lee'
};
person.name = "Kim";
console.log(person); // {name: "Kim"}
📍 프로퍼티 동적 생성
var person = {
name: 'Lee'
};
person.age = 20;
console.log(person);
📍 프로퍼티 삭제
var person = {
name: 'Lee'
};
person.age = 20;
delete person.age;
delete person.address;
console.log(person); // {name: "Lee"}
/* 프로퍼티 축약 표현 */
let x = 1, y = 2;
const obj = {x, y};
console.log(obj); // {x: 1, y: 2}
// 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
var prefix = 'prop';
var i = 0;
var obj = {
[`${prefix}-${++i}`]: i,
[`${prefix}-${++i}`]: i,
[`${prefix}-${++i}`]: i
};
console.log(obj); { prop-1: 1, prop-2: 2, prop-3: 3}
/* ES5 */
var obj = {
name: 'Lee',
sayHi: function() {
console.log('Hi ' + this.name);
}
};
obj.sayHi(); // Hi Lee
/* ES6 */
var obj = {
name: 'Lee',
sayHi() {
console.log('Hi ' + this.name);
}
};
obj.sayHi(); // Hi Lee
📌 클래스 기반 객체지향 언어 vs 프로토타입 기반 객체지향 언어
클래스 기반 객체지향 언어
- C++, 자바
- 클래스를 사전 정의하고 필요한 시점에 new 연산자와 함께 생성자를 호출하여 인스턴스 생성
프로토타입 기반 객체지향 언어
- 다양한 방법 지원
📍 객체 리터럴, Object 생성자 함수, 생성자 함수, Object.create 메서드, 클래스
📌 new 연산자와 함께 행성자 호출 필요 x -> JS의 유연함과 강력함을 나타내는 객체 생성 방식!
📍 원시 타입의 값 - 변경 불가능한 값 ( 원시 값을 변수에 할당 시 실제 값 저장 )
📌 상수와 변경 불가능한 값은 다르다.
- 상수는 재할당이 금지된 변수일 뿐, 원시 값은 값 변경은 불가능하지만 변수는 변경 가능하다.
var str = 'string';
str[0] = 'S';
console.log(str); // string (값 변경 x)
var score = 80;
var copy = score;
console.log(score, copy); // 80 80
console.log(score === copy); // true
💡 변수에 원시 값을 갖는 변수를 할당하는 시점에는 두 변수가 같은 원시 값을 참조하다가 어느 한쪽의 변수에 재할당이 이뤄졌을 때 비로소 새로운 메모리 공간에 재할당된 값을 저장하도록 동작할 수도 있다.
var score = 80;
var copy = score; // 이 시점에는 같은 주소를 참조하고 있을 수도 있다.
score = 100; // 다른 주소 참조
📌 식별자 === 메모리 주소에 붙인 이름
- 값에 의한 전달도 사실은 값을 전달하는 것이 아닌 메모리 주소를 전달한다.
- 메모리 주소를 통해 메모리 공간에 접근하면 값을 참조할 수 있다.
📍 객체(참조) 타입의 값 - 변경 가능한 값 ( 객체를 변수에 할당 시 참조 값이 저장 )
얕은 복사(shallow copy) vs 깊은 복사(deep copy)
얕은 복사
- 객체의 최상위 수준만 복사하므로 중첩된 객체가 변경될 경우 원본도 영향을 X
const obj1 = {
a: 1,
b: { c: 2 }
};
const obj2 = { ...obj1 }; // 얕은 복사
obj2.b.c = 3;
console.log(obj1.b.c); // 출력: 3
💡 최상위 수준이란?
객체 안에 중첩된 객체나 배열이 없는 첫 번째 레벨의 속성들을 지칭
예를 들어,
const obj = {
a: 1,
b: "text",
c: {
d: 2,
e: [3, 4],
f: {
g: 5
}
}
};
여기서 a, b, c는 obj의 최상위 수준의 속성이고 d, e, f는 c 속성 내의 중첩된 속성이므로 최상위 수준의 속성이 아니다.
얕은 복사를 할 경우 a와 b 속성은 원본 객체와 복사본 객체에서 독립적이게 됩니다. 그러나 c는 참조형 데이터이기 때문에 얕은 복사를 통해 복사된 객체에서 c를 수정하면 원본 객체의 c 속성도 변경됩니다.
깊은 복사
- 객체의 모든 수준을 복사하여 원본과 복사본이 독립적 복사본의 변경이 원본에 영향을 주지 않는다.
const obj1 = {
a: 1,
b: { c: 2 }
};
const obj2 = JSON.parse(JSON.stringify(obj1)); // 깊은 복사
obj2.b.c = 3;
console.log(obj1.b.c); // 출력: 2
📌 따라서 JS에는 참조에 의한 전달은 존재하지 않고 값에 의한 전달만 존재한다고 말할 수 있다.
📌 프로그래밍 언어의 함수
function add(x, y) {
return x + y;
}
var result = add(2, 5);
📌 리터럴 - 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기 방식
변수에 함수 리터럴을 할당
var f = function add(x + y) {
return x + y;
}
| 구성 요소 | 설명 |
|---|---|
| 함수 이름 | 함수 이름은 식별자다. -> 네이밍 규칙 준수 해야함 |
| 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다. | |
| 함수 이름은 생략 가능, 이름이 있는 함수를 기명 함수, 이름 없는 함수를 무명/익명 함수라고 한다. | |
| 매개변수 목록 | 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분 |
| 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당 즉, 매개변수 목록 순서도 의미가 있다. | |
| 매개변수는 함수 몸체 내에서 변수와 동일 취급, 따라서 매개변수도 네이밍 규칙 준수해야함 | |
| 함수 몸체 | 함수가 호출되었을 때 일괄적으로 실행될 문들을 하나의 실행 단위로 정의한 코드 |
| 함수 호출에 의해 실행됨 |
function add(x, y) {
return x + y;
}
/* 생략 불가 */
function (x, y) {
return x + y;
}
📌 기명 함수 리터럴은 중의적인 코드다. -> 문맥에 따라 해석이 다르다.
// 기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석
var foo = function add(x,y) {
return x + y;
};
// 함수 리터럴을 피연산자로 사용하면 함수 선언문이 아니라 함수 리터럴 표현식으로 해석
(var add = function add(x,y) {
return x + y;
});
add(); // ReferenceError: add is not defined
💡 에러 발생 이유
함수의 이름(add)은 내부에서만 참조할 수 있는 식별자이다. 외부에서 호출했기 때문에 에러가 발생한다.
foo를 호출하게 된다면 오류가 발생하지 않는다. -> JS 엔진이 암묵적으로 함수 이름을 식별자로 생성하고 거기에 함수 객체를 할당했기 때문이다.
⭐️ 지금까지 우리가 호출한 함수 이름은 사실 객체를 가리키는 식별자를 호출한 것이다.
/* 함수를 선언을 코드로 표현하면 이러한 형태이다. */
var add = function add(x,y){ // 앞에 add는 식별자 이름 뒤에 add는 함수 이름이다
return x + y;
};
console.log(add(2,5)) // 함수의 식별자(add)를 호출한 것이다.
📌 일급 객체 - JS 함수는 값처럼 변수에 할당할 수도 있고 프로퍼티 값, 배열의 요소가 될 수도 있다.
/* 함수를 선언과 동일해 보이지만 함수 이름 생략이 가능하다. */
var add = function(x,y){
return x + y;
};
console.log(add(2,5))
🟢 함수 표현식과 함수 선언문은 동일하게 보이지만 그렇지 않다.
💡 변수 호이스팅과 함수 호이스팅은 런타임 이전에 식별자를 생성한다는 부분은 동일하지만 변수 호이스팅은 undefined로, 함수 호이스팅은 함수 객체가 할당된다.
var add = new Funtion('x', 'y', 'return x + y');
console.log(add(2,5))
const add = (x + y) => x + y;
function add(x,y) {
console.log(arguments); // Arguments(3) [2, 5, 10]
return x + y;
}
add(2, 5, 10) // 10은 arguments에 저장
function add(x,y){
if(typeof x !== 'number' || typeof y !== 'number'){
throw new TypeError('인수는 모두 숫자만!')'
}
}
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
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
function changeVal(primitive, obj) {
primitive += 100;
obj.name = 'Kim';
}
var num = 100;
var person = { name: 'Lee' }
console.log(num);
console.log(person)
changeVal(num, person);
console.log(num); // 100
console.log(person) // {name: "Kim"}
📍원시 값은 값 자체가 복사되어 전달되므로 기존 값이 변경되지 않는다.
📍 객체의 경우 참조 값이 복사되어 전달되므로 값이 변경되는 부수효과가 발생한다.
💡 해결방법 - 객체를 불변 객체로 만들어 사용한다.(객체의 복사본을 만든다. (새롭게 생성하는 것처럼))
💡 그룹 연산자로 묶은 이유
(function() {
var a = 3;
var b = 5;
return a * b;
}());
var res = (function() {
var a = 3;
var b = 5;
return a * b;
}());
console.log(res); // 15
res = (function (a, b) {
return a * b;
}(3, 5));
console.log(res); // 15
function outer() {
var x = 1;
// 중첩함수
function inner () {
// 외부 함수의 변수 참조 가능
console.log(x + y); // 3
}
inner();
}
outer();
📌 콜백 함수는 고차 함수에 의해 호출, 이때 고차 함수는 필요에 따라 콜백 함수에 인자를 전달
📍외부 상태를 변경하지 않고 외부 상태에 의존하지도 않는 함수를 순수 함수라고 한다.
var cnt = 0;
// 언제나 동일한 인수가 전달되고 동일한 값 반환
function increase(n) {
return ++n;
}
cnt = increase(cnt);
console.log(cnt); // 1
cnt = increase(cnt);
console.log(cnt); // 2
📍 외부 상태에 의존하거나 외부 상태를 변경하는, 즉 부수 효과가 있는 함수를 비순수 함수라고 한다.
💡 외부 상태 - 전역 변수, 서버 데이터, 파일, console, DOM 등
var cnt = 0;
// 언제나 동일한 인수가 전달되고 동일한 값 반환
function increase(n) {
return ++cnt; // 외부 상태에 의존하여 외부 상태를 변경
}
// 비순수 함수는 외부 상태를 변경하므로 상태 변화 추적이 어렵다.
increase(cnt);
console.log(cnt); // 1
increase(cnt);
console.log(cnt); // 2
식별자가 유효한 범위
var x = 'global';
function foo() {
var x = 'local';
console.log(x) // 1
}
foo();
console.log(x); // 2
📌 변수 중복 선언을 방지하는 방법
- var 키워드가 아닌 let, const 키워드를 사용하면 중복 선언이 불가능하다.
| 구분 | 설명 | 스코프 | 함수 |
|---|---|---|---|
| 전역 | 코드의 가장 바깥 영역 | 전역 스코프 | 전역 변수 |
| 지역 | 함수 몸체 내부 | 지역 스코프 | 지역 변수 |
var x = 'global';
function foo() {
var x = 'local';
console.log(x) // 1
}
foo();
console.log(x); // 2

💡 var 키워드
- 함수의 코드 블록(함수 몸체)만 지역 스코프로 인정
- 함수 밖에서 var 키워드로 선언된 변수는 코드 블록 내에서 선언되었다 할지라도 모두 전역 변수가 된다.
- 의도치 않게 변수 값이 변경되는 부작용이 발생한다.
var x = 1;
if(true) {
var x = 10;
}
console.log(x); // 10
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar(){
console.log(x);
}
foo(); 1. ?
bar(); 2. ?
📌 두 가지 패턴으로 예측할 수 있다.
💡 변수의 생명 주기
메모리 공간 확보(스코프 등록) -> 메모리 공간이 해제되어 가용 메모리 풀에 반환되는 시점(스코프 소멸)
📌 전역 객체
- 코드 실행 이전 단계에 JS 엔진에 의해 가장 먼저 생성되는 특수한 객체
- 클라이언트 사이드 환경에서는 window, 서버 사이드 환경에서는 global 객체를 의미
- 표준 빌트인 객체(Object, String, Number ...)와 환경에 따른 호스트 객체(클라이언트 API or Node.js 호스트 API, var 키워드로 선언한 전역 변수와 전역 함수를 프로퍼티로 갖는다.
- 전역 객체 window는 웹페이지를 닫기 전까지 유효
var MYAPP = {}; // 전역 네임스페이스 객체
MYAPP.name = 'LEE';
console.log(MYAPP.name) // LEE
📌 캡슐화 - 객체의 상태를 나타내는 프로퍼티와 메서드를 하나로 묶는 것, 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용(은닉)
📌 공통적으로 에러를 발생시키지는 않지만 가독성과 오류를 발생시킬 여지를 남긴다
let name = '하민';
let name = '박하'; // SyntaxError Identifier 'name' has already been declared
let foo = 1; // 전역변수
{
let foo = 2; // 지역 변수
let bar = 3; // 지역 변수
}
conosle.log(foo); // 1
console.log(bar); // ReferenceError: bar is not defined
console.log(foo); // ReferenceError: foo is not defined
let foo; // 변수 선언문에서 초기화 단계가 실행
console.log(foo); // undefined
foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1
| 선언단계 | ReferenceError |
|---|---|
| 일시적 사각지대(TDZ) | ReferenceError |
| 초기화 단계 | foo === undefined |
| 할당 단계 | foo === 1 |
보통 상수 선언을 위해 사용(상수는 상태 유지와 가독성. 유지보수의 편의를 위해 적극 사용 권장, 상수 이름은 대문자로 선언해 상수임을 명확히 나타내고 여러 단어로 이뤄진 경우 언더스코어(_)로 구분해 스네이크 케이스로 표현하는 것이 일반적)
선언과 동시에 초기화가 필요하고 재할당이 불가능하다.
const foo = 1;
const foo; // SyntaxError: Missing initializer in const declaration
foo = 2; // TypeError: Assignment to constant variable.
💡 var vs let vs const
- ES6 사용 시 var 키워드는 사용하지 않는다.
- 재할당 필요 여부를 모를 경우 const를 우선 사용하고 상황을 지켜보는 편이 좋다.
JS 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript에서 사용하는 의사 프로퍼티와 의사 메서드
이중 대괄호 ([[...]])로 감싼 이름들이 내부 슬롯과 내부 메서드다.
JS 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 외부에 공개된 객체의 프로퍼티는 아니다. (JS 엔진 내부 로직으로 원칙적이므로 JS는 내부 슬롯과 내부 메서드에 직접적으로 접근 및 호출 방법 제공 x)
일부 내부 슬롯과 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공
모든 객체는 [[Prototype]] 내부 슬롯을 갖는다.
원래 JS 엔진 내부로직에 접근 불가능하지만 __proto__를 통해 간접 접근 가능
JS 엔진이 관리하는 내부 상태 값인 슬롯
[[Value]], [[Writable]], [[Enumerable]], [[Configurable]]
직접 접근이 불가능하지만 Object.getOwnPropertyDescriptor 메서드를 사용하여 간접적 확인 가능 (첫 번째 매개변수에 객체 참조 전달, 두 번째 매개변수에 프로퍼티 키를 문자열로 전달)
Object.getOwnPropertyDescriptor 메서드는 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환, 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터 요구 시 undefined 반환
const person = {
name: 'Lee'
};
console.log(Object.getOwnPropertyDescriptor(person, 'name');
// {value: "Lee", writable: true, enumerable: true, configurable: true}
| 프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
|---|---|---|
| [[Value]] | vale | 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값, 프로퍼티 키로 프로퍼티 값 변경 시 [[Value]]에 값을 재할당 이때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 [[Value]]값을 저장 |
| [[Writable]] | writable | 프로퍼티 값의 변경 가능 여부를 나타내며 불리언 값을 갖는다. false인 경우 [[Value]] 값을 변경할 수 없는 읽기 전용이 됨 |
| [[Enumerable]] | enumerable | 프로퍼티 값의 변경 가능 여부를 나타내며 불리언 값을 갖는다. false인 경우 해당 프로퍼티는 for ... in 문이나 Object.keys 메서드 등으로 열거 불가 |
| [[Configurable]] | configurable | 프로퍼티 재정의 가능여부를 나타내며 불리언 값을 갖는다. false인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰터 값의 변경이 금지된다. 단 [[Writable]]이 true인 경우 [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것을 허용 |
| 프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
|---|---|---|
| [[Get]] | get | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수, getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환 |
| [[Set]] | set | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수, setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장 |
| [[Enumerable]] | enumerable | 데이터 프로퍼티의 [[Enumerable]]과 동일 |
| [[Configurable]] | configurable | 데이터 프로퍼티의 [[Configurable]]과 동일 |
// 일반 객체의 __proto__는 접근자 프로퍼티다.
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__');
// {get: f, set: f, enumerable: false, configurable: true}
// 함수 객체의 prototype은 데이터 프로퍼티다.
Object.getOwnPropertyDescriptor(function() {}, 'prototype');
// {value: {...}, writable: true, enumerable: false, configurable: false}
| 구분 | 메서드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티 값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 어트리뷰트 재정의 |
|---|---|---|---|---|---|---|
| 객체 확장 금지 | Object.preventExtensions | X | O | O | O | O |
| 객체 밀봉 | Object.seal | X | X | O | O | X |
| 객체 동결 | Object.freeze | X | X | O | X | X |
const person = {name: 'Lee'};
console.log(Object.isExtensible(person)); // true
// 객체 확장 금지
Object.preventExtensions(person);
console.log(Object.isExtensible(person)); // false
const person = {name: 'Lee'};
// 밀봉된 객체 아님
console.log(Object.isSealed(person)); // false
// 객체 확장 금지
Object.seal(person);
console.log(Object.isSealed(person)); // true
const person = {name: 'Lee'};
// 동결된 객체가 아니다
console.log(Object.isFrozen(person)); // false
// 객체 동결
Object.freeze(person);
console.log(Object.isFrozen(person)); // true
const person = new Object();
person.name = 'Lee';
person.hi = function() {
console.log('hi');
}
console.log(person) // {name: "Lee", hi: f}
person.hi(); // "hi"
function Circle(radius) {
this.radius = radius; // 초기화는 선택이다.
this.getDiameter = function() {
return 2 * this.radius;
};
}
📌 바인딩
- 식별자와 값을 연결하는 과정
💡 함수가 일반 함수로서 호출되면 함수 객체의 내부 메서드 [[Call]]이 호출되고 new 연산자와 함께 생성자를 함수로서 호출되면 내부 메서드 [[Construct]]가 호출된다.
- 내부 메서드 [[Call]]을 갖는 함수 객체를 callable, 내부 메서드 [[Construct]]을 갖는 함수 객체를 constructor, [[Construct]]를 갖지 않는 함수 객체를 non-constructor 라고 부른다.
- 즉 함수 객체는 반드시 callable이면서 constructor이거나 callable이면서 non-constructor이다.
if(!new.target) // 생성자 함수로 호출되지 않은 경우를 방지가 가능하다.
📌 일급 객체의 조건
- 무명의 리터럴로 생성할 수 있다. (런타임에 생성 가능)
- 변수나 자료구조(객체, 배열 등)에 저장 가능하다.
- 함수의 매개변수로 전달 가능하다.
- 함수의 반환값으로 사용 가능하다.
// 1. 함수는 무명의 리털로 생성할 수 있다.
// 2. 함수는 벼수에 저장할 수 있다.
// 런타임(할당 단계)에 함수 리터럴이 평가되어 함수 객체가 생성되고 변수에 할당된다.
const increase = function(num) => {
return ++num;
};
// 2. 함수는 객체에 저장할 수 있다.
const auxs = { increase };
// 3. 함수의 매개변수에 전달할 수 있다.
// 4. 함수의 반환값으로 사용할 수 있다.
function makeCounter(aux) {
let num = 0;
return function() {
num = aux(num);
return num;
};
}
// 3. 함수는 매개변수에게 함수를 전달할 수 있다.
const increase = makeCounter(auxs.increase);
console.log(increase()); // 1
function square(number){
return number * number;
}
conosole.log(Object.getOwnPropertyDescriptors(square));
/*
{
length: {valye: 1, writable: false, enumerable, configuarable: true},
name: {valye: "square", writable: false, enumerable, configuarable: true},
arguments: {valye: null, writable: false, enumerable, configuarable: false},
caller: {valye: null, writable: false, enumerable, configuarable: false},
prototype: {valye: {}, writable: true, enumerable, configuarable: true}
}
*/
const obj = { a: 1 };
console.log(obj.__proto__ === Object.prototype); // true
// hasOwnproperty 메서드는 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티인지 판단한다.
console.log(obj.hasOwnproperty('a')); // true
console.log(obj.hasOwnproperty('__proto__')); // false
상속 - 객체지향 프로그래밍의 핵심 개념으로 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.getArea = function() {
return Math.PI * this.radius ** 2;
};
const circle1 = new Circle(1);
const circle2 = new Circle(2);
console.log(circle1.getArea === circle2.getArea); // true
__proto__ 접근자 프로퍼티__proto__ 접근자 프로퍼티를 통해 [[Prototype]] 내부 슬롯에 접근 가능
__proto__ 접근자 함수를 통해 프로토타입에 접근하면 내부적으로 __proto__ 접근자 프로퍼티인 getter 함수인 [[Get]]이 호출되고 새로운 프로토타입을 할당하면 setter 함수인 [[Set]]이 호출된다.const obj = {};
const parent = { x: 1};
// getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입 취득
obj.__proto__
// setter 함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;
console.log(obj.x); // 1
__proto__ 프로퍼티는 객체가 직접 소유하지 않고 Object.prototype의 프로퍼티다.__proto__ 접근자 프로퍼티 사용 가능💡 모든 객체는 프로토타입의 계층 구조인 프토토타입 체인에 묶여있다. JS 엔진이 프로퍼티, 메서드에 접근할 때 해당 객체에 접근하려는 프로퍼티가 없다면
__proto__접근자 프로퍼티가 가리키는 참조를 따라 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 프로토타입 체인의 종점은 Object.prototype이고 이 객체의 프로퍼티와 메서드는 모든 객체에 상속됨
__proto__ 접근자 프로퍼티 사용 이유는 상호 참조 방지를 위해서이다.const parent = {};
const child = {};
// child의 프로토타입을 Parent로 설정
child.__proto__ = parent;
// parent의 프로토타입을 child로 설정
parent.__proto__ = child // TypeError
서로가 자신의 프로토타입이 되는 비정상적인 프로토타입 체인이 만들어지게 된다. 프로토타입은 단방향 링크드 리스트로 구현되어야 한다. 순환 참조를 하게될 경우 체인의 종점이 존재하지 않기 때문에 검색 시 무한 루프에 빠진다. 따라서 __proto__ 접근자 프로퍼티를 통해서만 프로토타입에 접근하고 교체하도록 구현되어 있다.
Object.prototype을 상속받지 않는 객체도 존재하기 때문에 __proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장 x 대신 프로토타입의 참조를 취득하고 싶은 경우에는 Object.getPrototypeOf 메서드를 사용하고, 프로토타입 교체를 원하는 경우 Object.setPrototypeOf 메서드 사용을 권장
생성자 함수로서 호출할 수 없는 함수, 즉 non-constructor인 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않으며 프로토타입도 생성 하지 않는다.

const onj = {};
// obj 객체의 생성자 함수는 Object 생성자 함수다.
console.log(obj.constructor === Object); // true
추상 연산
- ECMAScript 사양에서 내부 동작의 구현 알고리즘을 표현한 것
| 리터럴 표기법 | 생성자 함수 | 프로토타입 |
|---|---|---|
| 객체 리터럴 | Object | Object.prototype |
| 함수 리터럴 | Function | Function.prototype |
| 배열 리터럴 | Array | Array.prototype |
| 정규 표현식 리터럴 | RegExp | RegExp.prototype |
// 함수 정의와 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성
console.log(Person.prototype); // {constructor: f}
function Person(name){
this.name = name;
}
함수 선언문은 런타임 이전에 JS엔진에 의해 먼저 실행 이때 함수 선언문으로 정의된 Person 생성자 함수는 먼저 함수 객체가 되고 프로토타입도 더불어 생성
생성된 프로토타입은 Person 생성자 함수의 prototype 프로퍼티에 바인딩된다.
모든 객체가 프로토타입을 가지므로 프로토타입도 자신의 프로토타입을 갖는다.
객체는 다른 객체로부터 상속을 받는다. 이 때 상속받는 객체를 '프로토타입'이라고 한다.
prototype 프로퍼티는 해당 생성자 함수로 생성될 객체 프로토타입을 참조한다.
function Person(name) {
this.name = name;
}
let john = new Person("John");
// hasOwnProperty는 Object.prototype의 메서드다.
// me 객체는 프로토타입 체인을 따라 hasOwnProperty 메서드를 검색하여 사용한다.
me.hasOwnProperty('name'); // true
//call 메서드는 this로 사용할 객체를전달하는 함수를 호출
Object.prototype.hasOwnProperty.call(me, 'name');
me.hasOwnProperty('name');
const Person = (function () {
function Person(name) {
this.name = name;
}
// 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
Person.prototype = {
// constructor 프로퍼티와 생성자 함수 간의 연결을 설정
constructor: Person,
~
}
~~
const me = new Person('Lee');
// constructor 프로퍼티가 생성자 함수를 가리킨다.
console.log(me.constructor === Person); // true
console.log(me.constructor === Object); // false
}
function Person(name){
this.name = name;
}
const me = new Person('Lee');
// 프로토타입으로 교체할 객체
const parent = {
sayHello() {
console.log(`hi my name is ${name}`);
}
};
// me 객체의 프로토타입을 Parent로 교체
Object.setPrototypeOf(me, parent);
// 위 코드는 me.__proto__ = parent와 동일하게 동작
me.sayHello(); // hi my name is Lee
위와 같은 경우도 검색 시 parent가 아닌 Object가 검색되고 constructor 프로퍼티와 생성자 함수간의 연결이 파괴된다.
연결을 위해서는
// constructor 프로퍼티와 생성자 함수를 연결해주고
constructor: Person
// 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결을 설정해준다
Person.Prototype = parent;
// me 객체의 프로토타입을 Parent로 변경
Object.setPrototypeOf(me, parent);
console.log(me.constructor === parent); // true
console.log(me.constructor === Object); // false
// 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리킨다
console.log(Person.prototype === Object.getPrototypeOf(me)) // true
프로토타입 변경 시 체인 상에 존재하는지 확인 가능하다.
// Person.prototype이 me 객체의 프로토타입 체인 상에 존재하지 않기 때문에 false
console.log(me instanceof Person); // false
// Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가된다.
console.log(me instance Object); // true
💡 Object.prototype의 빌트인 메서드를 객체가 직접 호출하는 것은 권장하지 않는다.
- Object.create 메서드를 통해 프로토타입 체인의 종점에 위치하는 객체를 생성할 수 있다. 프로토타입의 종점에 위치하는 객체는 Object.prototype의 빌트인 메서드를 사용할 수 없다.
// 프로토타입이 null인 객체, 즉 프로토타입 체인의 종점에 위치하는 객체를 생성한다.
const obj = Object.create(null);
obj.a = 1;
console.log(Object.getPrototypeOf(obj) === null); // true
// obj는 Object.prototype의 빌트인 메서드를 사용할 수 없다.
console.log(obj.hasOwnProperty('a')); // TypeError
// 해당 방법을 권장한다.
console.log(Object.prototype.hasOwnProperty.call(obj, 'a')); // true
__proto__에 의한 직접 상속__proto__ 접근자 프로퍼티를 사용하여 직접 상속 구현이 가능하다.const myProto = { x: 10 };
// 객체 리터럴에 의해 객체를 생성하면서 프로토타입을 지정하여 직접 상속받을 수 있다.
const obj = {
y: 20,
// 객체를 직접 상속받는다.
// obj -> myProto -> Object.prototype -> null
__proto__: myProto
};
/* 위 코드와 동일하다. 기존에는 아래처럼 정의해야 했다.
const obj = Object.create(myProto, {
y: { value: 20, writable: true, enumerable: true, configurable: true }
});
*/
console.log(obj.x, obj.y); // 10 20
console.log(Object.getPrototypeOf(obj) === myProto); // true
// 정적 프로퍼티
Person.staticProp = 'static prop';
// 정적 메서드
Person.staticMethod = function () {
console.log('staticMethod');
};
const me = new Person('Lee');
// 생성자 함수에 추가한 정적 프로퍼티/메서드는 생성자 함수로 참조/호출한다.
Person.staticMethod(); // staticMethod
// 인스턴스로 참조/호출은 불가능하다.
// 인스턴스로 참조/호출할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야 한다.
me.staticMethod(); // TypeError
Object.create 메서드는 Object 생성자 함수의 정적 메서드이므로 생성된 인스턴스에서는 접근이 불가능하고
Object.prototype.hasOwnProperty 메서드는 Object.prototype의 메서드로 모든 객체의 프로토타입 체인의 종점 Object.prototype의 메서드이므로 모든 객체가 호출 가능하다.
this를 참조하지 않는 프로토타입 메서드의 경우 정적 메서드로 변경해도 동일한 효과를 얻을 수 있다.
function Foo() {}
Foo.prototype.x = function() {
console.log('x');
};
const foo = new Foo();
// 프로토타입 메서드를 호출하려면 인스턴스를 생성해야 한다.
foo.x(); // x
// 정적 메서드
Foo.x = function () {
console.log('x');
};
// 정적 메서드는 인스턴스를 생서하지 않아도 호출할 수 있다.
Foo.x(); // x
프로토타입 프로퍼티/메서드를 표기할 때 prototype을 #으로 표기(ex. Object.prototype.isPrototypeOf를 Object#isPrototypeOf으로 표기)하는 경우도 있다.
const person = {
name: 'Lee',
address: 'Seoul'
};
console.log('name' in person); // true
const person = { name: 'Lee' };
console.log(Reflect.has(person, 'name')); // true
console.log(Reflect.has(person, 'toString')); // true
console.log(person.hasOwnProperty('toString')); // false
const person = {
name: 'Lee',
address: 'Seoul'
};
for(const ket in person) {
console.log(key + ': ' + person[key]);
}
// name: Lee
// address: Seoul
console.log(Object.entries(person)); // [["name", "Lee"], ["address", "Seoul"]]
function foo () {
x = 10;
}
foo();
console.log(x) // ??
<!DOCTYPE html>
<html>
~
~
<script>
'use strict';
</script>
(function () {
'use strict';
~~
}
(function() {
'use strict'
// SyntaxError
with({ x: 1 }) {
console.log(x);
}
}());
(function(a){
'use strict';
// 매개변수에 전달된 인수를 재할당하여 변경
a = 2;
// 변경된 인수가 arguments 객체에 반영 x
console.log(arguments) // { 0: 1, length: 1 }
}(1));