var x = "global";
function foo() {
console.log(x);
var x = "local";
}
foo();
console.log(x);
위 코드의 결과를 한 번 예측해보자. 나는 'global', 'global'이라 생각했다.
하지만 결과는 undefined, 'global'이었다. 왜? 함수 내부에서 새로운 x에 대한 선언이 발생을 했고
함수 내부적으로는 선언된 변수를 호이스팅하여 undefined로 초기화했기 때문이다.
여기서 우리는 런타임 이전에 변수 선언부를 초기화하는 과정인 "호이스팅은 스코프 단위로 이루어진다"를 알 수 있다.
그러면 이제 아래 코드의 결과를 예측해보고, 다음 주제로 넘어가자.
var x = "global";
function foo() {
console.log(x);
}
foo();
console.log(x);
암묵적 결합: 전역 변수는 코드의 어느 곳에서도 할당하고, 참조할 수 있는 변수이다. 이는 변수의 유효 범위가 크다
는 것을 의미하고, 이것은 의도치 않게 변수의 상태를 변경하게 만드는 위험성이 있다.
생명주기가 길다
스코프 체인 상에서 가장 마지막에 검색되기에, 전역 변수의 검색 속도가 가장 느리다.
자바스크립트는 파일이 분리되어 있다해도 하나의 전역 스코프를 공유한다. 따라서, 다른 파일 내에서 동일한 이름의 전역 변수, 전역 함수가 존재할 경우 큰 문제가 생길 수 있다. <= var키워드는 변수의 중복 선언을 허용하기에
var 키워드로 정의된 전역변수는 window 객체에 등록된다.
따라서 전역변수보다는 지역변수를 되도록 사용하자. 이를 위한 방법엔 여러가지가 있다.
var MYAPP = {} //전역 네임스페이스 객체
MYAPP.name = 'LEE'
console.log(MYAPP.name)
(function () {
var food = 10;
})();
console.log(food);
클로저
(24장) 개념을 참고한 이 패턴은, 마치 클래스를 모방한 듯이 관련 있느 변수와 함수를 즉시 실행 함수로 감싸 하나의 모듈을 만드는 패턴이다.
물론 전역 네임스페이스의 오염을 막는 기능은 한정적이지만, 정보 은닉을 구현하기 위해 사용할 수 있다.
var Counter = (function () {
var num = 0;
return {
increase() {
return ++num;
},
decrease() {
return --num;
},
};
})();
console.log(Counter.num);
console.log(Counter.increase());
console.log(Counter.decrease());
var키워드는
힘수레벨 스코프
여서 함수 외부에서는 var 키워드 선언 변수가 코드 브록 내에 중복 선언이 가능한 문제반면, let 키워드는
것처럼
동작한다.만약 호이스팅이 진짜로 발생하지 않는다면, 아래 코드에서 첫 번째 콘솔문은 무조건 전역 변수를 참조하여 1을 출력할 것이다.
하지만 ReferenceError가 발생하고, 이는 블록 레벨 스코프의 시작부터, 변수의 초기화단계 시작 지점(변수 선언문)까지 변수를 참조할 수 없는 TDZ(Temporal Dead Zone)구간동안에 해당 변수를 참조하여 발생했기 때문이다.
즉, let 변수도 호이스팅은 발생하나 TDZ로 변수의 참조 구간을 제한한 것이다.
let variableLet = 1;
{
console.log(variableLet);
let variableLet = 2;
}
console.log(variableLet);
//전역 변수
var x = 1;
//암묵적 전역 - 변수 선언 없이 값을 할당한 변수
y = 2;
//전역 함수
function foo(){
...
}
console.log(window.x)
console.log(window.y)
console.log(window.foo)
전역 객체 window의 프로퍼티가 된다. 전역 객체의 프로퍼티를 참조할 때는 window를 생략할 수 있다.
하지만 let 변수는 전역 객체의 프로퍼티가 아니다.
let 과 비슷한 특징을 가지고 있는 const는
let a;//가능
a = 20;
const b //불가능 SyntaxError: Missing initializer in const declaration 발생
const 키워드로 선언된 객체를 변경시키더라도, 변수에 할당된 참조 값
은 변경되지 않는다.
const person = { name: "Seungrok" };
person.name = "Yuri";
내부 슬롯(internal slot) 내부 메서드(internal method)의 개념에 대해 알아보자. 이 장을 공부하면서 Object.create 내장 매서드를 사용하면서 들었던 의문들이 조금씩 해소가 되는 느낌이었습니다.
내부 슬롯, 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 구현하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티(pseudo property)와 의사 메서드(pseudo method)이다. 이중 대괄호로 감싼 프로퍼티 이름들이 내부 슬롯과 내부 메서드이다.
기본적으로는 사용자가 접근할 수 없지만, 일부에 한하여 간접적으로 접근할 수 있는 수단을 제공한다.
const o= {}
o.[[Prototype]] //불가
o.__propto__ //가능
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
사실 객체의 프로퍼티는
이렇게 두 종류로 구분할 수 있다.
아래와 같은 프로퍼티 어트리뷰트를 갖는다.
프로퍼티 어트리뷰트는 자바스크립트 엔진이 관리하는 내부 상태값(meta-property)인 내부 슬롯 [[Value]],[[Writable]],[[Enumerable]],[[Configurable]]이다. 이 내부 슬롯은 직접 접근할 수는 없지만. Object.getOwnPropertyDescriptor
메서드를 사용하여 간접적으로 확인할 수 있다. 이 때 반환되는 프로퍼티 디스크립터 객체의 프로퍼티이름은 value, writable, enumerable, configurable이다.
각 프로퍼티 어트리뷰트에 대해 알아보자.
값 : value, 프로퍼티 키를 통해 값에 접근하면 반환되는 값. 프로퍼티 키를 통해 값을 저장하면 [[Value]]에 값이 저장됨. 만약 객체에 없던 프로퍼티라면, 동적으로 프로퍼티를 생성하고 여기에 값을 저장.
갱신 가능 여부 : writable, 이 값이 false라면, [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티가 된다.
열거 가능 여부 : enumerable, true인 경우에는 for...in 또는, Object.keys메서드로 열겨할 수 있다
재정의 가능 여부 : configurable, false의 경우에는 프로퍼티 값의 삭제, 프로퍼티 어트리뷰트의 값 변경이 금지된다.
아래와 같은 프로퍼티 어트리뷰트를 갖는다.
데이터 프로퍼티의 값을 읽을 때
호출되는 접근자 함수이다.데이터 프로퍼티의 값을 저장할 때
호출되는 접근자 함수이다. 접근자 프로퍼티 키로 프로퍼티 값을저장하면, 프로퍼티 어트리뷰트 [[Set]]이 실행되고 그 결과가 최종 값으로 저장이 된다.const animal = {
name: "Kookoo",
species: "bird",
get fullAcademicName() {
return `${this.name} ${this.species}`;
},
set fullAcademicName(name) {
[this.name, this.species] = name.split(" ");
},
};
console.log(animal.fullAcademicName);
animal.fullAcademicName = "seungrok human";
console.log(animal.fullAcademicName);
새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것을 말한다.
const me = {};
Object.defineProperty(me, "firstName", {
value: "Seungrok",
writable: false,
enumerable: true,
configurable: false,
});
console.log(me)
자사스크립트는 Object생성자 함수 이외에도 String, Number, Boolean, Function, Array 등의 빌트인 생성자 함수를 제공한다.
근데 리터럴보다 불편한 것 같은데 왜 사용하지?
객체 리터럴에 의한 객체 생성 방식의 문제점
객체 리터럴은 단 하나의 객체만 생성하기에, 동일한 프로퍼티를 갖는 객체를 여러개 생성해야 하는 경우 비효율적이다.
반면, 생성자 함수로 객체를 생성하면 구조가 동일한 여러 객체를 간편히 생성할 수 있다.
function Person(name, age) {
this.name = name;
this.age = age;
this.getInfo = function () {
return { name: this.name, age: this.age };
};
}
const aPerson = new Person("Seungrok", 31);
console.log(aPerson.getInfo());
인스턴스 생성(필수) + 생성된 인스턴스를 초기화(인스턴스 프로퍼티 추가 및 초기값 할당 - 옵션)
위 예시에서는 어디에서도 생성된 인스턴스를 반환하는 코드가 보이지 않는다. 그 이유는 자바스크립트 엔진은 new 연산자와 함께 생성자 함수가 호출될 경우, 암묵적으로 인스턴스를 생성하고 반환한다.
객체 인스턴스에 this가 바인딩된다. 바인딩은 '연결'이라고 생각하면 이해가 편하다. 식별자와, 실제 객체를 이어주는 작업이다.
this 바인딩은 런타임 이전에 실행된다.
생성자 내부의 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환이 된다. 만약 함수가 다른 객체를 명시적으로 반환하면 this를 반환하지 않고, 그 객체를 반환한다.
function Person(name, age) {
this.name = name;
this.age = age;
this.getInfo = function () {
return { name: this.name, age: this.age };
};
return {};
}
const aPerson = new Person("Seungrok", 31);
console.log(aPerson);
함수는 호출할 수 있기에, 내부적으로는 [[Call]], [[Construct]] 내부 메서드와 [[Environment]], [[FormalParameters]]를 추가로 가지고 있다.
일반함수로 호출될 때는 [Call]이, 생성자함수로 호출될 때는 [Construct]가 호출이 된다.
function Circle(radius){
if(!new.target){
return new Circle(radius)
}
if(!(this instanceof Circle)){
return new Circle(radius)
}
}
자바스크립트에서 함수는 일급 객체이다. 일급 객체는 값으로서 사용이 될 수 있는데, 이 말은
그러나 앞장에서도 설명했듯이, 기본적으로 callable이기에, 일반 객체와 대비되는 고유의 프로퍼티가 존재한다.
접근자 프로퍼티에 새로운 이름을 할당할 때는, 새로운 이름을 공백 기준으로 split하여 앞을 firstName으로, 뒤 요소를 lastName으로 설정한다.
이후 추가로, uniquePerson 객체는 동결하여 등록된 프로퍼티값들을 읽을수만 있게 만들자.
const me = {};
Object.defineProperty(me, "firstName", {
value: "Seungrok",
writable: false,
enumerable: true,
configurable: false,
});
console.log(me);
delete me.firstName;
console.log(me);
// me.firstName = "Dan";
Object.defineProperty(me, "lastName", {
value: "Yoon",
writable: true,
enumerable: false,
configurable: true,
});
me.lastName = "Park";
console.log(Object.getOwnPropertyDescriptors(me));
delete me.lastName;
console.log(Object.getOwnPropertyDescriptors(me));