this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수 self-referencing variable
this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있음
this를 가리키는 값, this 바인딩은 함수 호출 방식에 의해 동적으로 결정 / 클래스 기반 언어인 자바나 c++에서는 언제나 클래스가 생성하는 인스턴스를 가리킴
this는 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조변수이므로 일반 함수에서는 의미가 없음. strict mode가 적용된 일반 함수의 내부의 this에는 undefined가 바인딩
// this는 어디서든지 참조 가능// 전역에서 this는 전역 객체 window를 가리킴console.log(this); // Windowfunctionsquare(number) {
// 일반 함수 내부에서 this는 전역 객체 window를 가리킴console.log(this); // Windowreturn number * number;
}
console.log(square(2)); // 4functionstrictFn() {
"use strict"; // srict mode 적용된 일반 함수 내부의 this는 undefined가 바인딩console.log(this); // undefined
}
strictFn();
const person = {
name: "Jin",
getName() {
// 메서드 내부에서 this는 메서드를 호출한 객체를 가리킴console.log(this); // {name: 'Jin', getName: ƒ}returnthis.name;
},
};
console.log(person.getName()); //JinfunctionPerson(name) {
this.name = name;
// 생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스를 가리킴console.log(this); // Person {name: 'Jin'}
}
const me = new Person("Jin");
함수 호출 방식과 this 바인딩
this 바인딩(this에 바인딩될 값)은 함수 호출 방식, 즉 함수가 어떻게 호출되었는가에 따라 동적으로 결정
비교: 함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프는 함수 정의가 평가되어 함수 객체가 생성되는 시점에 상위 스코프 결정
함수 호출하는 방식
일반 함수 호출
메서드 호출
생성자 함수 호출
Function.prototype.apply/call/bind 메서드에 의한 간접 호출
// this 바인딩은 함수 호출 방식에 따라 동적으로 결정const foo = function () {
console.dir(this);
};
// 일반 함수 호출// foo 함수 내부의 this는 전역 객체 window를 가리킴
foo(); // Window// 메서드 호출// foo 함수 내부의 this는 호출한 객체 obj를 가리킴const obj = { foo };
obj.foo(); // obj// 생성자 함수 호출// foo 함수 내부의 this는 생성자 함수가 생성한 인스턴스를 가리킴new foo(); // foo// Function.prototype.apply/call/bind 메서드에 의한 간접 호출// foo 함수 내부의 this는 인수에 의해 결정const bar = { name: "bar" };
foo.call(bar); // bar
foo.apply(bar); // bar
foo.bind(bar)(); // bar
일반 함수 호출
일반 함수로 호출된 모든 함수(중첩 함수, 콜백 함수 포함) 내부의 this에는 전역 객체가 바인딩
this 대신 콜백함수 내에서 this를 할당한 변수 사용
Function.prototype.apply/call/bind 메서드를 사용하여 명시적으로 this 바인딩
functionPerson(name) {
this.name = name;
}
Person.prototype.getName = function () {
returnthis.name;
};
const me = new Person("Jin");
// getName 메서드를 호출한 객체는 meconsole.log(me.getName()); // Jin
Person.prototype.name = "Park";
// getName 메서드를 호출한 객체는 Person.prototypeconsole.log(Person.prototype.getName()); // Park
생성자 함수 호출
생성자 함수 내부의 this에는 생성자 함수가 (미래에) 생성할 인스턴스가 바인딩
new 연산자와 함께 호출하지 않으면 생성자 함수가 아닌 일반 함수의 호출임
// 생성자 함수functionCircle(radius) {
// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킴this.radius = radius;
this.getDiameter = function () {
return2 * this.radius;
};
}
const circle1 = new Circle(5);
const circle2 = new Circle(10);
// new 연산자와 함께 호출하지 않으면 생성자 함수로 동작 x, 일반 함수 호출임const circle3 = Circle(15);
console.log(circle1.getDiameter()); // 10console.log(circle2.getDiameter()); // 20// 일반 함수로 호출된 Circle 내부의 this는 전역 객체를 가리킴console.log(radius); // 15
Function.prototype.apply/call/bind 메서드에 의한 간접 호출
apply, call, bind 메서드는 Function.prototype의 메서드이므로 모든 함수가 상속 받아 사용 가능
apply와 call의 본질적인 기능은 함수를 호출하는 것
apply와 call은 호출 함수에 인수를 전달하는 방식만 다를 뿐 동일하게 동작
apply 메서드: 호출할 함수의 인수를 배열로 묶어 전달
call 메서드: 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달
대표적인 용도는 arguments 같은 유사 배열 객체에 배열 메서드를 사용하는 경우
bind는 첫 번째 인수로 전달한 값으로 this 바인딩이 교체된 함수를 새롭게 생성해 반환. apply와 call과 달리 함수를 호출하지 않기 때문에 명시적으로 호출해줌
functiongetThisBinding() {
returnthis;
}
// this로 사용할 객체const thisArg = { a: 1 };
// bind 메서드는 첫 번째 인수로 전달한 thisArg로 this바인딩이 교체된 함수를 새롭게 생성해 반환console.log(getThisBinding.bind(thisArg)); // [Function: bound getThisBinding]// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 함console.log(getThisBinding.bind(thisArg)()); // { a: 1 }
// 메서드 내부의 중첩 함수 또는 콜백 함수의 this가 불일치하는 문제const person = {
name: "Jin",
foo(callback) {
setTimeout(callback, 100);
},
};
person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // Hi! my name is . ; 브라우저 환경의 window.name은 ""
});
// bind 메서드는 this와 메서드 내부의 중첩 함수 또는 콜백 함수의 this가 불일치하는 문제를 해결하기 위해 사용됨const person = {
name: "Jin",
foo(callback) {
// bind 메서드로 callback 함수 내부의 this 바인딩을 전달setTimeout(callback.bind(this), 100);
},
};
person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // Hi! my name is Jin.
});
빌트인 전역 함수 built-in global function은 애플리케이션 전역에서 호출할 수 있는 빌트인 함수로서 전역 객체의 메서드
eval
eval 함수는 자바스크립트 코드를 나타내는 문자열을 인수로 전달 받음
전달 받은 문자열 코드가 표현식: 문자열 코드를 런타임에 평가하여 값 생성
전달 받은 문자열 코드가 표현식이 아닌 문: 문자열 코드를 런타임에 실행
문자열 코드가 여러 개의 문: 모든 문 실행
eval 함수는 기존의 스코프를 런타임에 동적으로 수정
strict mode에서는 기존의 스코프를 수정하지 않고 eval 함수 자신의 자체적인 스코프 생성
인수로 전달 받은 문자열 코드가 let, const 키워드를 사용한 변수 선언문이면, 암묵적으로 strict mode 적용
eval 함수 사용은 금지해야 함
eval 함수를 통해 사용자로부터 입력 받은 콘텐츠 untrusted data를 실행하는 것은 보안에 취약
eval 함수를 통해 실행되는 코드는 자바스크립트 엔진에 의해 최적화가 수행되지 않으므로 처리가 느림
// 표현식인 문console.log(eval("1 + 2;")); // 2// 표현식이 아닌 문console.log(eval("var x = 5;")); // undefined// eval 함수에 의해 런타임에 변수 선언문이 실행되어 x 변수가 선언console.log(x); // 5// 객체 리터럴, 함수 리터럴은 반드시 괄호로 둘러쌈const o = eval("({a: 1})");
console.log(o); // { a: 1 }const f = eval("(function() {return 1;})");
console.log(f()); // 1// eval 함수는 자신이 호출된 위치에서 해당하는 기존의 스코프를 런타임에 동적으로 수정const z = 1;
functionfoo() {
console.log(z); // 1 - 선언 단계에 실행되었다면 undefined가 되었어야 함// eval 함수는 런타임에 foo 함수의 스코프를 동적으로 수정eval("var z = 2;");
console.log(z); // 2
}
foo();
console.log(z); // 1
isFinite
전달받은 인수가 유한수이면 true / 무한수이면 false를 반환
전달 받은 인수의 타입이 숫자가 아닌 경우, 숫자로 타입을 변환 후 검사 실행 (NaN은 false 반환, null은 true 반환)
inNaN
전달 받은 인수가 NaN인지 검사하여 결과를 불리언으로 반환
전달 받은 인수의 타입이 숫자가 아닌 경우, 숫자로 타입을 변환 후 검사 실행
parseFloat
전달받은 문자열 인수를 부동 소수점 숫자 floating point number, 즉 실수로 해석 parsing하여 반환
parseInt
전달받은 문자열 인수를 정수 integer로 해석하여 반환
전달받은 인수가 문자열이 아니면 문자열로 변환한 다음 정수로 해석하여 반환
두 번째 인수로 진법을 나타내는 기수 (2 ~ 36) 전달 가능, 이때 인수로 전달된 문자열을 해당 기수의 숫자로 해석하여 10진수로 해석하여 반환
10진수를 해당 기수의 문자열로 변경하고 싶을 경우 Number.prototype.toString 메서드 사용
첫 번째 인수로 전달된 문자열이 "0x" 또는 "0X"로 시작하는 16진수 리터럴이면 16진수로 해석하여 10진수로 반환(2진수와 8진수 리터럴은 해당 안됨)
앞 뒤 공백은 무시하고 중간에 공백이 있으면 공백으로 구분된 앞에만 해석하여 반환
// 전달 받은 문자열 인수를 문자열이 아니면 문자열로 변환하고 정수로 해석하여 반환console.log(parseInt(10.123)); // 10// "10"을 8진수로 해석하고, 그 결과를 10진수 정수 반환console.log(parseInt("10", 8)); // 8// Number.prototype.toString 메서드를 사용하여 10을 8진수로console.log((10).toString(8)); // 12// 16진수 리터럴 자동으로 16진수로 해석하여 10진수로 반환console.log(parseInt("0x0000000f")); // 15// 앞뒤 공백 무시, 중간에 공백이 있을 경우 공백으로 구분된 앞에만 해석하여 반환console.log(parseInt(" 0x0000000f 34 ")); // 15
encodeURI / decodeURI
encodetURI 함수는 완전한 URI Uniform Resource Identifier를 문자열로 전달받아 이스케이프 처리를 위해 인코딩
URI는 인터넷에 있는 자원을 나타내는 유일한 주소를 말함. 하위 개념으로 URL, URN이 있음
URI 문법 형식 표준 RFC3986에 따르면 URL은 아스키 문자 셋으로만 구성되어야 하며, 쿼리 스트링 구분자로 사용되는 (=, ? , &)을 제외하고 이스케이프 처리
const uri = "http://example.com?name=진홍엽&status=jobless&hopeful";
// encodeURI 함수는 완전한 URI를 전달받아 이스케이프 처리를 위해 인코딩const enc = encodeURI(uri);
console.log(enc); // http://example.com?name=%EC%A7%84%ED%99%8D%EC%97%BD&status=jobless&hopeful// decodeURI 함수는 인코딩된 URI를 전달받아 이스케이프 처리 이전으로 디코딩const dec = decodeURI(enc);
console.log(dec); // http://example.com?name=진홍엽&status=jobless&hopeful
encodeURIComponent / decodeURIComponent
URI 구성요소를 인수로 전달받아 인코딩
쿼리 스트링 구분자로 사용되는 =, ?, &까지 인코딩하는 차이가 있음
const uriComp = "name=진홍엽&status=jobless&hopeful";
let enc = encodeURIComponent(uriComp);
console.log(enc); // name%3D%EC%A7%84%ED%99%8D%EC%97%BD%26status%3Djobless%26hopefullet dec = decodeURIComponent(enc);
console.log(dec); // name=진홍엽&status=jobless&hopeful
enc = encodeURI(uriComp);
console.log(enc); // name=%EC%A7%84%ED%99%8D%EC%97%BD&status=jobless&hopeful
dec = decodeURI(enc);
console.log(dec); // name=진홍엽&status=jobless&hopeful
암묵적 전역
암묵적 전역 implicit global: 선언하지 않은 식별자에 값을 할당하면 전역 객체의 프로퍼티를 동적 생성하여 전역 변수처럼 동작
전역 객체의 프로퍼티이므로 변수가 아님. 따라서 변수호이스팅이 발생하지 않음
변수가 아니라 단지 프로퍼티이므로 delete로 삭제 가능, 전역 변수는 프로퍼티이지만 delete로 삭제 불가능
// 전역 변수 x는 호이스팅 발생console.log(x); // undefined// 전역 객체의 프로퍼티 y는 호이스팅 발생 x// console.log(y); // ReferenceError: y is not definedvar x = 10; // 전역 변수functionfoo() {
y = 20; // 선언하지 않은 식별자에 값을 할당 (전역 객체의 프로퍼티로 y 추가)
}
foo();
// 선언하지 않은 식별자 y를 전역에서 참조할 수 있음console.log(x + y); // 30
strict mode는 자바스크립트 언어의 문법을 좀 더 엄격히 적용하여 오류를 발생싴닐 가능성이 높거나 자바스크립트 엔진의 최적화 작업에 문제를 일으킬 수 있는 코드에 대해 명시적인 에러를 발생시킴
ESLint 같은 린트 도구를 사용해도 유사한 효과를 얻을 수 있음. 린트 도구는 정적 분석 기능을 통해 소스코드를 실행하기 전에 소스코드를 실행하여 문법적 오류뿐만 아니라 잠재적 오류까지 찾아내고 오류의 원인을 리포팅해주는 도구
DeepDive 필자는 린트 도구가 strict mode에서 제한하는 오류는 몰론 코딩 컨벤션을 설정 파일 형태로 정의하고 강제하는 등 더 강력한 효과를 얻을 수 있기 때문에 strict mode의 사용보다 린트 도구 사용을 더 선호
ES6에서 도입된 클래스와 모듈은 모두 strict mode가 적용
// 오류를 발생시킬 것 같은 코드functionfoo() {
x = 10; // x 선언이 없어서 foo 스코프 내에서 검색실패 -> 전역 객체에 암묵적으로 x 프로퍼티 동적 생성
}
foo();
console.log(x); // 10// 개발자의 의도와 상관없이 발생한 암묵적 전역은 오류를 발생시킬 수 있다
strict mode의 적용
전역의 선두 또는 함수 몸체 선두에 "use strict" 를 추가
// 전역의 선두에 추가하면 스크립트 전체에 strict mode가 적용"use strict";
functionfoo() {
x = 10; // ReferenceError: x is not defined
}
foo();
// 함수 몸체의 선두에 추가하면 해당 함수와 중첩 함수에 strict mode 적용functionfoo() {
"use strict";
x = 10; // ReferenceError: x is not defined
}
foo();
// 코드의 선두에 "use strict"; 를 위치시키지 않으면 strict mode가 제대로 작동하지 않음functionfoo() {
x = 10; // 에러를 발생시키지 않음"use strict";
}
foo();
전역에 strict mode를 적용하는 것은 피하자
전역에 적용한 strict mode는 스크립트 단위로 적용 (다른 스크립트에 영향 주지 않음)
strict mode 스크립트와 non-strict mode 스크립트를 혼용하는 경우, 외부 서드파티 라이브러리를 사용하는 경우 해당 라이브러리가 non-strict인 경우 등 예상치 못한 에러가 발생할 수 있으므로 전역에 strict mode를 사용하는 것은 지양
즉시 실행 함수로 스크립트 전체를 감싸서 스코프를 구별하는 방법 권장
<!DOCTYPE html><html><body><script>
"use strict";
</script><script>
x = 1; // 에러 발생하지 않음console.log(x); // 1</script><script>
"use strict";
y = 1; // ReferenceError: y is not definedconsole.log(y);
</script></body></html>
// 즉시 실행 함수의 선두에 strict mode 적용
(function () {
"use strict";
// Do something...
})();
함수 단위로 strict mode를 적용하는 것도 피하자
함수마다 다르게 strict mode를 설정하는 건 바람직 하지 않음
strict mode가 적용된 함수가 참조할 함수 외부의 컨텍스트에 strict mode가 적용하지 않는 것도 문제가 될 수 있음
결론: strict mode는 즉시 실행함수로 감싼 스크립트 단위로 적용하자
(function () {
// non-strict modevarlet = 10; // 에러가 발생하지 않음functionfoo() {
"use strict";
let = 20; // SyntaxError: Unexpected strict mode reserved word
}
foo();
})();
strict mode가 발생시키는 에러
암묵적 전역
선언하지 않은 변수를 참조하면 ReferenceError가 발생
(function () {
"use strict";
x = 1; // ReferenceError: x is not definedconsole.log(x);
})();
변수, 함수, 매개변수의 삭제
delete 연산자로 변수, 함수, 매개변수를 삭제하면 SyntaxError 발생 (아닐 경우 Error 발생안되고 무시됨)
(function () {
"use strict";
var x = 1;
delete x; // SyntaxError: Delete of an unqualified identifier in strict mode.functionfoo(a) {
// delete a; // SyntaxError: Delete of an unqualified identifier in strict mode.
}
delete foo; // SyntaxError: Delete of an unqualified identifier in strict mode.
})();
매개변수 이름의 중복
중복된 매개변수 이름을 사용하면 SyntaxError 발생
참고: 화살표 함수는 중복된 매개변수 이름 사용 불가능
(function () {
"use strict";
functionfoo(x, x) { // SyntaxError: Duplicate parameter name not allowed in this contextreturn x + x;
}
console.log(foo(1, 2)); // 참고: strict mode가 아닐 때 결과 값은 4
})();
with 문의 사용
with 문을 사용하면 SyntaxError 발생
with 문은 전달된 객체를 스코프 체인에 추가함
with 문은 객체의 이름을 생략할 수 있어서 코드가 간단해지지만 성능과 가독성이 나빠져서 사용 지양
(function () {
"use strict";
with ({ x: 1 }) { // SyntaxError: Strict mode code may not include a with statementconsole.log(x);
}
})();
strict mode 적용에 의한 변화
일반 함수의 this
strict mode에서 함수를 일반 호출하면 this에 undefined가 바인딩 됨 (기존에는 전역객체가 바인딩)
상속 inheritance: 어떤 객체의 프로퍼티 또는 메소드를 다른 객체가 상속 받아 그대로 사용할 수 있는 것
자바스크립트는 프로토타입 prototype 기반으로 상속을 구현
상속 받을 경우 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입, 즉 상위(부모) 객체 역할을 하는 생성자.prototype의 모든 프로퍼티와 메서드를 상속 받음
상속은 코드의 재사용 관점에서 매우 유용
// 생성자 함수functionCircle(radius) {
this.radius = radius;
this.getArea = function () {
returnMath.PI * this.radius ** 2;
};
}
// 반지름이 1인 인스턴스 생성const circle1 = new Circle(1);
// 반지름이 2인 인스턴스 생성const circle2 = new Circle(2);
/*
Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는 getArea 메서드를 중복 생성, 모든 인스턴스가 중복 소유
각 Circle의 특이한 속성인 radius가 아닌, 공통으로 갖는 getArea 메서드는 공유해서 사용하는 것이 바람직
*/console.log(circle1.getArea === circle2.getArea); // false
// 생성자 함수functionCircle(radius) {
this.radius = radius;
}
// Circle생성자 함수가 생성한 모든 인스턴스가 getArea메서드를 공유해서 사용할 수 있도록 프로토타입에 추가// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어 있음
Circle.prototype.getArea = function () {
returnMath.PI * this.radius ** 2;
};
// 인스턴스 생성const circle1 = new Circle(1);
const circle2 = new Circle(2);
/*
Circle 생성자 함수가 생성한 모든 인스턴스는 부모 객체 역할을 하는 프로토타입 Circle.prototype으로부터 getArea 메서드 상속
즉, Cirlce 생성자 함수가 생성한 모든 인스턴스는 하나의 getArea 메서드를 공유
*/console.log(circle1.getArea === circle2.getArea); // true
프로토타입 객체
프로토타입은 객체지향 프로그래밍의 근간을 이루는 객체 간 상속을 구현하기 위해 사용
모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토 타입의 참조
객체가 생성될 때 객체 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]]에 저장
객체 리터럴에 의해 생성된 객체의 프로토타입: Object.prototype
생성자 함수에 의해 생성된 객체의 프로토타입: 생성자 함수의 prototype 프로퍼티에 바인딩 되어 있는 객체
모든 객체는 하나의 프로토타입을 갖음
모든 프로토타입은 생성자 함수와 연결되어 있음
[[Prototype]] 내부 슬롯에는 직접 접근할 수 없지만 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 자신의 [[Prototype]] 내부 슬롯이 가리키는 프로토타입에 간접적으로 접근 가능
프로토타입은 자신의 contructor 프로퍼티를 통해 생성자 함수에 접근 가능
생성자 함수는 자신의 prototype 프로퍼티를 통해 프로토타입에 접근 가능
__proto__ 접근자 프로퍼티
모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토 타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근 가능
__proto__는 접근자 프로퍼티다
내부 슬롯은 프로퍼티가 아니며 원칙적으로 직접적으로 접근할 수 없음
접근자 프로퍼티는 자체적으로는 값을 갖지 않고 다른 데이터의 값을 읽거나 저장할 때 사용하는 접근자 함수, 즉 [[Get]], [[Set]] 프로퍼티 어트리뷰트로 구성된 프로퍼티
// 생성자 함수functionPerson(name) {
this.this = name;
}
const me = new Person("Jin");
// 결국 Person.prototype과 __proto__는 결국 동일한 가리킴console.log(Person.prototype == me.__proto__); // true
프로토타입의 constructor 프로퍼티와 생성자 함수
모든 프로토타입은 constructor 프로퍼티를 갖음
contructor 프로퍼티는 prototype 프로토타입 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킴
이 연결은 생성자 함수가 생성될 때, 즉 함수 객체가 생성될 때 이뤄짐
me 객체에는 constructor가 없지만 프로토타입인 Person.prototype의 constructor 프로퍼티를 상속 받아 사용 가능
리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
리터럴 표기법에 의해 생성된 객체는 생성자 함수가 생성한 객체는 아님
하지만 객체 리터럴 contructor 프로퍼티로 확인한 생성자 함수는 Object 생성자 함수
프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재
리터럴 표기법으로 생성한 객체도 생성자 함수로 생성한 객체와 본질적인 면에서 큰 차이는 없음
생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체
표준 빌트인 객체인 Object 생성자 함수와 더불어 생성된 프로토타입 Object.prototype은 다양한 빌트인 메서드 (hasOwnProperty, etc) 등 을 가짐 / 사용자 정의 생성자 함수 Person과 더불어 생성된 Person.prototype의 프로퍼티는 constructor 뿐임
functionPerson(name) {
this.name = name;
}
const me = new Person("Jin");
functionPerson(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hi my name is ${this.name}`);
};
const me = new Person("Jin");
const you = new Person("Park");
me.sayHello(); // Hi my name is Jin
you.sayHello(); // Hi my name is Park
프로토타입 체인
프로토타입 체인: 자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티가 없으면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색
프로토타입 체인은 객체 지향 프로그래밍의 상속과 프로퍼티 검색을 위한 메카니즘
Object.prototype은 프로토타입 체인의 종점 end of prototype chain
Object.prototype의 [[Prototype]] 내부 슬롯 값은 null
프로토타입 체인의 종점에서도 프로퍼티 값을 검색할 수 없는 경우 undefined 반환 (에러는 발생 x)
참고: 스코프 체인은 식별자 검색을 위한 메카니즘
functionPerson(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hi my name is ${this.name}`);
};
const me = new Person("Jin");
// hasOwnProperty는 Object.prototype의 메서드인데 동작console.log(me.hasOwnProperty("name")); // true// 이것은 me 객체가 Person.prototype뿐만 아니라 Object.prototype도 상속 받았다는 것을 나타냄console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype); // true
오버라이딩과 프로퍼티 섀도잉
프로퍼티 섀도잉: property shadowing: 상속 관계에 의해 프로퍼티가 가려지는 현상
오버라이딩 overriding: 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
오버로딩 overloading: 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식. 자바스크립트는 오버로딩을 지원하지는 않지만 arguments 객체를 통하여 구현 가능
하위 객체를 통해 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능, (get 엑세스는 허용되나 set 엑세스는 허용되지 않음)
프로토타입 프로퍼티를 변경하려면 하위 객체를 통해 프로토타입 체인으로 접근하는 것이 아니라 프로토타입에 직접 접근해야 함
const Person = (function () {
// 생성자 함수functionPerson(name) {
this.name = name;
}
// 프로토 타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! my name is ${this.name}`);
};
// 생성자 함수를 반환return Person;
})();
const me = new Person("Jin");
// 인스턴스 메서드
me.sayHello = function () {
console.log(`안녕! 내 이름은 ${this.name}`);
};
// 인스턴스 메서드가 호출됨. 프로토타입 메서드는 인스턴스 메서드에 의해 가려짐
me.sayHello(); // 안녕! 내 이름은 Jin// 인스턴스 메서드 삭제delete me.sayHello;
me.sayHello(); // Hi! my name is Jin// 하위 객체를 통하여 프로토타입 프로퍼티를 변경 또는 삭제는 불가능delete me.sayHello;
me.sayHello(); // Hi! my name is Jin 여전히 프로토타입 메서드 호출// 프로토타입 프로퍼티를 변경하려면 프로토타입에 직접 접근delete Person.prototype.sayHello;
me.sayHello(); // TypeError: me.sayHello is not a function
프로토타입의 교체
프로토타입은 임의의 다른 객체로 변경 가능
객체의 간의 상속 관계를 동적으로 변경 가능
const Person = (function () {
functionPerson(name) {
this.name = name;
}
// 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체// 자바스크립트가 암묵적으로 생성한 prototype을 객체 리터럴로 교체
Person.prototype = {
sayHello() {
console.log(`Hey, my name is ${this.name}`);
},
};
return Person;
})();
const me = new Person("Jin");
// constructor 와 생성자 함수간의 연결을 살려보자const Person = (function () {
functionPerson(name) {
this.name = name;
}
// 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체// 자바스크립트가 암묵적으로 생성한 prototype을 객체 리터럴로 교체
Person.prototype = {
// constructor 프로퍼티와 생성자 함수간의 연결 설정constructor: Person,
sayHello() {
console.log(`Hey, my name is ${this.name}`);
},
};
return Person;
})();
const me = new Person("Jin");
// constructor 프로퍼티가 생성자 함수를 가리킴console.log(me.constructor === Person); // trueconsole.log(me.constructor === Object); // false
인스턴스에 의한 프로토타입의 교체
생성자 함수의 prototype 프로퍼티에 다른 임의의 객체를 바인딩 하는 것은 미래에 생성할 인스턴스의 프로토타입을 교체하는 것
인스턴스의 __proto__ 접근자 프로퍼티를 통해 프로토타입을 교체하는 것은 이미 생성된 객체의 프로토타입을 교체하는 것
Object .setPrototypeOf 메서드 또는 인스턴스의 __proto__ 접근자 프로퍼티를 통해 인스턴스의 프로토타입 교체 가능
프로토타입은 직접 변경하지 않는 것을 권장 (객체 관의 상속 관계를 동적으로 관리하는 것은 번거로움)
상속 관계를 인위적으로 설정하려면 직접 상속 권장
ES6에 도입된 클래스를 사용하면 간편하고 직관적으로 상속 관계 구현 가능
functionPerson(name) {
this.name = name;
}
const me = new Person("Jin");
// 프로토 타입으로 교체할 객체const parent = {
sayHello() {
console.log(`Hi! my name is ${this.name}`);
},
};
// me 객체의 프로토타입을 parent로 교체// me.__proto__ = parent; 와 동일하게 동작Object.setPrototypeOf(me, parent);
me.sayHello(); // Hi! my name is Jin// 당연히 생성자 함수 Person의 prototype은 parent가 아닌 Person.prototype임console.log(Person.prototype);
/*
{constructor: ƒ}
constructor: ƒ Person(name)
[[Prototype]]: Object
*/// 인스턴스 prototype이 교체되어서 생성자함수와 prototype간의 연결이 끊어짐// 인스턴스의 constructor는 프로토타입 체인을 따라 Object 됨console.log(me.constructor === Object); // true
instance of 연산자
객체 instance of 생성자 함수
우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true, 그렇지 않은 경우 false 반환
생성자 함수를 찾아주는 것이 아니라 생성자 함수의 prototype이 프로토타입 체인 상에 존재하는지 확인
const Person = (function () {
functionPerson(name) {
this.name = name;
}
// 프로토타입 교체
Person.prototype = {
sayHello() {
console.log(`Hi! my name is ${this.name}`);
},
};
return Person;
})();
const me = new Person("Jin");
// constructor 프로퍼티와 생성자 함수 간의 연결이 파괴되어도 intanceof 에는 영향 xconsole.log(me.constructor === Person); // false// Person.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 trueconsole.log(me instanceof Person); // true// Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 trueconsole.log(me instanceofObject); // true
직접 상속
Object.create에 의한 직접 상속
Object.create 메서드는 명시적으로 프로토타입을 지정하여 새로운 객체 생성
첫 번째 매개변수: 생성할 객체의 프로토타입으로 지정할 객체 (null로 할 경우 프로토타입 종점에 위치)
두 번재 매개변수: 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터객체로 이뤄진 객체 (옵션; 생략가능)
장점
new 연산자가 없어도 객체 생성 가능
프로토타입을 지정하면서 객체 생성 가능
객체 리터럴에 의해 생성된 객체도 상속 가능
참고: ESLint에서는 Object.prototype 빌트인 메서드를 직접 호출하는 것을 권장하지 않음. Object.create(null)처럼 프로토타입 종점에 위치하는 객체를 생성 가능하기 때문. 간접적으로 호출 권장
// 프로토타입이 null인 객체 생성. 생성된 객체는 프로토타입 체인의 종점에 위치let obj = Object.create(null);
console.log(Object.getPrototypeOf(obj) === null); // true// Object.prototype을 상속 받지 못함// console.log(obj.toString()); // TypeError: obj.toString is not a function// obj = {}; 와 동일
obj = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true// obj = {x: 1} 와 동일
obj = Object.create(
Object.create(Object.prototype, {
x: { value: 1, writable: true, enumerable: true, configurable: true },
})
);
// 임의의 객체 직접 상송const parent = { x: 10 };
obj = Object.create(parent);
console.log(obj.x); // 10console.log(Object.getPrototypeOf(obj) === parent); // true// 생성자 함수functionPerson(name) {
this.name = name;
}
obj = Object.create(Person.prototype);
obj.name = "Jin";
console.log(obj.name); // Jinconsole.log(obj.__proto__ === Person.prototype); // true
// 프로토타입이 null인 객체 생성const obj = Object.create(null);
obj.a = 1;
// console.log(obj.hasOwnProperty("a"));// TypeError: obj.hasOwnProperty is not a function// 간접 호출console.log(Object.prototype.hasOwnProperty.call(obj, "a")); // true
객체 리터럴 내부에서 __proto__에 의한 직접 상속
ES6에서는 __proto__ 접근자 프로퍼티를 사용하여 직접 상속 구현
const myProto = { x: 10 };
// 객체 리터럴에 의해 객체를 생성하면서 프로토타입을 지정하여 직접 상속 받을 수 있음const obj = {
y: 20,
// 객체 직접 상속__proto__: myProto,
};
/* 아래 코드와 동일하게 동작
const obj = Object.create(myProto, {
y: { value: 20, writable: true, enumerable: true, configurable: true },
});
*/
정적 프로퍼티 / 메서드
정적 static 프로퍼티/메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 뜻함
정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출 당연히 불가능
사실상 일반 함수의 프로퍼티랑 메서드
프로토타입 프로퍼티/메서드를 표기할 때 prototype을 #으로 표기하는 경우도 있음
예시: Object.prototype.isPrototypeOf를 Object#isPrototypeof 로 표기
// 생성자 함수functionPerson(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! my name is ${this.name}`);
};
// 정적 프로퍼티
Person.staticProp = "static prop";
// 정적 메서드
Person.staticMethod = function () {
console.log("static method");
};
const me = new Person("Jin");
// 생성자 함수에 추가한 정적 프로퍼티/메서드는 생성자 함수로 참조/호출
Person.staticMethod(); // static method// 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출 불가// 인스턴스로 참조/호출할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야 함
me.staticMethod(); // TypeError: me.staticMethod is not a function
프로퍼티 존재 확인
in 연산자
in 연산자는 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인
in 연산자는 확인 대상 객체의 프로퍼티 뿐만 아니라 확인 대상 객체가 상속 받은 모든 포로토타입의 프로퍼티를 확인
ES6에서 도입된 Reflect.has 메서드는 in 연산자와 동일하게 동작
const person = {
name: "Jin",
address: "Seoul",
};
// person 객체에 name 프로퍼티가 존재console.log("name"in person); // true// person 객체에 address 프로퍼티가 존재console.log("address"in person); // true// person 객체에 age 프로퍼티가 존재?console.log("age"in person); // false// person에는 Object.prototype에서 상속받은 toString이라는 프로퍼티 존재console.log("toString"in person); // true// ES6에 도입된 Relect.has 메서드는 in과 동일하게 동작console.log(Reflect.has(person, "name")); // trueconsole.log(Reflect.has(person, "toString")); // true
Object.prototype.hasOwnProperty 메서드
인수로 전달 받은 프로퍼티가 객체 고유의 프로퍼티 키인 경우에만 true 반환 (상속 받은 프로퍼티는 false)
생성자 함수 constructor란 new 연산자와 함께 호출하여 객체(인스턴스)를 생성하는 함수
생성자 함수에 의해서 생성된 객체를 인스턴스 instance라 함
new 연산자와 함께 Object 생성자 함수를 호출하면 빈 객체를 생성하여 반환
Object 외에도 String, Number, Boolean, Function, Array, Date, RegExp, Promise 등의 빌트인 생성자 함수 제공
// 빈 객체의 생성const person = newObject();
// 프로퍼티 추가
person.name = "Jin";
person.sayHello = function () {
console.log("Hi! My name is " + this.name);
};
console.log(person); // {name: 'Jin', sayHello: ƒ}
person.sayHello(); // Hi! My name is Jin
// String 생성자 함수에 의한 String 객체 생성const strObj = newString("Jin");
console.log(typeof strObj); // objectconsole.log(strObj); // String {'Jin'}// Number 생성자 함수에 의한 Number 객체 생성const numObj = newNumber(123);
console.log(typeof numObj); // objectconsole.log(numObj); // Number {123}// Boolean 생성자 함수에 의한 Boolean 객체 생성const boolObj = newBoolean(true);
console.log(typeof boolObj); // objectconsole.log(boolObj); // Boolean {true}// Function 생성자 함수에 의한 Function 객체(함수) 생성const func = newFunction("x", "return x * x");
// Uncaught EvalError: Refused to evaluate a string as JavaScript // because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive:// Array 생성자 함수에 의한 Array 객체(배열) 생성const arr = newArray(1, 2, 3);
console.log(typeof arr); // objectconsole.log(arr); // [1, 2, 3]// RegExp 생성자 함수에 의한 RegExp 객체(정규표현식) 생성const regExp = newRegExp(/ab+c/i);
console.log(typeof regExp); // objectconsole.log(regExp); // /ab+c/i// Date 생성자 함수에 의한 Date 객체 생성const date = newDate();
console.log(typeof date); // objectconsole.log(date); // Tue Dec 12 2023 21:46:29 GMT+0900 (한국 표준시)
생성자 함수
객체 리터럴에 의한 객체 생성 방식의 문제점
객체 리터럴에 의한 생성 방식은 단 하나의 객체만 생성하므로 동일한 프로퍼티를 갖는 여러 객체를 생성할 때 비효율적
// 생성자 함수 내부에서 다른 객체를 return할 경우functionCircle(radius) {
this.radius = radius;
this.getDiameter = function () {
return2 * this.radius;
};
return {};
}
const circle1 = new Circle(5);
console.log(circle1); // {} this가 반환되지 못하고 return문에 명시한 객체가 반환
내부 메서드 [[Call]]과 [[Construct]]
함수는 객체이면서 호출할 수 있다는 추가적인 특징 보유
함수 객체는 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드는 몰론 함수로 동작하기 위해 함수 객체만을 위한 [[Environment]], [[FormalParameters]] 등의 내부 슬롯과 [[Call]], [[Contruct]] 같은 내부 메서드를 추가로 보유
함수가 일반 함수로 호출되면 함수 객체의 내부 메서드 [[Call]] 호출
함수가 new 연산자와 함게 생성자 함수로 호출되면 함수 객체 내부 메서드 [[Construct]] 호출
내부 메서드 [[Call]]을 갖는 함수 객체를 callable
내부 메서드 [[Construct]]를 갖는 함수 객체를 constructor / 안갖는 함수 객체를 non-constructor
모든 함수는 callable이지만 모든 함수 객체가 constructor인 것은 아님
constructor와 non-constructor의 구분
constructor: 함수 선언문, 함수 표현식, 클래스(클래스도 함수다)
non-constructor: 메서드(ES6 메서드 축약 표현만 인정), 화살표 함수
주의사항: ECMAScript 사양에서 메서드로 인정하는 범위가 일반적인 의미의 메서드보다 좁음
// constructor// 일반 함수 정의: 함수 선언문, 함수 표현식functionfoo() {}
const bar = function () {};
// 프로퍼티 x의 값으로 할당된 것은 일반 함수로 정의된 함수. 이는 메서드로 인정 xconst baz = {
x: function () {},
};
new foo();
new bar();
new baz.x();
// non-constuctor// 화살표 함수const arrow = () => {};
const obj = {
y() {},
};
new arrow(); // TypeError: arrow is not a constructornew obj.y(); //TypeError: obj.y is not a constructor
new 연산자
일반 함수와 생성자 함수에 특별한 형식적 차이는 없음
new 연산자와 함께 함수를 호출함녀 해당 함수는 생성자 함수로 동작
함수 객체의 내부 메서드 [[Construct]]가 호출 ([[Call]]이 아닌)
단, new 연산자와 함께 호출하는 함수는 constructor여야 함
생성자 함수는 일반적으로 첫 문자를 대문자로 기술하는 파스칼 케이스로 명명하여 일반 함수와 구별할 수 있도록 함
new.target
ES6부터 지원, IE는 지원 안함
new.target은 this와 유사하게 contructor인 모든 함수 내부에서 암묵적인 지역 변수와 같이 사용되며 메타 프로퍼티라고 부름
new 연산자와 함께 생성자 함수로 호출되면 함수 내부의 new.target은 함수 자신을 가리킴
new 연산자 없이 일반 함수로 호출되면 함수 내부의 new.target은 undefined
따라서 함수 내부에서 new.target을 사용하여 new 연산자와 생성자 함수로서 호출했는지 확인하여 그렇지 않은 경우 new 연산자와 함께 재귀 호출을 통해 생성자 함수로 호출 가능
참고: 대부분의 빌트인 생성자 함수(Object, String, Number, Boolean, Function, Array, Date, RegExp, Promise 등)는 new 연산자와 함께 호출되었는지를 확인 후 적절한 값을 반환
Object와 Function 생성자 함수는 new 연산자 없이 호출해도 new 연산자와 함께 호출했을 때와 동일하게 동작
String, Number, Boolean 생성자 함수는 new 연산자와 함께 호출했을 때 String, Number, Boolean 객체를 생성하여 반환하지만 new 연산자 없이 호출하면 문자열, 숫자, 불리언 값을 반환. 이를 통해 데이터 타입을 변환하기도 함
functionCircle(radius) {
// 이 함수가 new 연산자와 함께 호출되지 않았다면 new.target은 undefinedif (!new.target) {
// new 연산자와 함께 생성자 함수를 재귀 호출하여 생성된 인스턴스 반환returnnew Circle(radius);
}
this.radius = radius;
this.getDiameter = function () {
return2 * this.radius;
};
}
// new 연산자 호출 없이 생성자 함수를 호출해도 new.target을 통해 생성자 함수로 호출됨const circle = Circle(5);
console.log(circle.getDiameter()); // 10// ES6를 지원하지 않는 IE의 경우, 스코프 세이프 생성자 패턴 사용 가능// Scope Safe PatternfunctionCircle2(radius) {
// 이 함수가 new 연산자와 함게 호출되지 않았다면(일반 함수 호출) 이 시점의 this는 전역 객체 window// 즉 this와 Circle은 프로토타입에 의해 연결되지 않는다if (!(thisinstanceof Circle)) {
returnnew Circle(radius);
}
this.radius = radius;
this.getDiameter = function () {
return2 * this.radius;
};
}
const circle2 = Circle2(5);
console.log(circle2.getDiameter()); // 10
// new 없이 호출해도 동일하게 동작하는 Objectlet obj = newObject();
console.log(obj); // {}
obj = Object();
console.log(obj); // {}// new 없이 호출할 경우 다른 데이터 타입을 반환하는 Numberconst numObj = newNumber("123");
console.log(numObj, typeof numObj); // [Number: 123] objectconst num = Number("123");
console.log(num, typeof num); // 123 number
접근자 프로퍼티 accessor property: 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수 accessor function으로 구성된 프로퍼티
데이터 프로퍼티
프로퍼티 어트리뷰트
프로퍼티 디스크립터 객체의 프로퍼티
설명
[[Value]]
value
- 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값 - 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당. 이때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티의 [[Value]]에 값저장
[[Writable]]
writable
- 프로퍼티 값의 변경 가능 여부를 나타내며 불리언 값 - [[Writable]] 값이 false인 경우 해당 프로퍼티의 [[Value]] 값을 변경할 수 없는 읽기 전용 프로퍼티가 됨
[[Enumerable]]
enumerable
- 프로퍼티의 열거 가능 여부를 나타내며 불리언 값 - [[Enumerable]] 값이 false인 경우 해당 프로퍼티는 for ... in 문이나 Object.keys 메서드 등으로 열거 불가능
[[Configurable]]
configurable
- 프로퍼티의 재정의 가능 여부를 나타내며 불리언 값 - [[Configurable]] 값이 false인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지 됨. 단, [[Writable]] 값이 true인 경우 [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것은 허용
접근자 프로퍼티
자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티
접근자 함수는 getter / setter 함수라고도 부름
프로퍼티 어트리뷰트
프로퍼티 디스크립터 객체의 프로퍼티
설명
[[Get]]
get
접근자 프로퍼티를 통해 데이터 프로퍼티 값을 읽을 때 호출되는 접근자 함수. 즉, 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환
[[Set]]
set
- 접근자 프로퍼티를 통해 데이터 프로퍼티 값을 저장할 때 호출되는 접근자 함수. 즉, 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 [[Set]]의 값, 즉 setter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환
[[Enumerable]]
enumerable
- 프로퍼티의 열거 가능 여부를 나타내며 불리언 값 - [[Enumerable]] 값이 false인 경우 해당 프로퍼티는 for ... in 문이나 Object.keys 메서드 등으로 열거 불가능
[[Configurable]]
configurable
- 프로퍼티의 재정의 가능 여부를 나타내며 불리언 값 -[[Configurable]]값이 false인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지 됨
var x = 1;
var y = 1;
// var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언 허용// 초기화문이 있는 경우: var 키워드가 없는 것처럼 동작var x = 100;
console.log(x); // 100// 초기화문이 없는 경우: 변수 선언문 무시var y;
console.log(y); // 1
함수 레벨 스코프
var 키워드로 선언한 변수는 오로지 함수의 코드 블록만을 지역 스코프로 인정
var x = 1;
// if문의 변수 선언문에서 var 키워드로 선언한 변수도 전역변수가 됨if (true) {
var x = 10;
}
console.log(x); // 10// for문의 변수 선언문에서 var 키워드로 선언한 변수도 전역변수가 됨for (var x = 0; x < 5; x++) {}
console.log(x); // 5
변수 호이스팅
var 키워드로 변수를 선언하면 변수 호이스팅에 의해 변수 선언문이 스코프의 선두로 올려진 것처럼 동작
할당문 이전에 변수를 참조할 경우 undefined 반환
// 이 시점에// 1. 호이스팅에 의해 변수 foo는 이미 선언// 2. foo는 undefined로 초기화console.log(foo); // undefined
foo = 123;
console.log(foo); //123// 변수 선언은 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 실행var foo;
let 키워드
변수 중복 선언 금지
let 키워드로 이름이 같은 변수를 중복 선언하면 문법 에러 발생 SyntaxError
var foo = 123;
// var 키워드 중복 선언 허용// 아래 변수 선언문은 var 키워드가 없는 것처럼 동작var foo = 456;
let bar = 123;
// let이나 const 키워드로 선언된 변수는 같은 스코프 내에서 변수 중복 선언 허용 xlet bar = 456; // SyntaxError: Identifier 'bar' has already been declared
블록 레벨 스코프
let 키워드로 선언한 변수는 모든 코드 블록 (함수, if 문, for문, while문, try/catch 문 등)을 지역 스코프로 인정하는 블록 레벨 스코프 block-level scope를 따름
변수 호이스팅
var 키워드로 선언한 변수와 달리 let 키워드로 선언한 변수는 변수 호이스팅이 발생하지 않는 것처럼 동작
let 키워드로 선언한 변수는 "선언 단계"와 "초기화 단계"가 분리되어 진행
초기화 단계가 실행되기 이전에 변수를 접근하려고 하면 참조 에러 ReferenceError 발생
일시적 사각지대 TDZ; Temporal Dead Zone: 스코프의 시작 시점부터 초기화 시작 시점까지 변수를 참조할 수 없는 구간
호이스팅이 발생하지 않는 것처럼 동작하는 것이지 실제로 호이스팅은 발생
console.log(foo);
// 브라우저: ReferenceError: foo is not defined// NodeJs: ReferenceError: Cannot access 'foo' before initializationlet foo;
// var// 선언한 변수는 런타임 이전에 선언 단계와 초기화 단계가 실행// 따라서 변수 선언 이전에 변수를 참조 가능console.log(foo); // undefinedvar foo;
console.log(foo); // undefined
foo = 1;
console.log(foo); // 1// let// 런타임 이전에 선언 단계가 실행 / 초기화는 진행 x// 초기화 이전의 일시적 사각 지대에서는 변수 참조 불가능console.log(bar); // ReferenceError: Cannot access 'bar' before initializationlet bar; // 변수 선언문에서 초기화 실행console.log(bar); // undefined
bar = 2; // 할당문에서 할당 실행console.log(bar); // 2
// let의 호이스팅let foo = 1; // 전역 변수
{
console.log(foo); // ReferenceError: Cannot access 'foo' before initializationlet foo = 2; // 지역 변수
}
전역 객체와 let
var 키워드로 선언한 전역 변수와 전역 함수, 선안하지 않은 변수에 값을 할당한 암묵적 전역은 전역 객체 window의 프로퍼티가 됨
참고: 전역 객체의 프로퍼티를 참조할 때 window를 생략할 수 있음
let 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 아님
let 전역 변수는 보이지 않는 개념적인 블록(전역 렉시컬 환경의 선언적 환경 레코드) 내에 존재
// 전역 변수var x = 1;
// 암묵적 전역
y = 2;
// 전역 함수functionfoo() {}
// var키워드로 선언한 전역 변수는 전역 객체 window의 프로퍼티console.log(window.x); // 1// 전역 객체 window의 프로퍼티는 전역 변수처럼 사용 가능console.log(x); // 1// 암묵적 전역은 전역 객체 window의 프로퍼티console.log(window.y); // 2console.log(y); // 2// 함수 선언문으로 정의한 전역 함수는 전역 객체 window의 프로퍼티console.log(window.foo); // ƒ foo() {}console.log(foo); // ƒ foo() {}
let x = 1;
// let, const키워드로 선언한 전역 변수는 전역 객체 window의 프로퍼티가 아님console.log(window.x); // undefinedconsole.log(x); // 1
const 키워드
const 키워드는 상수 const를 선언하기 위해 사용
반드시 상수만을 위해 사용하지는 않음
선언과 초기화
const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화해야 함
let 키워드와 마찬가지로 블록 레벨 스코프, 변수 호이스팅이 발생하지 않는 것처럼 동작
// const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화const foo = 1;
const bar // SyntaxError: Missing initializer in const declaration