호이스팅은 해당하는 스코프에서 변수들을 가장 높이 끌어올리는 작업입니다. 호이스팅이 되는 경우는 해당 스코프에서 선언하거나 정의된 코드 순서와는 상관없이 가장위에서 해당 변수와 함수를 호출해도 작용합니다.
하지만 이러한 호이스팅은 다양한 조건에서 다르게 적용됩니다.
함수 선언식 : 호이스팅이 됩니다. strict 모드에서 블록스코프에 적용됩니다.
var 선언 변수 : 호이스팅이 되지만 선언된 순서와 상관없이 해당변수에 접근하게되면 undefined 라는 변수가 나옵니다. 이런 경우는 개발자가 코드에 버그가 있거나 로직이 잘못 짜여졌나 헷갈리게 만드는 주범입니다. 이런 경우때문에 var 사용을 하지말라고 합니다.
let & const 변수 : 호이스팅이 됩니다. 하지만 끌어올려지면서 값이 undefind 로 초기화 되지 않아 이경우 코드에 상관없이 위에서 해당 변수에 접근하면 ReferenceError
가 나옵니다. 코드가 사용되는 Temperal Dead Zone (TDZ)
의 위에 해당 변수를 선언해야 접근이 가능합니다.
arrow-function : 변수 선언 방식에 따라 같은 형태로 호이스팅이 적용됩니다.
❓호이스팅은 왜 사용할까?
실제 선언전에 함수를 사용하기위해 이러한 개념이 도입되었는데 이런 개념은 상호 재귀 함수나 일부 프로그래밍 기술에 꼭 필요한 개념이다. 그 부산물 적인 개념으로 var
과 같은 호이스팅시 undefined
가 선언되는 형태가 탄생했는데 이제는 너무나 안좋은 개념이다. 해당 개념을 무시하기 위해서 let
과 const
와 같은 선언방식이 등장했다.
TDZ
는 ES6에서 소개된 개념으로 변수가 선언되지 않은 상태로 쓰이는 것을 방지하기 위한 개념입니다. 이 개념은 에러를 캐치해내고 좀더 이러한 버그를 피할 수 있게 합니다.
실제로 작동시 const
변수를 만들어 냅니다. const
변수는 선언시외에 재할당이 불가능하기 때문에 TDZ
존 전에 변수를 만들어내어 undefined
값을 가지지 못하게 합니다.
💡 위와 같은 이유 때문에 var
대신 const
, let
과 같은 변수 선언 방식을 사용하며 변수를 사용할 때에는 const
를 사용하고 꼭 변경될 변수일 경우에는 let
을 사용합니다. 또한 해당 변수가 객체의 속성일 경우 객체를 const
로 선언하고, 이러한 변수들을 선언할 때에는 코드의 맨위에 선언을 해야 훨씬 클린한 코드가 됩니다. 또한 함수도 코드의 맨위에 선언을 해서 선언 후에 코드를 사용해야 합니다. 그래도 선언적인 오류를 피할 수 있습니다. 사실 이런 부분들은 자바스크립트에서 기본이라고 할수 있습니다.
❗️ 추가로 var
변수를 전역 스코프에서 선언한다면 var
변수는 놀랍게도 window
라는 전역객체에 선언되어 버립니다! 하지만 const
, let
은 window
객체에 정의되지 않습니다. 이러한 var
의 특성은 window
객체를 사용할 때문제가 될수 있습니다.
기본적으로 특별한 변수로서 arrow function
을 제외한 모든 실행컨텍스트에서 생성됩니다. 즉, arrow function
을 제외한 모든 함수에서this
변수가 생성됩니다. this
는 해당기능을 가지는 컨텍스트를 가르킵니다.
this
는 항상 같은 값을 가지지 않습니다. this
의 기능에 따라 다른 값을 가집니다.
모든 함수에 대하여 this
는 생성되므로 함수가 사용되는 조건에 대하여 this
봅니다.
객체의 메소드로서 함수가 사용되어질 경우의 this
는 해당 메소드를 소유하고 있는 객체를 가르킵니다. 이때의 this
는 해당 객체를 가리킴으로서 그자체의 기능을 할수 있습니다.
정상적인 방법으로 함수를 호출하는 경우 : this
는 undefined
로 정의됩니다.
'use strict`
const testFunc = function (){
const inner = function () {
console.log(this)
// undefined
}
}
const testFunc = function (){
const inner = function () {
console.log(this)
// window
}
}
(strict mode
일 경우에만 ❗️ strict mode
가 아닌경우 window
객체를 가리켜 버리는 아주 안좋은 코드가 나온다. this의 스코프 체이닝에 의해 전역공간에서의 this는 window를 가르키기 때문이다. )
arrow-function : 이 함수는 실행컨텍스트 자체에서 this
의 기능을 갖지 않는다. 이안에서 this
를 사용하면 문자로서 this
라는 변수를 스코프로 찾기 시작하면서 스코프 체이닝으로 가장 가까운 관계에 있는 실행컨텍스트의 this
를 불러온다. 즉, 부모 함수가 arrow-function
이 아니라면 부모 함수의 this
를 가르킨다. 이를 lexical this
라고 따로 부르기도 한다.
이벤트 리스너 : 이때의 this는 핸들러 함수 안에서 쓰일 수 있는데, 핸들러 함수가 부착된 DOM을 가르킵니다.
이외의 다른 this 사용법들이 존재한다. new
, call
, apply
, bind
...
예) 전역 스코프에서 this를 사용하면 this는 window 라는 전역객체를 가르킵니다. 전역 컨텍스트에 window 라는 전역객체가 기본으로 실행되기 때문입니다.
❗️ 객체의 메소드에는 arrow-function을 사용하면 안된다.
arrow-function을 메소드로 사용한다면 해당 함수안에 this를 사용할 경우 해당 객체를 가리키는 것이 아니라 스코프체이닝으로 올라가서 해당 객체보다 상위에 있는 this를 가리키기 때문이다.
보통 대부분의 경우 이중객체가 아닌이상 전역객체인 window를 가르키게되고 이는 심각한 오류를 일으킨다. this를 사용하지 않는경우에도 arrow-function을 사용하지 말아야한다. 습관이 되어 arrow-function을 사용하지 않는다면 이런 종류의 버그를 예방할 수 있다.
❗️ 추가로 window 전역객체 같은경우 객체이기 때문에 변수를 선언하지 않더라도 접근이 가능해져 버린다. 객체에 변수가 선언이 안되어도 접근을 하면 해당객체에 속성을 추가해서 undefined를 생성해버리기 때문이다.
const testObj = {}; console.log(testObj.a); // undefined
❗️ 객체의 메서드 내부에서 함수 선언후 해당 함수를 호출 하는 경우
→ 메서드 내부에서 선언된 함수는 정상적인 호출로 인해 메서드 내부에서 실행될때 해당 함수는 this에 undefinded를 할당 합니다. 원하지 않는 결과가 나올 수 있습니다.
const testObj = { name: 'doodream', printName: function () { const testFunc = function () { console.log(this.name); // this = undefined }; testFunc(); }, }; console.log(testObj.printName()); // Uncaught TypeError: Cannot read property 'name' of undefined
위와 같은 방식을 피하는 방법으로는 변수를 따로 지정해주는 것입니다. 관습적인 방법으로 self라는 변수로 지정해서 this를 넣습니다. 이름만 그런것이지 일반적인 변수 입니다.
const testObj = { name: 'doodream', printName: function () { const self = this; const testFunc = function () { console.log(self.name); // doodream; }; testFunc(); }, }; console.log(testObj.printName());
ES6 이전의 해결법이 위의 방식이라면 ES6이후 더 멋진 해결법이 있습니다.
💡 객체의 메소드 안에서의 함수 정의는 arrow-function을 사용해보자.
arrow-function은 this 값이 정의되지 않기 때문에 this를 사용하는 상위 스코프에서 찾아나갑니다. 이경우에는 객체를 this가 받기 때문에 깔끔한 해결법이 됩니다.const testObj = { name: 'doodream', printName: function () { const testFunc = () => { console.log(this.name); // doodream; }; testFunc(); }, }; console.log(testObj.printName());
실행컨텍스트 생성시 arrow-function을 제외한 실행컨텍스트에는 arguments 키워드가 생성된다고 하였습니다. 이 키워드는 함수의 매개변수를 전달할때 전해지는 배열입니다.
const testFunc = function (a, b) {
console.log(arguments);
return a + b;
};
testFunc(1, 2, 3, 4, 5);
//Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
위 코드와 같이 기존의 함수에서 보다 더 많은 양의 매개변수를 전달했음에도 함수의 실행컨텍스트에는 arguments 키워드에 의해 모든 매개변수들이 받아진 모습입니다. 이러한 arguments 키워드는 arrow-function에서는 찾을 수 없습니다.
const testFunc = (a, b) => {
console.log(arguments);
return a + b;
};
testFunc(1, 2, 3, 4, 5);
//Uncaught ReferenceError: arguments is not defined
이러한 arguments 키워드는 현대 자바스크립트에서 더이상 중요한 키워드는 아닙니다. 더 멋진 방식의 매개변수 처리방식이 있어서 중요하지는 않으나 이러한 arguments 키워드의 존재는 알아야합니다.