전역 객체(Global Object)
모든 객체의 유일한 최상위 객체를 의미하며 일반적으로 Browser-side에서는 window, Server-side(Node.js)에서는 global객체를 의미하게 됩니다.
// in browser console
this === window // true
// in Terminal
this === global // true
- 전역 객체는 실행 컨텍스트에 컨트롤이 들어가기 이전에 생성이 되며 constructor가 없기 때문에 new 연산자를 이용하여 새롭게 생성할 수 없습니다. 즉 개발자가 전역 객체를 생성하는 것은 불가능합니다.
- 전역 객체는 전역 스코프(Global Scope)를 갖게 됩니다.
- 전역 객체의 자식 객체를 사용할 떄 전역 객체의 기술은 생략할 수 있다. 예를 들어 document객체는 전역 객체 window의 자식 객체로서 window.document...와 같이 기술할 수 있으나 일반적으로 전역 객체의 기술은 생략합니다.
그러나 사용자가 사용자가 정의한 전역 객체의 자식 객체 이름이 충돌하는 경우, 명확히 전역 객체를 기술하여 혼동을 방지할 수 있습니다.
function moveTo(url) {
var location = { 'href': 'move to' };
alert(location.href + url);
window.location.href = url;
}
moveTo(~~);
- 전역 객체는 전역 변수를 프로퍼티로 가지게 됩니다. 다시 말해 전역 변수는 전역 객체의 프로퍼티입니다.
- 글로벌 영역에 선언한 함수도 전역 객체의 프로퍼티로 접근할 수 있습니다. 다시 말해 전역 함수는 전역 객체의 메소드입니다.
- Standard Built-in Objects(표준 빌트인 객체)도 역시 전역 객체의 자식입니다. 전역 객체의 자식 객체를 사용할 떄 전역 객체의 기술은 생략할 수 있으므로 표준 빌트인 객체도 전역 객체의 기술을 생략할 수 있습니다.
This
JS의 this는 상황에 따라 다르게 바인딩됩니다. 대표적으로 this에 바인딩되는 값들은 이렇습니다.
- 전역 공간의 this: 전역 객체
- 메소드 호출 시 메소드 내부의 this : 해당 메소드를 호출한 객체
- 함수 호출 시 함수 내부의 this : 지정되지 않음
1번과 2번은 그럴듯한데... 3번이 좀 이상합니다. 함수를 호출했을 떄 그 함수 내부의 this는 지정되지 않습니다. 그리고 this가 지정되지 않은 경우, this는 지정되지 않습니다. 그리고 this가 지정되지 않는 경우, this는 자동으로 전역 객체를 바라보기 때문에 함수를 호출하면 함수 내부에서는 this는 전역 객체가 된다고 정리할 수 있겠습니다.
const cat = {
name: "meow",
foo1: function () {
const foo2 = function () {
console.log(this.name);
};
foo2();
},
};
cat.foo1();
- cat.foo1()메소드 호출 시 내부 함수 foo2가 실행됩니다.
- 함수가 호출됐으므로 foo2내부의 this는 지정되지 않아서 곧 전역 객체를 가리킵니다.
- 전역 객체에 name이란 속성은 존재하지 않으므로 undefine가 뜹니다.
이를 화살표 함수가 해결할 수 있습니다.
const cat = {
name: "meow",
foo1: function () {
const foo2 = () => {
console.log(this.name);
};
foo2();
},
};
cat.foo1();
화살표 함수의 this바인딩
이게 가능한 이유는 화살표 함수에는 this가 아예 없기 때문입니다. 즉, function으로 선언한 함수를 실행할 땐 this가 존재하긴 하지만 값을 지정하지 않는데, 화살표 함수로 선언한 함수에는 this가 없습니다.
JS에서는 어떤 식별자(변수)를 찾을 때 현재 환경에서 그 변수가 없으면 바로 상위 환경을 검색합니다. 그렇게 점점 상위 환경으로 타고 타고 올라가다가 변수를 찾거나 가장 상위 환경에 도달하면 그만두게 되는 것입니다. 화살표 함수에서의 this바인딩 방식도 이와 유사합니다. 화살표 함수에는 this라는 변수 자체가 존재하지 않기 때문에 그 상위 환경에서의 this를 참조하게 됩니다.
더 정확히는, function으로 선언한 함수가 메소드로 호출되냐 함수 자체로 호출되냐에 따라 동적으로 this가 바인딩되는 반면, 화살표 함수는 선언될 시점에서의 상위 스코프가 this로 바인딩됩니다.
const cat = {
name: "meow",
callName: () => console.log(this.name),
};
cat.callName();
이 같은 경우, callName 메소드의 this는 자신을 호출한 객체 cat이 아니라 함수 선언 시점의 상위 스코프인 전역객체를 가리키게 됩니다. 어차피 일반 함수를 사용해도 메소드로 호출하면 자신을 호출한 객체를 가리키기 때문에 화살표 함수를 쓸 필요는 없을 것입니다.
또한 화살표 함수를 생성자 함수로 사용하면 에러가 납니다.
const Foo = () => {};
const foo_ = new Foo();
또한 원래 addEventListener의 콜백함수에서는 this에 해당 이벤트 리스너가 호출된 엘리먼트가 바인딩되도록 정의되어 있습니다. 이처럼 이미 this의 값이 정해져 있는 콜백함수의 경우, 화살표 함수를 사용하면 기존 바인딩 값이 사라지고 상위 스코프(이 경우엔 전역 객체)가 바인딩되기 때문에 의도했던대로 동작하지 않을 수 있습니다. 물론 상위 스코프의 속성들을 쓰려고 의도한 경우라면 사용할 수 있습니다.
const button = document.getElementById("myButton");
button.addEventListener("click", () => {
console.log(this); // Window
this.innerHTML = "clicked";
});
button.addEventListener("click", function () {
console.log(this);
this.innerHTML = "clicked";
});
화살표 함수는 이처럼 함수를 선언할 떄 this에 바인딩할 객체가 정적으로 결정됩니다. 동적으로 결정되는 일반 함수와는 달리 화살표 함수의 this는 언제나 상위 스코프의 this를 가리키는 것입니다. 이를 Lexical this라고 합니다.
function foobaz() {
return {
first: "hyunseo",
last: "lee",
asyncFn: function () {
console.log(this);
setTimeout(() => {
console.log(this.first);
}, 1000);
},
};
}
let foobaz_ = foobaz();
foobaz_.asyncFn();
// { first: 'hyunseo', last: 'lee', asyncFn: [Function: asyncFn] }
// hyunseo
다음 예시를 마지막으로 분석해 보면 foo()라는 함수를 실행하면 객체를 리턴합니다. 리턴되는 객체 안에는 asyncFn()라는 메서드가 있고 이 메서드는 1초 후에 this.first를 출력하는 함수입니다.
변수 bar안에 foo()함수를 실행하여 return되는 객체를 담았고, bar.asyncFn()을 실행하여 1초 후에 this.first값인 'hyunseo'을 잘 출력하는 것을 보실 수 있습니다.
setTimeout()에서의 this는 항상 window를 가리킵니다. 왜냐하면 setTimeout()은 window.setTimeout()이기 때문입니다. 실제로 저 화살표 함수를 일반 함수로 바꿔서 작성한다면 this.first값은 undefined를 출력할 것입니다. window에는 first라는 속성을 지정해 준적이 없기 때문입니다
근데 화살표 함수에서는 this.first를 잘 출력합니다. 화살표 함수에서늬 this는 언제나 상위 스코프의 this를 상속받는다고 하였습니다. setTimeout()의 상위 스코프인 asyncFn()에서 console로 this값을 출력해 보니까 리턴된 객체를 가리키는 것을 확인할 수 있었습니다. 그래서 setTimeout()의 콜백함수로 작성한 화살표 함수는 상위 스코프의 this를 상속받아 리턴된 객체에서 first속성을 찾아서 출력하는 것입니다.
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
console.log(this);
return arr.map(
function (x) {
return this.prefix + " " + x;
}.bind(this)
);
};
const pre = new Prefixer("Hi");
console.log(pre.prefixArray(["Lee", "Kim"]));
다음과 같은 상황에서는 메소드 안에서의 this는 Prefixer객체를 잘 가리키고 있는데, 그 안에서의 function이 global객체를 가리키므로, Prefixer객체에 bind시켜주어 정상 작동 하도록 고쳐주었습니다.
화살표 함수를 사용해서는 안되는 경우
1) 메소드
화살표 함수로 메소드를 정의하는 것은 피해야 합니다. 메소드로 정의한 화살표 함수 내부의 this는 메소드를 소유한 객체, 즉 메소드를 호출한 객체를 가리키지 않고 상위 컨택스트인 전역 객체 window를 가리키게 됩니다.
const person = {
name: "Lee",
sayHi: () => console.log(`Hi ${this.name}`),
};
person.sayHi();
2) prototype
// Bad
const person = {
name: 'Lee',
};
Object.prototype.sayHi = () => console.log(`Hi ${this.name}`);
person.sayHi(); // Hi undefined
객체의 메소드로 정의하였을 때와 마찬가지로 화살표 함수 내부의 this는 사용자 함수를 호출한 Instance를 가리키지 않고 상위 컨택스트인 전역 객체 window를 가리키게 됩니다. 그러므로 prototype에 메소드를 할당하는 경우네는 일반 함수를 할당하는 것이 좋은 방법입니다.
3) 생성자 함수
또한 위에서 말했듯이 화살표 함수는 생성자 함수로 사용할 수 없습니다. 그 이유는 생성자 함수는 prototype프로퍼티를 가지며 prototype프로퍼티가 가리키는 프로토타입 객체의 constructor를 사용합니다. 하지만 화살표 함수는 prototype프로퍼티를 가지고 있지 않습니다.
4) addEventListener함수의 콜백 함수
이 함수의 콜백 함수를 화살표 함수로 정의하면 this가 상위 컨텍스트인 전역 객체 window를 가리킵니다.
'Web > Advanced Web Programming' 카테고리의 다른 글
Advanced Web Programming - lexical scope ( 2 ) (0) | 2022.06.13 |
---|---|
Advanced Web Programming - lexical scope (0) | 2022.06.13 |
고급 웹 프로그래밍 - 정리 - 2 (0) | 2022.06.04 |
고급 웹 프로그래밍 - 기말 정리 - 1 (0) | 2022.06.04 |
Advanced Web Programming - CSS (2) (0) | 2022.04.04 |