해당 이미지 경로:
Business vector created by macrovector - www.freepik.com
전역변수를 설정할 때, 키워드 ‘var, const 둘 중 무엇을 선택해야 할까,,,’
그리고 var를 사용 할 때, const를 할 때, 그 차이점은 무엇일까?..
const global = 100;
console.log(this.global) // undefined
console.log(global) // 100
다이나믹?하게 변하는 this값을 테스트하려 간단한 코드를 작성해보았다,
문득 전역변수를 정의하는 부분에서 고민이 생겼다,
‘var로 해야할까??,,,아니, const로 하면 어떻게 되지? 무엇으로 해야할까?..var? const?,,,흠...’
코드는 확신?을 갖고 작성해야 하지 않겠는가,
키보드 위에서 나의 손가락이 어디를 눌러야 잠시 방황을 했지만, 그냥 찍기?로 결정했다, const로 결정했다
딱히 이유는 없었다, ‘블로그에서 const를 자주봐서,’ ’ES6가 요즘 대세니깐,,,’
그렇게 한줄을 해결?하고 모든 코드를 완성했다.
감사하게 thist로 참조하는 값들을 의도한대로 확인할 수 있었지만, const로 설정한 전역변수의 값만 계속‘undefined’만 뱉어냈다,
이리저리 코드를 훓어보기도, window로 변수를 찾아봐도 소용은 없었다.
오늘의 글은 이런 const로 전역변수를 사용해보면서 var와 차이점을 이해하고 내가 해답을 찾았던 과정, 느낀점을 공유해보려고 한다.
더불어 전역변수는 window, 즉 this로 접근할 수 있기 때문에, this에 대해서 나눠보고자 한다
이런분이 읽으면 괜찮지 않을까 싶네요
#전역변수를 const키워드로 정의할때 this로 접근되지 않아 고생했던 분
#전역변수를 const키워드로 쓰려는데, 조금 어색?한분, 그래서 어떻게 쓰면 좋을지 그 생각을 알고 싶은 분
#함수 안에서 this값이 결정되는 원리에 궁금하신 분
다음내용을 확인할 수 있습니다,
1.const/var로 전역변수 설정할 때 차이
2.const로 전역변수를 설정할 때, 어떤 마인드셋을 가져야 할까?
2-1.관련 링크(스택오버플로우) 살펴보기
2-2.블록{…}단위의 let/const
2-3.코드에서 전역변수 const와 var의 차이 (feat. window/코드설명)
3.함수 안에서 this값이 설정되는 과정과 코드안에서 this 이해하기
이 글의 결론
글을 쓰면서 var와 const/let 둘 중 무엇을 선택 해야되는 메세지를 전달하지 않는다. 다만 서로 어떤 차이점이 존재하는지를 설명하고 있다.
다만 개인적으로 생각하길 const/let의 출현이 var의 문제점을 보완해 사용하도록 권고하고 있기 때문에 const/let을 추천한다.
var 자체로도 변수를 설정하고 사용하는데 문제는 없지만 '호이스팅'이 발생하는 것에 신경이 쓰이고 엄격하게 '스코프'를 관리하고자 한다면 const/let을 좋은 선택이라고 한다.
예를 들어, for문에서 스코프 이슈를 var+즉시실행함수 조합으로 해결할 수 있지만, 간단하게 let변수 설정으로 조치할 수 도 있기 때문이다.
1.const/var로 전역변수 설정할 때 차이
전역변수를 쓰겠다는 의미는 무엇일까, 그리고 어떻게 사용해야 할까, 그건 코드 어디에서든 접근해 값을 이용하겠단 의도다. 그럼 이것을 어떻게 써야할까, window 객체를 활용하면 된다.
코드 바깥에 변수를 정의하면 이것은 window객체 프로퍼티로 편입된다. 변수명이 ‘foo’라고 가정할때, console.log(foo) 혹은 console.log(window.foo)하면 해당 값을 반환한다.
지금까지 살펴본 이야기는 var 키워드를 사용한 경우다.
(사실 var이든, const든 기본 자바스크립트의 전역변수라는 개념은 모두 동일하다)
반면,전역변수를 const로 정의하면, 해당 변수는 window 객체의 프로퍼티로 설정되지 않아 this로 찾을 수 없다.(그 이유에 대해서 앞으로 이야기 나눠보자!) this는 함수를 호출하는 객체를 말하는데, 함수는 함수자신이 호출되는 형태에 따라 다양한 그것을 값으로 갖는다. 그 중 this는 window를 참조하기도 한다.
전역변수는 전역객체가 갖는 속성이다. 그래서 전역변수는 전역객체를 통해서 값을 접근하기 때문에, 전역변수는 'window.변수', '변수'라는 고정?관념이 있었다. 그것이 키워드 const든 var이든 상관없이 말이다. 하지만 서로 다르다, 매우 다르다. 그 이유에 대해서 하나씩 다뤄보고자 한다.
2.const로 전역변수를 설정할 때, 어떤 마인드셋을 가져야 할까?
2-1.관련 링크(스택오버플로우) 살펴보기
지금까지 전역변수를 const/let키워드로 정의 할 경우, var로 할 경우, 차이점에 대해서 이야기해봤다.
이와 관련해 링크를 공유한다.
위 링크는
다음의 질문과 답을 갖고 있다.
Why do variables declared using the var keyword get defined on window and variables declared with const and let not defined on window?
왜 const/let으로 정의한 변수는 window에서 정의되지 않는가요?
Because the specification says so. If you are asking for the reason behind that decision, then you should reach out to the specification maintainers.
cons 키워드 스펙 자체의 특성 때문(Because the specification says so)이라는 것이다.
그럼 const로 전역변수를 정의한다면,
어떤 관점에서 어떻게 해당변수를 전역이라고 생각할 수 있을까?
어떻게 정의해야할까?
‘What is the proper to write a global const in javascript es6?’
ES6에서 const로 전역변수를 설정하는 적절한 방법은 무엇일까?
‘You just use const at global scope:’
그냥 코드 글로벌 스코프(global scope)에 const로 변수를 작성하면 된다 라는 것이다,,
const aGlobalConstant = 42;
That creates a global constant. It is not a property of the global object (because const, let, and class don't create properties on the global object), but it is a global constant accessible to all code running within that global environment.’
but it is a global constant accessible to all code running within that global environment.
전역환경(within that global environment)에 const로 변수를 설정하면, 그 자체로 전역변수를 설정하는 것이라는 의미로 해석했다.
2-2.블록{…}단위의 let/const
const/let의 핵심은 블록{…} 단위라고 생각한다.
같은 식별자라도 서로 다른 블록{…}에 위치한다면 그것은 다르다 라고 할 수 있다.
즉, 의미의 단위가 블록{…}이기 때문에, 변수가 갖는 의미도 블록{…}을 기준으로 구분할 수 있다.
블록{…} 밖에 있는 것을 전역변수, 안에 있는 것을 지역변수로 생각하자는 것이다.
let과 const가 블록{…}을 기준으로 값의 유효성이 구분되기 때문에 블록{…}밖에 둔다면,
모든 코드들이 블록{…}밖에 둔 값에 접근하지 않겠는가(it is a global constant accessible to all code running within that global environment)
2-3.코드에서 전역변수 const와 var의 차이 (feat. window/코드설명)
2-1, 2-2 내용들을 코드에서 하나씩 확인해보자
다음의 2가지 내용을 확인한다.
1.const는 window(this/전역객체)에 속하지 않는다, 그래서 그냥 해당 변수로 접근해야한다.
2.전역환경에 정의하면 모든 코드들이 접근 할 수 있다.
(but it is a global constant accessible to all code running within that global environment.’)
아래 코드들을 살펴보기 전에 this의 값에 대해서 정리해보자
함수가 호출 될때, 함수의 형태가
1.객체.함수 일 경우(obj.foo() ), foo함수안에 this는 객체, obj를 참조한다
2.함수(); (예: bar(), baz() )와 같이 함수 앞에 객체가 없을 경우, 함수안에 this는 window를 가리킨다
3.함수 안에 정의된 내부함수(아래코드에서 함수baz와 함수 bar)를 호출 할 경우, 함수 안에 this는 window를 가리킨다.
[코드1]
//const로 전역변수 설정
const a = 100;
const b = 200;
const obj = {
a:300,
b:400,
foo: function () {
console.log('-----1.func foo start-----');
console.log('foo함수에서 접근하는 전역변수 a, a로 접근할 경우', a);
console.log('여기서 this는 obj를 의미, this.a는 곧 obj.a임',this.a)
console.log('foo함수에서 접근하는 전역변수 b, b로 접근할 경우', b);
console.log('여기서 this는 obj를 의미, this.a는 곧 obj.b임',this.b)
function baz() {
console.log('-----2. func baz starts-----');
console.log('baz함수에서 접근하는 전역변수 a, 그냥 a로 하면', a);
console.log('여기서 this는 window를 의미, this.a는 곧 window.a임',this.a)
console.log('baz함수에서 접근하는 전역변수 b, 그냥 b로 하면', b);
console.log('여기서 this는 window를 의미, this.b는 곧 window.b임',this.b)
function bar() {
console.log('-----3. func bar starts-----');
console.log('bar함수에서 접근하는 전역변수 a, 그냥 a로 하면', a);
console.log('여기서 this는 window를 의미, this.a는 곧 window.a임',this.a)
console.log('bar함수에서 접근하는 전역변수 b, 그냥 b로 하면', b);
console.log('여기서 this는 window를 의미, this.b는 곧 window.b임',this.b)
}
bar();
}
baz();
},
};
obj.foo();
[코드1,설명]
위 코드를 작성하면 다음과 같은 콘솔값을 볼 수 있다.
-----1.func foo start-----
foo함수에서 접근하는 전역변수 a, a로 접근할 경우 100
여기서 this는 obj를 의미, this.a는 곧 obj.a임 300
foo함수에서 접근하는 전역변수 b, b로 접근할 경우 200
여기서 this는 obj를 의미, this.a는 곧 obj.b임 400
-----2. func baz starts-----
baz함수에서 접근하는 전역변수 a, 그냥 a로 하면 100
여기서 this는 window를 의미, this.a는 곧 window.a임 undefined
baz함수에서 접근하는 전역변수 b, 그냥 b로 하면 200
여기서 this는 window를 의미, this.b는 곧 window.b임 undefined
-----3. func bar starts-----
bar함수에서 접근하는 전역변수 a, 그냥 a로 하면 100
여기서 this는 window를 의미, this.a는 곧 window.a임 undefined
bar함수에서 접근하는 전역변수 b, 그냥 b로 하면 200
여기서 this는 window를 의미, this.b는 곧 window.b임 undefined
1.const는 window(this/전역객체)에 속하지 않는다, 그래서 그냥 해당 변수로 접근해야한다.
=> 키워드 const로 전역변수를 정의했다. foo안에서 this는 obj를 baz와 bar에서는 window를 참조한다.
함수가 호출되는 형태가 다르기 때문에 this값이 다르다.
만약 const로 정의한 전역변수가 window에 속한 프로퍼티라면 this.변수의 값은 출력될 것이다. 그렇지 않으면 해당 프로퍼티가 존재하지 않기 때문에 undefined를 반환할 것이다.
주목해야할 곳은 함수 baz와 bar이다.
(여기서 this는 window다. 함수안의 함수를(=내부함수) 호출하면 this는 window를 참조한다.)
a와 b를 this.a/this.b로 참조할 경우, undefined가 출력된다. 반면 식별자 자체(a,b)로 검색하면 선언한 값이 출력된다.
즉. const는 window의 프로퍼티로 속하지 않는다는 것을 알 수 있다.
2.전역환경에 정의하면 모든 코드들이 접근 할 수 있다.
(but it is a global constant accessible to all code running within that global environment.’)
=> 변수 a와 b는 코드 최상단?(global environment)에 있기 때문에, 모든 코드(함수foo, 함수baz, 함수bar)에서 모두 접근할 수 있다.
각각 함수(foo,baz,bar)에서 100, 200 동일하게 값을 반환하고 있다.
[코드2]
//var로 전역변수를 선언 할 경우
var varDefined = 500;
//const로 전역변수를 선언 할 경우
const constDefined = 100;
const obj = {
valueInObj:300,
trigger:function(){
const btnElem = document.getElementById('btn');
//버튼이벤트가 발생한다.
btnElem.onclick = this.handler.bind(window)
},
handler:function(){
//this는 window를 가리킨다
//this안에 varDefined의 값은?
console.log('this.varDefined의 값은',this.varDefined)
//this안에 constDefined의 값은?
console.log('this.constDefined의 값은',this.constDefined)
//그냥 constDefined의 값은?
console.log('그냥 constDefined로 접근하면',constDefined)
}
}
obj.trigger();
[코드2,설명]
위 코드는 다음과 같은 콘솔값을 확인할 수 있다.
this.varDefined의 값은 500
this.constDefined의 값은 undefined
그냥 constDefined로 접근하면 100
1.const는 window(this/전역객체)에 속하지 않는다, 그래서 그냥 해당 변수로 접근해야한다.
여기서 this는 window를 의미한다.
varDefined는 var 키워드로 만든 전역변수이고 constDefined는 const키워드로 만든 전역변수이다.
window는 this로 치환해 사용할 수 있다.
즉 this를 통해 각각의 변수에 접근하고 해당 변수가 값을 반환하면, 그것을 window에 속한 프로퍼티라고 할 수 있다.
버튼의 클릭이벤트가 발생하면, handler함수가 호출된다. 만약 window에 정의한 전역변수가 존재한다면 선언한 변수값이 출력되고 그렇지 않으면 undefined를 반환한다.
버튼 클릭미를 출력하면 varDefined만 값을 반환하고 constDefined는 undefined를 갖게 된다.
즉 const로 정의한 전역변수는 window에 속하지 않음이 증명됐다.
3.함수 안에서 this값이 설정되는 과정과 코드안에서 this 이해하기
지금까지 const와 window에 대해 다뤘는데,
이번에는 함수안의 this라는 소주제로 이야기를 바꿔보도록 한다.
아래의 내용을 숙지하고 아래 글을 읽자
window는 곧 객체, this이기 때문에 함수에서 해당 this값을 제대로 아는 것이 중요하다고 생각한다
기본개념은 함수안의 this는 함수 앞에 있는 객체를 가리킨다. 함수 앞에 객체가 있는지 없는지를 확인해보자! 예를 들어 함수foo가 있고 그 안에 this.변수와 같은 코드가 있다. 그리고 이를 호출하는 객체obj가 있다고 해보자
- obj.foo()일 경우, 만약 foo안에 this는 객체 obj를 참조한다.
- (객체.)foo()일 경우, 즉, 함수foo를 호출하는 객체가 생략될 경우, 함수foo 안에 작성된 this는 window를 가리킨다.
- foo.call()/bind()일 처럼, call/bind로 함수foo를 호출 할 경우, 함수foo 앞에 (객체obj)가 없기 때문에, 함수foo 안에 정의한 this는 window를 참조한다.
만약 함수foo.call(obj)/bind(obj)와 같이, call/bind에 첫번째 인자로 객체obj를 넣으면 함수foo안의 정의된 this는 객체obj를 가리킨다.
다음의 코드를 통해 함수안의 this값이 어떻게 변하는지 이야기 해보자, 함수가 호출되는 순서에 따라 설명하려고 한다.
const obj= {
trigger:function(){
this.handler.bind(window)
}
handler:function(){
console.log(this…)
}
}
obj.trigger()
obj.trigger()
객체 obj가 trigger함수를 호출했다.
그러면 trigger함수 입장에서 trigger함수 안에서 정의된 this는 obj를 가리킨다.
함수안의 this는 함수 앞에 객체를 가리킨다 하지 않았나, 그래서 trigger함수안에 this는 obj를 참조하고 있다.
함수가 호출되면 해당 함수의 실행문맥이 생성된다. 함수의 파라미터와 this값은 실행문맥 안으로 들어가는 값이다.
함수가 호출되는 형태로 객체가 정의되면, 그 객체가 실행문맥 안에 "this 바인딩"에 키/밸류로 this값이 설정된다.
함수안에서 this는 this바인딩의 값의 그것을 가리킨다.
[#단계1]
여기서 trigger함수 실행문맥의 this 바인딩은 obj다
trigger함수 안에 this는 obj를 참조하고 있다.
trigger함수안에 this는 obj를 참조한다.
객체obj가 trigger함수를 호출했기 때문에 trigger함수 안의 this는 obj를 의미한다.
그런데,
함수 trigger안에 this.변수 처럼 this가 보이지 않는다.
[#단계2]
trigger:function(){ btnElem.onclick=this.handler.bind(window)
}
document.getElementById('')를 통해 버튼을 가져오고, 그 값을 btnElem라고 정의했다.
버튼 btnElem을 클릭(onclick)하면,
해당 (클릭)이벤트를 리스닝할 이벤트 핸들러가 작동한다.
이벤트 핸들러는 이벤트가 발생하면 바로 호출된다.
여기서 이벤트 핸들러는 this.handler.bind(window) 이다.
이벤트를 처리하는 (이벤트) 핸들러의 this는 이벤트를 발생한 해당 돔을 this로 가리킨다.
그래서 이제 this.handler의 this는
<button id="btnElem"></button>을 의미한다
[#단계3]
그런데! bind(window)가 있다!!
bind()는 첫번째 인자로 넣은 값이 this가 된다.
여기에서 window를 첫번째 인자로 넣었기 때문에 this는 window가 된다.
button id="btnElem.handler 였는데, bind(window)가 더해져 button id="btnElem가 window를 가리킨다.
이벤트 핸들러, this.handler.bind(window)에서 bind(window)를 제외한 this.handler로 변경 후 코드를 실행해 보자, 그때 this는 button이다.
즉,
this.handler.bind(window)의 this는 window를 가리킨다.
this는
obj => 'button id="btnElem"'=>window로 변경됐다.
중요!![#단계4]
다음의 개념을 잊지말자
함수안에서 정의(사용)하는 this는 함수를 호출한(함수 앞에 위치한) 객체를 가리킨다
함수 handler 안에는 정의한
this.varDefined
this.constDefined에서
this는 window를 가리킨다.
어떻게 this가 window 를 가리킬까?
차근차근 하나씩 알아보자
버튼이 클릭되기 전, bind(window)를 통해 this가 window로 바인딩된 함수, 즉 새로운 handler가 반환된다.(새롭다 라고 말한 이유는, bind함수는 새롭게 객체를 설정한 함수를 반환하기 때문이다.)
그리고 실제 버튼이 클릭 되면(=클릭 이벤트가 발생하면), this가 window인 handler함수가 호출된다
(bind()는 새롭게 바인딩한 함수를 반환하고 그 이후에 호출되는 2가지 단계를 갖는다.)
2의 내용을 함수 handler 입장에서 해석해보자
함수 handler는 bind()를 통해 객체를 window를 고정하게 됐다. 만약 bind()가 없다면 this는 버튼을 가리켜야 한다.
2번 내용에서 함수 handler자신은, 함수 앞에 객체가 window임을 /객체 window가 자신(함수 handler)을 호출함을 알게된다.
즉. 함수 handler 자신을 호출하는 객체는 window가 된다.
(이때 this 바인딩 값이 설정된다.)
3의 내용을 함수 handler 안에서 지켜봐보자
함수 안의 this는 함수 앞에 위치한 객체를 참조한다고 했다.
예를 들어 obj.foo()할때, 함수 foo안에 this는 obj를 참조한다.
다시 함수 handler 안의 시점에서 상황을 보자
함수 앞에 어떤 객체가 있는가?
window 아닌가? 그래서 함수 안에 this는 window를 참조한다.
이제 함수 handler 안에 정의한 모든 this는
window를 참조하게 된다.
그래서 결론적으로
this.varDefined와
this.constDefined는
각각
window.varDefined
window.constDefined 로 치환할 수 있다.
실제 코드는 다음과 같다.
handler:function(){
console.log(‘this.varDefined의 값은’, window.varDefiend);
console.log(‘this.constDefined의 값은’, window.constDefiend);
}
this는 함수호출 형태에 따라 유동적으로 변경된다.