스코프 (scope)

피자냠냠(피자냠냠)·2023년 1월 2일
0

JavaScript

목록 보기
12/16

배경지식
let, var, const의 차이를 확실히 기억하자.

  • 함수만드는법 두 가지
    함수 선언식(declaration)과 함수 표현식(expression)을 하나로 통일해서 작성하자
    함수 선언식 표현식 차이점

  • 함수 선언식의 호이스팅을 생각해서 작성한다.
    함수 표현식은 호이스팅 없다.
    함수 선언식(function으로 시작)은 호이스팅있다. (위로 끌려서 올라온다.)

let funcExpressed = 'to be a function';

    console.log(typeof funcDeclared); // 'function'
    console.log(typeof funcExpressed); // 'string'

    function funcDeclared() {
      return 'this is a function declaration';
    }

    funcExpressed = function () {
      return 'this is a function expression';
    };

해설
function으로 시작하는 funcDeclared는 맨위로 올라가서 타입이 function으로 뜬다.
let으로 시작하는 funcExpressed는 맨 밑에 있어서 console.log가 위에 있는 'to be a function'을 불러내서 string이 된다.

스코프의 기초


스코프의 정의

변수 접근 규칙에 따른 유효 범위

어떤 변수는 어디서나 액세스 할 수 있지만,
어떤 변수는 특정 맥락 안에서만 사용할 수 있는 등의 제약이 있다.

변수의 사용범위를 정의한 것이 스코프이다.

스코프의 규칙

  1. 안쪽 스코프에서 바깥쪽 스코프로는 접근할 수 있지만 반대는 불가능하다.
  2. 스코프는 중첩이 가능하다
  3. 특별히 가장 바깥쪽의 스코프는 전역 스코프(Global Scope)라고 부릅니다. 전역의 반대말은 지역(local)으로 전역이 아닌 다른 스코프는 전부 지역 스코프(local scope)이다.
  4. 지역 변수전역 변수보다 더 높은 우선순위를 가진다.

지역변수는 전역변수보다 높은 우선순위를 가진다.

let name = '김코딩';

function showName() {
  let name = '박해커'; // 지역 변수
  console.log(name); // 두번째 출력 : 박해커
}

console.log(name); // 첫번째 출력 :김코딩
showName();
console.log(name); // 세번째 출력 : 김코딩

let const var없이 함수안에서 재할당을 하고 함수 실행하면 전역변수에 영향을 미친다.

let name = '김코딩'; // let const var 다 똑같다.

function showName() {
  name = '박해커'; //
  console.log(name); // 두 번째 출력 : 박해커
}

console.log(name); // 첫 번째 출력 : 김코딩
showName();
console.log(name); // 세 번째 출력 : 박해커

스코프의 종류

함수스코프(function scope)

함수스코프는 함수(function)만을 스코프 영역으로 인정한다.

var만 사용안하면 알 필요없다.

예외
화살표 함수는 블록 스코프로 취급됩니다. 함수 스코프가 아닙니다.

블록스코프(block scope)

함수(function)를 포함if for while 등등등의 모든 {} 가 들어가는 것을 블록 스코프로 한다.

위 그림에서 Function ScopeBlock Scope라고도 할 수 있다.

(lexical scope)

Lexical scope는 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정하는 것이다. 중요한 점은 함수의 호출이 아니라 함수의 선언에 따라 결정된다는 점이다.

var num = 1;

function a() {
  var num = 10;
  b();
  console.log(num)
}

function b() {
  console.log(num);
}

a();  // 1,10
b(); // 1

해설
코드를 보면 a()함수를 호출할 때 b()함수도 호출되는데 변경된 num =10을 가져가지 않는다. 왜냐하면 b에서는 num값이 선언되지 않았기 때문이다.
a()함수는 b()함수를 호출하고 나서 num은 10이 되는걸 볼 수 있다.
즉 a()내에서 num값이 선언될 때 a()내에서만 num값이 변하고 a()내의 함수가 있어도 num값을 가져가지 않는다.

let message = 'Outer';

function shadowGlobal2(message) { // 매개변수도 하나의 변수라는 느낌?
  console.log(message);
}

shadowGlobal2() // undefined
shadowGlobal2('a') // 'a'

해설
shadowGlobal2의 매개변수 message도 새롭게 선언되서 message를 호출하면 'undefined'가 반환된다.

let message = 'Outer';

function shadowParameter(message) { // message가 새롭게 선언
  message = 'Do not use parameters like this!';
  return message;
}

console.log(message) // 'Outer'

해설
'Do not use parameters like this!'가 반환되지 않는 이유는 message가 매개변수()에서 선언되고 {}에서 할당되기 때문이다.

블록 스코프의 규칙

var

var 키워드로 정의한 변수는 블록 스코프를 무시하고, 함수 스코프만 따른다.

//let으로 변수를 선언하면 for문을 못 벗어난다.(=블록스코프 적용)
for(let i=0; i<5; i++){
  console.log(i) // 0,1,2,3,4
}

console.log('final i:', i) // ReferenceError
//var로 변수를 선언하며 for문을 벗어난다.(=블록스코프 무시)
for(var i=0; i<5; i++){
  console.log(i) // 0,1,2,3,4
}

console.log(i) // 5

for문은 함수안에서 i값이 4일때까지 돌다가 밖으로 나와 5가 된다.

예외
화살표 함수는 블록 스코프는 무시하지 않는다.

var에 대해 기억해야 할 것!!

  • (선언 키워드 없는 선언 = var선언)
  • var 선언은 함수 스코프의 최상단에 선언된다.
  • 함수 내에서 선언 키워드 없는 선언은, 함수의 실행 전까지 선언되지 않은 것으로 취급한다.
  • 블록스코프 안 var선언은 선언된 곳에서부터 시작한다.
console.log(i) // error
for(var i=0; i < 5; i++){ 
}

보통 코드를 작성할 때 블록은 들여쓰기가 적용되고, 그 구분이 시각적으로 분명한다. 따라서 많은 사람들은 블록 스코프를 기준으로 코드를 작성하고, 생각하기 마련이다. 그러나 var는 이 규칙을 무시하므로, 코드를 작성하는 사람이 블록 스코프/함수 스코프에 대한 이해가 없으면 코드가 다소 혼란스러울 수 있다.

결론
var 보다는 let, const 으로 변수 선언을 하는 것을 권장한다.


이후에는 읽어보세요

변수선언할 때 주의해야 할 점


window 객체

var로 선언된 전역 변수 및 전역 함수는 window 객체에 속하게 된다.

window 객체는 두 가지 역할
1. 브라우저 안의 모든 요소들이 소속된 객체로, 최상위에 있기 때문에 어디서든 접근이 가능하다고 해서 '전역 객체'라고도 부른다.
2. 일반적으로 우리가 열고 있는 브라우저의 창(browser window)을 의미하고, 이 창을 제어하는 다양한 메서드를 제공한다.

전역객체로써 window

브라우저 콘솔에서 그냥 window를 찍어보면,
window 객체

이런 값을 얻을 수 있고, 펼쳐보면 생각보다 뜸을 들이면서 펼쳐지는데 이 속에 무수하게 많은 프로퍼티들이 존재하는 것을 확인할 수 있다.

우리가 작성하는 코드들은 대부분 다 이 window 객체의 프로퍼티가 된다는 사실을 기억해두자.

무슨 말이냐면,

var myName = 'Bigtop';

function getMyName() {
    return myName;
}
console.log(window.myName); // Bigtop
console.log(window.getMyName()); // Bigtop

console.log(myName === window.myName) // true

이렇게, 우리가 var 키워드로 변수를 선언하거나 함수를 선언하면, 다 이 window객체의 프로퍼티가 된다.

그럼에도 불구하고 우리는 그냥 변수와 함수를 선언하고서 앞에 window를 붙이지 않는데, 말 그대로 window는 전역 객체로 페이지 내에 있는 모든 객체를 다 포함하고 있기 때문에 window는 그냥 생략이 가능한 특징이 있다.

그래서 특별한 경우를 제외하면 사실상 window 객체를 직접적인 사용할 일은 드물다고 볼 수 있다.

let과 const 키워드로 선언한 변수는 블록 스코프이기 때문에 window 객체 내부의 블록에서 선언된 것으로 평가되어 전역 객체의 프로퍼티로 활용되기는 어렵다. 이 부분은 scope의 개념을 잘 이해하고 있다면 충분히 이해할 수 있는 부분이다.

let myName = 'Bigtop';

console.log(window.myName); // undefined

브라우저 창으로써 window

window 객체는 브라우저의 창을 대변하고, 다양한 메서드를 통해 이 창을 제어할 수 있다.

window.close(), window.open() 메서드를 활용하면, 창을 열거나 닫을 수 있고,

window.innerWidth, window.innerHeight 같은 프로퍼티에 접근하면 창의 너비와 높이 등을 확인할 수도 있다.

전역 변수(global variable)

전역 변수는 가장 바깥 스코프에 정의한 변수이다. 따라서, 어디서든 접근이 가능하다

얼핏 "모든 변수를 바깥으로 빼면 스코프 걱정을 하지 않아도 되겠네?" 라는 생각이 들 수도 있다.
그러나, 전역 변수를 많이 만드는 것은 그다지 좋은 선택이 아니다.

보통 애플리케이션을 만들 때에는, 내가 직접 작성하지 않은 수많은 다른 함수와 로직이 포함된다. 너도나도 똑같은 이름으로 전역 변수를 선언하려고 한다면 분명 문제가 발생할 것이다.

이를 side effect라고 한다. 전역 변수를 최소화하는 것은 side effect를 줄이는 좋은 방법이다.

지역 변수 (local variable)
지역 스코프 안의 변수

let과 const를 사용해!!

var는 블록 스코프를 무시하고 재선언해도 에러를 내지 않는다.

전역변수var로 선언하는 경우 문제가 될 수 있다. 기존 window기능을 덮어씌워서 기능을 사용할 수 없게 만든다.

선언 없이 변수를 할당하지 말라!!

선언 없이 변수를 할당하면, 해당 변수는 var로 선언한 전역 변수처럼 취급됩니다.

// 선언없이 변수를 할당하면 자동으로 var가 붙는다.
userName = 'yongju'; // 앞쪽에 var가 생략되어있다.

console.log(userName) // yongju

사실 이런 것은 애초에 브라우저에서 방지해 준다면 더 안전하게 코드를 작성할 수 있을 것이다.

Strict Mode는 브라우저가 보다 엄격하게 작동하도록 만들어준다. 앞서 언급한 것처럼 "선언 없는 변수 할당"의 경우도 Strict Mode는 에러로 판단한다.

Strict Mode를 적용하려면, js 파일 상단에 'use strict' 라고 입력하면 됩니다. (따옴표 포함)

profile
교사에서 개발자로

0개의 댓글