스코프 [R]

nada_1221·2022년 7월 11일
0

공부

목록 보기
8/49

JavaScript 에서 scope는 무엇일까?


컴퓨터 공학, 그리고 JS에서의 스코프는 "변수의 유효범위"로 사용된다.

let name = 'nada'; //바깥쪽 스코프
if (name){  // 안쪽 스코프 
  let message = `Hello, ${name}!`;
  console.log(message); 
  // 'Hello, nada!' <-- 처음 선언된 name을 message가 가져와서 적용시켰으므로 정상 작동
}

console.log(message) 
// Uncaught ReferenceError : ~~ 
// message 라는 변수 자체가 중괄호 안쪽에 선언되어 있으므로, 바깥쪽에서는 접근 할 수 없다.

이처럼 변수에 접근할 수 있는 범위가 존재한다.
중괄호(블록) 안쪽에 변수가 선언 되었는가, 바깥쪽에 변수가 선언되었는가가 중요하다.
이 범위를 스코프라고 부른다.

let word = 'Hello'
function wordSomeone (){
  let firstName = 'na';
  return word + ' ' + firstName;
}

console.log(wordSomeone());// = 'Hello na'
//word 변수는 바깥에 정의되어 있으므로, 함수 안쪽에서 사용할 수 있다. 
//따라서 word 변수와 firstName 변수의 조합에 의해 문자열이 출력된다.
console.log(firstName);// ReferenceError 
//firstName 변수는 함수 안쪽에 정의되어 있으므로 함수 바깥쪽에서는 접급이 불가능하다.

범위가 중괄호(블록) 또는 함수에 의해 나눠지고, 그 범위를 스코프라고 부른다고 했다.
여기서 우리가 알 수 있는 규칙은 바로 다음과 같다.

  • 바깥쪽 스코프에서 선언한 변수는 안쪽 스코프에서 사용 가능하다.
  • 반면에, 안족에서 선언한 변수는 바깥쪽 스코프에서는 사용할 수 없다.

스코프는 "변수 접근 규칙에 따른 유효 범위" 이다.
안쪽 스코프에서 바깥쪽 스코프로는 접근할 수 있지만 반대는 불가능하다.

이것이 첫 번째 규칙.

두번째 규칙은, 스코프는 중첩이 가능하다는 것.

스코프는 마치 중첩된 울타리와 같다.
ex)

가장 바깥쪽의 스코프는 전역 스코프(Global Scope)라고 부르며, 전역의 반대말은 지역(local)으로 전역이 아닌 다른 스코프는 전부 지역 스코프(Local Scope) 이다.

지역 스코프에 선언한 변수는 지역 변수, 전역 스코프에서 선언한 변수는 전역 변수이다.

스코프 규칙에서 또 하나 기억해야 할 규칙은, 지역 변수는 전역 변수보다 더 높은 우선 순위를 가진다.

let name = 'nada';

function showName(){
  let name = 'witch'; //지역 변수
  console.log(name); //두 번째 출력 
  //왜 두 번째 출력인가? --> 함수를 아직 불러오지 않았기 때문에 불려지기 전까지는 console.log는 실행되지 않는다.
}

console.log(name);//첫 번째 출력
showName(); // 여기서 함수를 불렀기 때문에 두 번째 출력으로 인정되는 것이다.
console.log(name); // 세 번째 출력

// --> nada witch nada

첫 번째 출력은 첫째 줄에서 전역 변수로 선언된 name을 가져온다. 이는, showName 함수 안쪽에 선언된 지역 변수 name은 애초에 스코프 규칙에 의해 접근할 수 없기 때문이다. 따라서 "nada"를 출력한다.

반면 두 번째 출력은 함수 안에서 선언한 name이라는 지역 변수에 접근하고 있다. 변수 이름이 전역 변수와 똑같지만, 지역 변수가 전역 변수보다 우선수위가 높으므로, 지역 변수 name이 출력되는 것이다. 동일한 변수 이름으로 인해 바깥족 변수가 안쪽 변수에 의해 가려지는 이러한 현상을 쉐도잉(variable shadowing)이라고 부른다. 따라서 두 번재 출력은 "witch" 이다.

세 번째 출력은 첫 번째 출력과 마찬가지로 전역 변수 name을 출력한다. 지역 변수에 선언된 name변수는 안쪽 스코프이므로 접근이 불가능하다. 따라서 "nada"를 출력한다.

let name = 'nada';

function showName(){
  name = 'witch'; 
  console.log(name); //두 번째 출력
}

console.log(name);//첫 번째 출력
showName();//이때부터 name은 'nada' 에서 'witch'로 바뀐다.
console.log(name); // 세 번째 출력

// --> nada witch witch

앞서 문제와는 다르게, 세 번째 줄에서 let 키워드를 사용한 선언이 존재하지 않는다.
이는, 'witch'라는 값으로 할당하고 있는 name 변수는 전역에 선언된 name 변수를 그대로 사용하겠다는 의미이다.

지역 스코프에서 새로 선언되지 않으면 그냥 같은 변수이다.

따라서 showName함수가 실행되기 전, 처음에는 'nada'를 축력하고, 그 이후에는 전역변수 name의 값이 바뀌기 때문에 두 번째 및 세 번째 출력에 'witch'가 출력된다.


스코프는 두 가지 종류가 있다. 하나는 block scope라고 부르며, 중괄호를 기준으로 범위가 구분된다.

또 다른 스코프 종류로는 function scope가 있다. function 키워드가 등장하는 함수 선언식 및 함수 표현식은 함수 스코프를 만든다.

여기서 한 가지 유의해야 할 점이 있다.

let name = user =>{ //user 부터 } 까지 블록 스코프
  return user.name;
}

let name = function (user) { //function 부터 { 까지 함수 스코프
  return user.name;
}

//같은 함수여도, 화살표 함수를 사용하면 블록 스코프로 취급된다.

화살표 함수는 블록 스코프로 취급된다. 함수 스코프가 아니다.

함수 스코프와 블록 스코프는 논리적인 구분 외에도 코드를 작성할 때 기억해야 할 다른 점이 몇가지 존재한다.

for(let i=0; i<5; i++){
  console.log(i); // 다섯번 반복됨
}
console.log('final i', i); // ReferenceError

블록 스코프 안에서 정의된 변수 i 는 블록 범위를 벗어나는 즉시 접근할 수 없습니다. 따라서 결과로는 ReferenceError가 나오게 됩니다.

for (var i=0; i<5; i++){ // let 대신 var 사용
  console.log(i); // 다섯번 반복됨
}
console.log('final i', i); // = 5
// var 키워드는 for문이 만들어낸 블록 스코프를 무시한다.

var 키워드로 정의한 변수는 블록 스코프를 무시하고, 함수 스코프만 따른다. 그러나, 모든 블록 스코프를 무시하는 것인 아니다. 화살표 함수의 블록스코프는 무시하지 않는다.

함수 스코프는 함수의 실행부터 종료까지이고, var 선언은 함수 스코프의 최상단에 선언된다. 선언키워드 없는 선언은 최고 스코프에 선언된다.
함수 내에서 선언 키워드 없는 선언은, 함수의 실행 전까지 선언되지 않은 것으로 취급한다.

보통 코드를 작성할 때 블록은 들여쓰기가 적용되고, 그 부분이 시각적으로 분명하다. 따라서 많은 사람들은 블록 스코프를 기준으로 코드를 작성하고, 생각하기 마련이다.

그러나 var는 이 규칙을 무시하므로, 코드를 작성하는 사람이 블록 스코프/ 함수 스코프에 대한 이해가 없으면 코드가 다소 혼란스러울 수 있다.

따라서, var보다는 let으로 변수 선언을 하는 것을 권장한다.

var 키워드 보다 let 키워드가 안전한 이유는 또 있다.
let 키워드는 재선언을 방지한다. 실제로 코딩할 때에 변수를 재선언 해야 할 필요가 있을가?
대부분 이런 경우는 버그다.

const라는 키워드도 있다. 변하지 않는 값, 곧 상수(constant)를 정의할 때에는 const를 이용한다.

const는 값의 재할당이 불가능하다. 값을 재할당할 경우 TypeError를 내므로, 의도하지 않은 값의 변경을 막을 수 있다.

Reassiged = 재할당
Redeclared = 재선언
Hoisted = 해당 내용에 대해 작성 예정
*임시 링크
https://velog.io/@remon/%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85hoistingvar%EC%9D%98-%EB%AC%B8%EC%A0%9C%EC%A0%90

+a) var vs let

var

Local scope, Global Scope 가 존재한다.
Local Scope에는 (this 값을 제외하고)
세개의 변수가 담겨있다. (Name, greeting, time)
함수 안에 정의된 모든 변수를 Local Scope에서 사용이 가능하다.

let

Local Scope에 var 와는 다르게 greeting 변수가 보이지 않는다.
greeting 변수가 없으므로 해당 breakpoint 기준으로 ReferenceError 가 발생한다.
greeting 변수는, 해당 block 안쪽에서만 접근이 가능하다.


전역 변수를 최소화 하는 것은 side effect를 줄인다.

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

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

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

& var 키워드는 블록 스코프를 무시한다. 또한 재선언을 해도 에러를 내지 않는다. 따라서 let과 const를 주로 사용하도록 하자.

전역 변수를 var로 선언하는 것은 브라우저의 내장 기능을 사용하지 못하게 만들 수도 있다.

& 선언 없이 변수를 할당하지 마라. 선언 없이 변수를 할당하면, 해당 변수는 var로 선언한 전역 변수처럼 취급된다.

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

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

Strict Mode를 적용하려면, js 파일 상단에 'use strict'라고 입력하면 된다.

profile
FE_개발자_지망생

0개의 댓글