본문 바로가기

Javascript

[JavaScript] 함수/블록 기반 스코프

반응형

함수 기반 스코프


function test(a) {
	var b = 2;
	function sub() {...}
	var c = 3;
}

sub(); // fail
console.log(a, b, c); // fail

 

선언문이 스코프의 어디에 있는지는 중요하지 않다.

스코프 안에 있는 모든 변수와 함수는 그 스코프 버블에 속한다.

a, b, c, sub 모두 test()의 스코프 버블에 속하므로 test() 바깥에서는 이들에게 접근할 수 없다.

따라서 호출된 확인자가 글로벌 스코프에는 없기 때문에 ReferenceError 오류를 발생시킨다.

 

함수 스코프는 모든 변수가 함수에 속하고함수 전체에 걸쳐 사용되며 재사용 된다.

 

일반 스코프에 숨기

1 ) 함수를 선언하고 그안에 코드를 넣는다.

2 ) 작성한 코드에서 임의 부분을 함수 선언문으로 감싼다. (이는 해당 코드를 '숨기는' 효과를 냄)

 

위의 순서로 코드를 작성하면 스코프 버블이 생성된다.

즉, 감싸진 코드 안에 있는 '모든 변수' 또는 '함수 선언문'은 이전 코드에 포함됐던 스코프가 아니라 새로이 코드를 감싼 함수의 스코프에 묶인다.

 

스코프를 이용해 숨기는 방식을 사용하는 이유는 여러 가지가 있는데, 소프트웨어 디자인 원칙인 ‘최소 권한의 원칙’과 관련이 있다.

원칙은 모듈/객체의 API 같은 소프트웨어를 설계할 때 필요한 것만 최소한으로 남기고 나머지는 '숨겨야'한다는 것이다.

 

예시 )

function test1(a) {
    b = a + test2(a * 2); // 5

    console.log(b * 3);
}

function test2(a) {
    return a - 1;
}

var b;

test1(2); // 15

 

이를 더 적절하게 설계하려면,

다음과 같이 비공개 부분(test2())은 스코프 내부(test1())에 숨겨져야 한다.

 

예시 )

function test1(a) {
    function test2(a) {
        return a - 1;
    }
    
    var b;
    b = a + test2(a * 2); // 5

    console.log(b * 3);
}

test1(2); // 15

 

기능과 최종 결과는 달라지지 않았지만, 변경된 디자인은 비공개로 해야할 내용을 확실하게 비공개로 해뒀다.

일반적으로 이것이 더 나은 코드라고 본다.

 

충돌 회피

글로벌 스코프에서 변수 충돌이 쉽게 일어나는데,

예를들어 내부/비공개 함수와 변수가 적절하게 숨겨져 있지 않은 여러 라이브러리를 한 프로그램에서 불러오면 라이브러리들은 서로 쉽게 충돌한다.

 

이런 라이브러리는 일반적으로 글로벌 스코프에 하나의 고유 이름을 가지는 객체 선언물을 생성한다.

이후 객체는 해당 라이브러리의 '네임스페이스'로 이용된다.

네임스페이스를 통해 최상위 스코프의 확인자가 아니라 속성 형태로 라이브러리의 모든 기능이 노출된다.

 

예시 )

var library = {
    awsome: "things",
    test1: function(){},
    test2: function(){}
};

 

이를 좀 더 현대적인 충돌 방지 옵션으로는 다양한 의존성 관리자를 이용한 '모듈' 접근법이 있다.

이 도구를 사용하면 어떤 라이브러리도 확인자를 글로벌 스코프에 추가할 필요 없이,

특정 스포프로부터 의존성 관리자를 이용한 다양한 명시적인 방법으로 확인자를 가져와 사용할 수 있다.

 

단, 이런 도구를 쓴다고 렉시컬 스코프 규칙을 벗어날 수 없으며,

의존성 관리자는 공유 스코프에 누출되는 것을 방지하고, 우발적인 스코프 충돌을 예방하기 위해 충돌 위험이 없는 비공개 스코프에 확인자를 보관한다.

 

물론 충돌을 방어하는 코딩을 한다면, 실제로 의존성 관리자를 사용하지 않고도 사용한 것과 같은 결과를 얻을 수 있다.

 


스코프 역할을 하는 함수


함수를 이름 없이 선언하고, 이를 자동으로 실행한다면 함수를 선언함에 있어 더 이상적일 것이다.

다행히 자바 스크립트는 이를 해결할 수 있다.

 

예시 )

var a = 2;

(function test() {
	var a = 3;
})();

console.log(a); // 2

 

★ 'function' 이구문의 시작 위치에 있다면 "함수 선언문"이고, 다른 경우는 "함수 표현식"이다.

익명 함수 표현식

"function() ..."에 확인자 이름 없이 표현하는 것을 '익명 함수 표현식'이라 부른다.

함수 표현식은 이름 없이 사용할 수 있지만, 함수 선언문에는 이름이 빠져서는 안된다.

 

 

예시 )

setTimeout( function() { // 익명 함수
	console.log("I waited 1 second");
}, 1000);

// I waited 1 second

 

단, 이런 편한 익명함수도 단점이 존재했으니..

  • 익명 함수는 스택 추적시 표시할 이름이 없어 디버깅이 더 어려울 수 있음
  • 이름 없이 함수 스스로 재귀호출을 하려면 불행히도 폐기 예정인 arguments.callee 참조가 필요함
    자기 참조가 필요한 예로는 한 번 실행하면 해제되는 이벤트 처리 함수가 있음
  • 이름은 보통 쉽게 이해하고 읽을 수 있는 코드 작성에 도움을 주는데 익명함수는 이런 이름을 생략함

 

따라서 함수 표현식을 사용할 때 이름을 항상 쓰는 것이 좋다.

 

예시 )

setTimeout( function timeoutHandler() { // 기명 함수
	console.log("I waited 1 second");
}, 1000);

// I waited 1 second

 

즉시 호출 함수 표현식

"(function test(){}()"처럼 ()로 함수를 감싸면 함수를 표현식으로 바꾸는데, 이를 '즉시 호출 함수 표현식'이라 부른다.

첫번째 ()는 함수를 표현식으로 바꾸고 두번째 ()는 함수를 실행시킨다.

 

예시 )

var a = 2;
(function test() {
	var a = 3;
    console.log(a);	// 3
})();

console.log(a);	// 2

(function test(global) {
	var a = 3;
    console.log(a);	// 3
    console.log(global.a);	// 2
})(window);

console.log(a);	// 2

(function test(undefined) {
	var a;
    
    if (a === undefined) {
    	console.log("oh, NO!!"); // oh, NO!!
    }
})();

 

스코프 역할을 하는 블록


/*
 * (1) for()문의 시작부에 선언된 변수
 */
for (var i=0; i<10; i++) {
	console.log(i);
}

/*
 * (2) with 문 안에 생성된 객체 ⇢ 지양
 */

/*
 * (3) try/catch 문 중 catch 부분에 선언된 변수
 */
try {
	undefined();
} catch (err) {
	console.log(err); // TypeError: undefined is not a function
}

console.log(err); // uncaught ReferenceError: err is not defined

/*
 * (4) let ⇢ ES6부터 사용가능, 선언된 변수를 둘러싼 아무 블록({})의 스코프에 사용, 호이스팅(Hoisting) 효과를 받지 않음
 */
var a = true;
if (a) {
	let b = a * 2;
    console.log(b); // 2
}
console.log(b); // Uncaught ReferenceError: b is not defined

for (let i=0; i<10; i++) {
	console.log(i);
}
console.log(i); // Uncaught ReferenceError

{
    let j = 100;
    for (let j=0; j<10; j++) {
        let i = j;
        console.log(i); // 0 1 2 3 4 5 6 7 8 9
    }
}

/*
 * (5) connst ⇢ ES6부터 사용가능, 선언된 값은 고정, 선언된 후 변경시 오류 발생
 */
 
 var a = true;
 
 if (a) {
 	var a = 2;
    const b = 3;
    b = 3; // Uncaught TypeError: Assignment to constant variable
 }

/*
 * (6) 가비지 콜렉션
 */

 


[JavaScript] 스코프, Scope : chati.tistory.com/157

 

[JavaScript] 스코프, Scope

⌗ 스코프, Scope 특정 상소에 변수를 저장하고 나중에 그 변수를 찾는데는 잘 정의된 규칙이 필요하는데 이를 스코프(Scope)라고 한다. 변수를 검색하는 이유는 변수에 값을 대입(LHS 참조)하거나 ��

chati.tistory.com

 


[참고] You Don't Know JS 타입과 문법, 스코프와 클로저 - 카일 심슨

 

반응형

❥ CHATI Github