프로그래밍을 할 때, 우리는 범위를 고려해야한다. 왜냐하면, 어디까지가 잘 작동하는 코드이고, 어디까지가 잘 작동되지 않는지 파악할 수 있다.
즉, 잘 작동하는 코드를 작성하는데 있어서 반드시 알아야 할 기초이다.
javascript에 있어서 scope의 의미는 무엇인지 확인해보자. 그리고 프로그래밍에 있어서 왜 중요한지 보자.
먼저 다음 예시 코드를 가지고 문제를 통해 확인해보자.
Q : greetSomeone()과 fistName 실행의 결과는?
let greeting = "Hello";
function greetSomeone(){
let fistName = "Josh";
return greeting + " " + firstName;
}
greetSomeone(); // "Hello Josh"
firstName; // Reference Error
greetSomeone()
의 결과는 "Hello Josh"이다.
firstName
의 결과는 레퍼런스 에러가 뜨게된다.
밖에 적혀있는firstName
은 greetSomeone()
안에 접근을 할 수가 없다.
즉, 우리 눈에는 보이지 않는 무언가 바운더리가 있음을 알 수 있다.
function greetSomeone(){ // local scope
let fistName = "Josh";
return greeting + " " + firstName;
}
변수 firstName
에 접근 할 수 있는 범위가 존재한다.
function greetSomeone(){...}
는 local scope로 local scope 안쪽에서 선언된 변수는 밖에서 사용 할 수 없다.
[그림으로 확인해 보자.]
자, 이제 scope라는 것이 무엇인지 정의를 하자면, 아래와 같다.
case 1 :let
선언이 있는 경우
let name = "Richard";
function showName() {
let name = "Jack"; // 지역 변수
// showName 함수 안에서만 접근 가능
console.log(name); // ???
}
// 코드에서 순서대로 콘솔에 출력되는 결과는?
console.log(name); // Richard
showName(); // Jack
console.log(name); // Richard
case 2 : let
선언이 없는 경우
let name = "Richard";
function showName() {
name = "Jack"; // 전역 변수
// 선언(let)이 없기 때문에, 바깥 scope에 있는 name이라는 변수를 가져옵니다
console.log(name); // ???
}
// 코드에서 순서대로 콘솔에 출력되는 결과는?
console.log(name); // Richard
showName(); // Jack
console.log(name); // Jack
if(true){ // 중괄호 시작
console.log("i am in the block")
} // 끝
for(let i=0; i<10; i++){ // 중괄호 시작
console.log(i);
} // 끝
{ // 중괄호 시작
console.log("it works")
} // 끝
보통의 경우는 scope를 구분 할 수 있는 단위로서 생각 할 수 있는데, 그럼 function scope와 Block scope의 차이점이 무엇인지 다음 문제를 통해 확인해 보자.
for(let i=0; i<5; i++){
console.log(i); // 다섯 번 interation
}
console.log("final i: ", i); // reference Error
// block 범위를 벗어나는 즉시 변수를 사용할 수 없다
reference Erorr가 뜨는 것은 정의된 i
를 찾을 수 없다는 것인데, 즉, i
는 특정한 block 내에 범위가 한정되어 있다라는 것을 알 수 있다.
reference Erorr가 뜨는 이유는 위의 주석처럼 block 범위를 벗어나는 즉시 변수를 사용할 수 없기 때문이다.
그럼 let
키워드 대신 var
키워드를 이용해 변수를 선언해서 다시보자.
// function scope
for(var i=0; i<5; i++){ // block
console.log(i); // 다섯 번 interation
}
console.log("final i: ", i); // 5
// 범위를 벗어나도(같은 function scope에서는) 시용이 가능하다.
여기서 i
는 block 안쪽에서만 사용 할 수 있는 것이 아니라, 하나의 function scope 안쪽에서 사용이 가능하다.
위 코드의 function 구분이 없는 관계로 코드 자체로 하나의 function scope를 가지고 있다.
여기서 우리는 let
, const
, var
이 세가지 차이점의 대해 알아둘 필요가 있다.
let // 유효범위: block / 값 재정의: 가능 / 재선언: 불가능
const // 유효범위: block / 값 재정의: 불가능 / 재선언: 불가능
var // 유효범위: function / 값 재정의: 가능 / 재선언: 가능
var
의 작동원리를 통해 function scope와 Block scope가 무엇인지 알아보자.var
를 통한 변수 선언
function greetSomeone(firstName){
var time = "night";
if(time === "night"){
var greeting = "Good night";
}
return greeting + " " + firstName;
}
greetSomeone("steve"); // "Good night steve"
let
을 통한 변수 선언
function greetSomeone(firstName){
let time = "night";
if(time === "night"){ // 이 Block 안의 변수 greeting은 참조 할 수 없다.
let greeting = "Good night";
} // 여 block 안의 greeting 변수는 이 안쪽에서만 사용이 가능하다.
return greeting + " " + firstName;
}
greetSomeone("steve"); // Reference Error
let
을 통해 변수를 선언했을 때 아래와 같이 콘솔 결과를 확인 할 수 있다.greeting
변수를 찾을수 없다고 나온다.greeting
변수는, 해당 Block 안쪽에서만 접근이 가능하다.function outerFn(){
let outerVar = "outer";
console.log(outerVar);
function innerFn(){
let innerVar = "inner";
console.log(innerVar);
}
}
let globalVar = "global";
outerFn();
innerFn 함수에 접근 할 수 있는 Scope는 3개다. 아래의 그림을 통해 확인해 보자.
function outerFn(){
let outerVar = "outer";
console.log(outerVar);
function innerFn(){
let innerVar = "inner";
console.log(innerVar);
}
return innerFn;
}
outerFn(); // outer
// console 창에 outerVar의 값이 찍히고,
// 아직 실행되지 않은 함수 innerFn가 return되는 것을 확인 할 수 있다.
다음의 경우 각각 콘솔에 어떻게 찍힐까?
outerFn()(); // "outer" "inner"
let innerFn = outerFn(); // "outer"
innerFn(); // "inner"
function adder(x){
return function(y){
return x + y;
}
}
adder(2)(3); // 5
let add100 = adder(100); // x의 값을 고정해 놓고 재사용 가능
add100(13) // 113
add100(10) // 110
let add5 = adder(5);
add5(2) // 7
const adder = x => y => x + y;
adder(2)(3); // 5
let add100 = adder(100); // x의 값을 고정해 놓고 재사용 가능
add100(13) // 113
add100(10) // 110
let add5 = adder(5);
add5(2) // 7
function htmlMaker(tag){
let startTag = "<" + tag + ">";
let endTag = "</" + tag + ">";
return function(content){
return startTag + content + endTag;
}
}
let divMaker = htmlMaker("div");
divMaker("code"); // <div>code</div>
divMaker("states"); // <div>states</div>
let h1Maker = htmlMaker("h1");
h1Maker("HeadLine"); // <h1>HeadLine</h1>
function makeCounter(){
let privateCounter = 0;
return {
increment: function() {
privateCounter++;
},
decrement: function() {
privateCounter--;
},
getValue: function() {
return privateCounter;
}
}
}
let counter1 = makeCounter();
counter1.increment();
counter1.increment();
counter1.getValue(); // 2
let counter2 = makeCounter();
counter2.increment();
counter2.decrement();
counter2.increment();
counter2.getValue(); // 1
// 두 Counter에 각기 다른 privateCounter를 다루면서,
// privateCounter를 밖으로 노출시키지 않는다.
// 위의 return 값을 변수 obj에 담아서도 할 수 있다.
function makeCounter(){
let privateCounter = 0;
let obj = { // obj라는 변수에 담아서 return 해줄수도 있다.
increment: function() {
privateCounter++;
},
decrement: function() {
privateCounter--;
},
getValue: function() {
return privateCounter;
}
}
return obj; // 변수 obj return
}
let counter1 = makeCounter();
counter1.increment();
counter1.increment();
counter1.getValue(); // 2
let counter2 = makeCounter();
counter2.increment();
counter2.decrement();
counter2.increment();
counter2.getValue(); // 1
비슷하지만 다른 에제도 있다. 보고 참고만해서 알고있자.
function makePayment(){
let type = "현금"; // 반드시 "현금" 또는 "카드"여야 함
return {
payWithCash: function(amount){
type: "현금";
console.log(type + "으로" + amount + "만큼 지불합니다.");
},
payWithCard: function(amount){
type: "카드";
console.log(type + "으로" + amount + "만큼 지불합니다.");
}
}
}
// 위의 return 값을 변수 obj에 담아서도 할 수 있다.
function makePayment(){
let type = "현금"; // 반드시 "현금" 또는 "카드"여야 함
let obj = { // obj라는 변수에 담아서 return 해줄수도 있다.
payWithCash: function(amount){
type: "현금";
console.log(type + "으로" + amount + "만큼 지불합니다.");
},
payWithCard: function(amount){
type: "카드";
console.log(type + "으로" + amount + "만큼 지불합니다.");
}
}
return obj; // 변수 obj return
}