[javascript] JavaScript에서 변수의 범위는 무엇입니까?

자바 스크립트에서 변수의 범위는 무엇입니까? 함수 외부와 반대로 내부와 동일한 범위를 가지고 있습니까? 아니면 중요합니까? 또한 전역 적으로 정의 된 변수는 어디에 저장됩니까?



답변

TLDR

JavaScript에는 어휘 (정적이라고도 함) 범위 지정 및 클로저가 있습니다. 즉, 소스 코드를 보면 식별자의 범위를 알 수 있습니다.

네 가지 범위는 다음과 같습니다.

  1. 글로벌-모든 것으로 표시
  2. 기능-기능 (및 해당 하위 기능 및 블록) 내에서 표시
  3. 블록-블록 (및 해당 하위 블록)에 표시
  4. 모듈-모듈 내에서 표시

전역 및 모듈 범위의 특수한 경우를 제외하고 변수는 var(함수 범위), let(블록 범위) 및 const(블록 범위)를 사용하여 선언됩니다 . 대부분의 다른 형태의 식별자 선언은 엄격 모드에서 블록 범위를 갖습니다.

개요

범위는 식별자가 유효한 코드베이스의 영역입니다.

어휘 환경은 식별자 이름과 관련된 값 사이의 매핑입니다.

범위는 어휘 환경의 연결된 중첩으로 구성되며, 중첩의 각 레벨은 상위 실행 컨텍스트의 어휘 환경에 해당합니다.

이러한 연결된 어휘 환경은 범위 “체인”을 형성합니다. 식별자 확인은이 체인을 따라 일치하는 식별자를 검색하는 프로세스입니다.

식별자 해상도는 한 방향으로 만 발생합니다 : 바깥 쪽. 이러한 방식으로 외부 어휘 환경은 내부 어휘 환경을 “볼”수 없습니다.

JavaScript 에서 식별자범위 를 결정하는 데는 세 가지 관련 요소가 있습니다 .

  1. 식별자 선언 방법
  2. 식별자가 선언 된 곳
  3. 엄격 모드 인지 엄격하지 않은 모드 인지 여부

식별자를 선언 할 수있는 몇 가지 방법 :

  1. var, letconst
  2. 기능 매개 변수
  3. 캐치 블록 매개 변수
  4. 함수 선언
  5. 명명 된 함수 표현식
  6. 전역 객체에 대해 암시 적으로 정의 된 속성 (예 : var엄격하지 않은 모드에서는 누락 )
  7. import 진술
  8. eval

위치 식별자 중 일부를 선언 할 수 있습니다.

  1. 글로벌 컨텍스트
  2. 기능 바디
  3. 보통 블록
  4. 제어 구조의 상단 (예 : 루프, if, while 등)
  5. 제어 구조체
  6. 모듈

선언 스타일

var

전역 컨텍스트에서 직접 선언 된 경우를 제외하고 사용하여 선언 된 식별자 에는 전역 범위에서 특성으로 추가되고 전역 var 범위가있는 함수 범위가 있습니다 . eval기능에 사용하기위한 별도의 규칙이 있습니다.

let과 const

식별자를 사용하여 선언 letconst 블록 범위를 가지고 는 그들이 글로벌 범위를 가지고있는 경우에 국제적인 맥락에서 직접 선언 할 때 그렇다.

참고 : let, constvar 모든 게양된다 . 이는 논리적 정의 위치가 둘러싸는 범위 (블록 또는 기능)의 최상위임을 의미합니다. 그러나 변수는 useing 선언 letconst읽거나 제어 소스 코드에 선언의 지점을 통과 할 때까지 할당 할 수 없습니다. 임시 기간은 임시 데드 존이라고합니다.

function f() {
    function g() {
        console.log(x)
    }
    let x = 1
    g()
}
f() // 1 because x is hoisted even though declared with `let`!

함수 매개 변수 이름

함수 매개 변수 이름은 함수 본문의 범위입니다. 이것에는 약간의 복잡성이 있습니다. 기본 인수로 선언 된 함수는 함수 본문이 아닌 매개 변수 목록 위에 닫힙니다 .

함수 선언

함수 선언은 엄격 모드의 블록 범위와 엄격하지 않은 모드의 함수 범위를 갖습니다. 참고 : 엄격하지 않은 모드는 서로 다른 브라우저의 기발한 역사적 구현을 ​​기반으로하는 복잡한 규칙입니다.

명명 된 함수 표현식

명명 된 함수 표현식은 그 자체로 범위가 지정됩니다 (예 : 재귀 목적으로).

전역 객체에 대해 암시 적으로 정의 된 속성

엄격하지 않은 모드에서는 전역 개체가 범위 체인의 최상위에 위치하기 때문에 전역 개체에 대해 암시 적으로 정의 된 속성에 전역 범위가 있습니다. 엄격 모드에서는 허용되지 않습니다.

평가

에서는 eval문자열 변수 사용 선언 var한다면, 현재 범위에 배치된다 또는 eval간접적으로 사용되는 전역 객체 건물.

다음은 이름 때문에 ReferenceError가 발생합니다 x, y그리고 z함수의 의미의 외부가 없습니다 f.

function f() {
    var x = 1
    let y = 1
    const z = 1
}
console.log(typeof x) // undefined (because var has function scope!)
console.log(typeof y) // undefined (because the body of the function is a block)
console.log(typeof z) // undefined (because the body of the function is a block)

다음은 의 가시성 이 블록에 의해 제한되지 않기 때문에 yz에 대해서는 ReferenceError를 발생 시키지 않습니다. 제어 구조의 시체를 정의 블록 좋아 , 와 유사하게 동작합니다.xxifforwhile

{
    var x = 1
    let y = 1
    const z = 1
}
console.log(x) // 1
console.log(typeof y) // undefined because `y` has block scope
console.log(typeof z) // undefined because `z` has block scope

다음에서는 함수 범위가 x있으므로 루프 외부에서 볼 수 있습니다 var.

for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)

…이 동작으로 인해 var루프에서 사용하여 선언 된 변수를 닫을 때주의해야합니다 . x여기에 선언 된 변수 인스턴스는 하나 뿐이며 논리적으로 루프 외부에 있습니다.

다음은 5을 다섯 번 인쇄 한 다음 루프 외부 5에 대해 여섯 번째 로 인쇄 console.log합니다.

for(var x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop

다음 은 블록 범위 undefined이므로 인쇄 x됩니다. 콜백은 하나씩 비동기 적으로 실행됩니다. 새로운 행동 let각 익명 함수라는 이름의 다른 변수에 대해 닫혀 있음을 변수 수단 x(이 함께 할 것이다 달리 var) 및 정수 그래서 0를 통해 4인쇄 :

for(let x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables
}
console.log(typeof x) // undefined

다음은 블록 ReferenceError의 가시성이 x제한되지 않기 때문에를 던지지 않습니다. 그러나, 명령문 undefined때문에 변수가 초기화되지 않았기 때문에 인쇄 if됩니다.

if(false) {
    var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised

for사용하여 루프 의 맨 위에 선언 된 변수는 루프 let의 본문으로 범위가 지정됩니다.

for(let x = 0; x < 10; ++x) {} 
console.log(typeof x) // undefined, because `x` is block-scoped

ReferenceError가시성은 x블록에 의해 제한 되기 때문에 다음이 발생 합니다.

if(false) {
    let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped

변수로 선언 var, let또는 const모든 모듈에 범위가 있습니다 :

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

var전역 컨텍스트 내에서 사용하여 선언 된 변수가 전역 개체에 속성으로 추가되므로 다음은 전역 개체에 속성을 선언 합니다.

var x = 1
console.log(window.hasOwnProperty('x')) // true

letconst글로벌 맥락에서 전역 객체에 속성을 추가 할 수 있지만 여전히 글로벌 범위가 없습니다 :

let x = 1
console.log(window.hasOwnProperty('x')) // false

함수 매개 변수는 함수 본문에서 선언 된 것으로 간주 될 수 있습니다.

function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function

캐치 블록 매개 변수는 캐치 블록 본체에 적용됩니다.

try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block

명명 된 함수 표현식은 표현식 자체에만 적용됩니다.

(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression

엄격하지 않은 모드에서는 전역 개체에 대해 암시 적으로 정의 된 속성의 범위가 전역 적으로 지정됩니다. 엄격 모드에서는 오류가 발생합니다.

x = 1 // implicitly defined property on the global object (no "var"!)

console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true

엄격하지 않은 모드에서 함수 선언에는 함수 범위가 있습니다. 엄격 모드에서는 블록 범위가 있습니다.

'use strict'
{
    function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped

후드 아래에서 작동하는 방법

범위는 식별자가 유효한 코드 의 어휘 영역으로 정의됩니다 .

JavaScript에서 모든 function-object에는 숨겨진 컨텍스트 가 있으며 실행 컨텍스트 (스택 프레임) [[Environment]]어휘 환경 에 대한 참조 입니다.

함수를 호출하면 숨겨진 [[Call]]메소드가 호출됩니다. 이 메소드는 새 실행 컨텍스트를 작성하고 새 실행 컨텍스트와 함수 오브젝트의 어휘 환경 사이에 링크를 설정합니다. [[Environment]]함수 객체 의 값을 새 실행 컨텍스트의 어휘 환경에 있는 외부 참조 필드에 복사하여이를 수행합니다.

새로운 실행 컨텍스트와 함수 객체의 어휘 환경 사이의이 링크를 클로저 라고합니다 .

따라서 JavaScript에서 범위는 외부 참조에 의해 “체인”으로 함께 연결된 어휘 환경을 통해 구현됩니다. 이 어휘 환경 체인을 스코프 체인이라고하며, 식별자 확인 은 일치하는 식별자를 찾기 위해 체인검색하여 발생합니다 .

자세한 내용을 알아보십시오 .


답변

Javascript는 범위 체인을 사용하여 지정된 함수의 범위를 설정합니다. 일반적으로 하나의 전역 범위가 있으며 정의 된 각 함수에는 자체 중첩 범위가 있습니다. 다른 함수 내에 정의 된 모든 함수에는 외부 함수에 연결된 로컬 범위가 있습니다. 항상 범위를 정의하는 소스의 위치입니다.

스코프 체인의 요소는 기본적으로 상위 범위에 대한 포인터가있는 맵입니다.

변수를 해결할 때 javascript는 가장 안쪽 범위에서 시작하여 바깥쪽으로 검색합니다.


답변

전역 적으로 선언 된 변수에는 전역 범위가 있습니다. 함수 내에서 선언 된 변수는 해당 함수로 범위가 지정되고 동일한 이름의 전역 변수를 음영 처리합니다.

(나는 확실히 실제 자바 스크립트 프로그래머는 다른 답변에서 지적 할 수있을 것으로 많은 미묘한이있다하고 있습니다. 특히 내가 건너 온에서 이 페이지 정확히 무엇에 대해 this언제든지 의미합니다. 희망은 이 더 소개 링크가 당신이 생각 시작할 수 있도록 충분하다 .)


답변

구식 JavaScript

일반적으로 JavaScript는 실제로 두 가지 유형의 범위 만 있습니다.

  1. 글로벌 범위 : 변수는 응용 프로그램 시작부터 응용 프로그램 전체에 알려져 있습니다 (*)
  2. 기능 범위 : 변수는 함수 의 시작부터 선언 된 함수에서 알려져 있습니다 (*)

차이점을 설명하는 다른 많은 답변이 이미 있기 때문에 이에 대해 자세히 설명하지 않습니다.


현대 JavaScript

가장 최근의 자바 스크립트 사양은 이제 세 번째 범위를 허용 :

  1. 블록 범위 : 식별자는 자신이 선언 한 범위의 상단에서 “알려져” 있지만 선언이 끝날 때까지 식별자를 할당하거나 역 참조 (읽기) 할 수 없습니다. 이 기간을 “임시 데드 존”이라고합니다.

블록 범위 변수를 작성하는 방법

전통적으로 다음과 같이 변수를 만듭니다.

var myVariable = "Some text";

블록 범위 변수는 다음과 같이 생성됩니다.

let myVariable = "Some text";

기능 범위와 블록 범위의 차이점은 무엇입니까?

기능 범위와 블록 범위의 차이점을 이해하려면 다음 코드를 고려하십시오.

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

여기에서 변수 j는 첫 번째 for 루프에서만 알려져 있지만 이전과 이후에는 알려지지 않았습니다. 그러나 우리의 변수 i는 전체 기능에 알려져 있습니다.

또한 블록 범위 변수는 게양되지 않았기 때문에 선언되기 전에 알 수 없습니다. 또한 동일한 블록 내에서 동일한 블록 범위 변수를 다시 선언 할 수 없습니다. 이렇게하면 블록 범위 변수가 전역 또는 기능 범위 변수보다 오류가 덜 발생합니다.이 변수는 게양되어 여러 선언의 경우 오류가 발생하지 않습니다.


오늘날 블록 범위 변수를 사용하는 것이 안전합니까?

오늘날 사용하기에 안전한지 여부는 환경에 따라 다릅니다.

  • 서버 측 JavaScript 코드 ( Node.js )를 let작성 하는 경우 명령문을 안전하게 사용할 수 있습니다 .

  • 클라이언트 측 JavaScript 코드를 작성하고 Traceur 또는 babel-standalone 과 같은 브라우저 기반 변환기를 사용하는 경우이 let명령문을 안전하게 사용할 수 있지만 코드는 성능 측면에서 최적 일 수 있습니다.

  • 클라이언트 측 JavaScript 코드를 작성하고 traceur 쉘 스크립트 또는 Babel 과 같은 노드 기반 변환기를 사용하는 경우 let명령문을 안전하게 사용할 수 있습니다 . 그리고 브라우저는 변환 된 코드에 대해서만 알기 때문에 성능 단점이 제한되어야합니다.

  • 클라이언트 측 JavaScript 코드를 작성 중이고 트랜스 파일러를 사용하지 않는 경우 브라우저 지원을 고려해야합니다.

    이들은 전혀 지원하지 않는 일부 브라우저입니다 let.

    • 인터넷 익스플로러 10 이하
    • Firefox 43 이하
    • 사파리 9 이하
    • 안드로이드 브라우저 4 이하
    • 오페라 27 이하
    • Chome 40 이하
    • 모든 버전의 Opera Mini & Blackberry Browser

여기에 이미지 설명을 입력하십시오


브라우저 지원을 추적하는 방법

let이 답변을 읽을 당시의 진술을 지원하는 브라우저에 대한 최신 개요는 Can I Use페이지를 참조 하십시오 .


(*) JavaScript 변수가 들어 있기 때문에 전역 및 기능적으로 범위가 지정된 변수를 선언하기 전에 초기화하고 사용할 수 있습니다 . 이것은 선언이 항상 범위의 맨 위에 있다는 것을 의미합니다.


답변

예를 들면 다음과 같습니다.

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  };

}

</script>

클로저를 조사하고 클로저를 사용하여 비공개 회원 을 만드는 방법을 조사해야합니다 .


답변

내가 이해하는 열쇠는 Javascript가 기능 수준 범위 지정보다 일반적인 C 블록 범위 지정이라는 것입니다.

여기 주제에 관한 좋은 기사가 있습니다.


답변

“Javascript 1.7″(Mozilla의 Javascript 확장)에서 다음 과 같이 letstatement -scope 변수를 선언 할 수 있습니다 .

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4