스코프 : 식별자 유효 범위
변수 이름 충돌 방지
var은 한 스코프 내 중복 선언이 가능하나 let/const는 불가
스코프의 종류
1. 전역과 전역 스코프
어디서든 참조 가능
2. 지역과 지역 스코프
자신의 지역 스코프와 하위 지역 스코프에서 유효
스코프 체인
지역 스코프는 함수에 따라 중첩됨 → 계층 구조
스코프 체인 : 스코프를 계층적으로 연결한 것
변수 참조 시 변수를 참조하는 스코프에서 상위 방향으로 변수를 검색함
(하위 스코프에서 유효한 변수를 상위에서 참조할 수 없음)
함수 레벨 스코프
: 함수의 코드 블록만을 지역 스코프로 인정함
//예시
var x = 1;
if (true) { var x = 10; }
console.log(x); //x를 전역 변수로 침 -> 의도치 않은 재할당
렉시컬 스코프(= 정적 스코프)
함수를 어디서 정의했느냐에 따라 상위 스코프 결정
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() { console.log(x); }
foo(); //bar이 전역 함수이므로 1
bar(); //1
변수의 생명 주기
전역 : 애플리케이션의 생명주기와 동일 / js 엔진에 의해 런타임 이전 단계에 실행됨
지역 : 함수의 생명주기와 동일 / js 엔진에 의해 함수 몸체 코드 실행 이전에 실행됨
누군가 참조하고 있으면 할당된 메모리를 계속 확보한 상태로 유지
전역 변수의 문제점
1. 암묵적 결합의 허용
암묵적 결합 : 전역 변수를 어디서든 참조 및 변경 가능
2. 긴 생명 주기
메모리 리소스 오래 소비
변수 이름 중복 가능성 → 의도치 않은 재할당
3. 스코프 체인 상에서 종점에 존재
변수 검색 시 가장 마지막에 검색됨 = 검색 속도가 가장 느림
4. 네임스페이스 오염
파일이 분리되어 있어도 하나의 전역 스코프 공유 → 예상치 못한 결과 초래
전역 변수 사용을 억제하는 법
1. 반드시 사용해야하는 경우 제외 지역 변수 사용
2. 즉시 실행 함수
모든 코드를 즉시 실행함수로 감싸서 전역 변수 사용 제한
3. 네임스페이스 객체
네임스페이스 역할을 할 객체 생성 후 전역 변수처럼 사용하고 싶은 변수를 프로퍼티로 추가
식별자 충돌 방지는 가능하나 네임스페이스 객체 자체가 전역 변수에 해당되어 별로임
모듈 패턴
: 클래스를 모방해 관련 있는 변수와 함수를 모아 즉시 실행 함수로 감싸 하나의 모듈 생성
전역 변수의 억제와 캡슐화 구현 가능
캡슐화 : 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용(= 정보 은닉)
var Counter = (function () {
//private 변수
var num = 0;
//외부 공개
return {
increase() {
return ++num;
},
decrease() {
return --num;
}
};
}());
ES6 모듈
파일 자체의 독자적 모듈 스코프 제공
var로 선언한 변수의 문제점
1. 변수 중복 선언 가능
2. 함수 레벨 스코프
3. 변수 호이스팅
let 키워드
1. 변수 중복 선언 금지
2. 블록 레벨 스코프
모든 코드 블록을 지역 스코프로 인정
3. 변수 호이스팅
선언 단계와 초기화 단계 분리 진행
런타임 이전에 선언 단계가 진행되지만 초기화 단계는 변수 선언문에 도달했을 때 실행됨
초기화 단계 이전에 참조하면 참조 에러 발생
일시적 사각지대 : 스코프 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간
변수 호이스팅이 발생하나 발생하지 않는 것처럼 동작
const 키워드
1. 선언과 초기화
선언과 동시에 초기화 해야 함
블록 레벨 스코프, 변수 호이스팅이 발생하지 않는 것처럼 동작
2. 재할당 금지
3. 상수
상수 표현할 때 const 많이 사용
const로 선언된 변수에 객체 할당 시 값 변경 가능
var vs let vs const
기본적으로 const를 사용하고 재할당이 필요한 경우에 let을 최대한 좁은 스코프로 만든다. var는 사용하지 않는다.
내부 슬롯과 내부 메소드
js 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메로드.
직접 호출하거나 접근하는 방법 제공 X
but, 일부 메소드에 한 해 간접적으로 접근 가능
프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
프로퍼티 어트리뷰트(= 상태) : 값, 값의 갱신 가능 여부, 열거 가능 여부, 재정의 가능 여부
프로퍼티 디스크립터 : 프로퍼티 어트리뷰트 정보 제공
Object.getOwnPropertyDescriptor
메소드를 사용하면 프로퍼티 디스크립터 객체 반환
데이터 프로퍼티와 접근자 프로퍼티
데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티
데이터 프로퍼티가 갖는 어트리뷰트: value, writable, enumerable, configurable
접근자 프로퍼티 : 자체적으로 값을 가지지 않고 접근자 함수로 구성된 프로퍼티
접근자 프로퍼티가 갖는 어트리뷰트: get, setenumerable, configurable
<구분 방법>
일반 객체의 __proto__
는 접근자 프로퍼티
함수 객체의 prototype
은 데이터 프로퍼티
프로퍼티 정의
Object.defineProperty
, Object.defineProperties
메서드로 프로퍼티 정의 가능
이때 일부 프로퍼티 생략하고 정의하면 자동으로 undefined/false로 적용됨
객체 변경 방지
1. 객체 확장 방지 Object.preventExtension
프로퍼티 추가 금지(동적 추가, Object.defineProperty
추가 둘 다)
Object.isExtensible
로 확장 가능 여부 파악
2. 객체 밀봉 Object.seal
읽기와 쓰기만 가능
Object.isSealed
로 밀봉 여부 파악
3. 객체 동결 Object.freeze
읽기만 가능
Object.isFrozen
로 동결 여부 파악
const person = {
name: 'Lee',
address: {city: seoul} //얘는 동결 못함
};
중첩 객체까지 동결시키진 못함
재귀적으로 모든 프로퍼티에 대해 Object.freeze 메소드 호출 필요
Object 생성자 함수
new Object()
로 빈 객체 생성 이후 프로퍼티/메소드 추가로 완성
Object 이외에도 String, Number, Boolean, Function, Array, RegExp, Date
가 있음
생성자 함수
객체 리터럴에 의한 객체 생성의 문제: 구조는 같고 값만 다른데도 계속 처음부터 만들어야한다.
생성자 함수에 의한 객체 생성의 장점: 생성자 함수를 템플릿처럼 사용하여 구조가 동일한 객체를 여러 개 만들 수 있다.
//객체 리터럴로 생성
const circle = {
radius: 5,
getDiameter() { return 2*this.radius }
};
//생성자 함수로 생성
function Circle(radius) {
this.radius = radius;
this.getDiameter = function () { return 2*radius };
}
this
자기 참조 변수, 호출 방식에 따라 동적으로 결정됨
일반함수 - 전역 객체
메소드 - 메소드를 호출한 객체
생성자 함수 - 생성자가 미래에 생성할 인스턴스
new 연산자와 함께 호출 시 생성자 함수로 동작
생성자 함수의 인스턴스 생성 과정
new 연산자와 생성자 함수 호출 시 js엔진이 암묵적으로 인스턴스를 생성/초기화/반환 함
<인스턴스 생성과 this 바인딩>
암묵적으로 빈 객체가 생성되고 이 빈 객체가 인스턴스가 됨 → this에 인스턴스(빈 객체)가 바인딩됨
바인딩
식별자와 값을 연결하는 과정
내부 메서드 [[Call]], [[Construct]]
함수는 객체이나 일반 객체와 달리 호출 가능
함수가 일반 함수로 호출되면 [[Call]] 호출
함수가 new 연산자와 함께 생성자로 호출되면 [[Constructor]] 호출
모든 함수는 callable이지만 모든 함수가 constructor인 것은 아님
<구분>
constructor: 함수 선언문, 함수 표현식, 클래스
non-constructor: 메서드, 화살표 함수
new 연산자
new
연산자와 함께 constructor 호출 시 생성자 함수로 동작
생성자 함수로 호출되면 new.target
이 자기자신을 가리킴
ES6부턴 스코프 세이프 생성자 패턴 사용 가능