본문 바로가기
Javascript/YDKJS

YDKJS (유돈노JS) 6,8 - 스코프 노출제한, 모듈

by ginny. 2024. 10. 17.

어떻게, 왜 함수와 블록을 사용해 변수를 다양한 스코프로 구성해야 할까?

최소 노출의 원칙(POLE) : principle of least exposure

블록은 왜 스코프가 필요할까.

최소 권한의 원칙(POLP:least privilege)-정보보안원칙 중 하나. 방어적 아키텍처 설계.

POLE에 따라 스코프마다 등록된 변수의 노출을 최소화해야함.>>가능한 작고 깊게 중첩된 스코프에 변수선언.

모든 변수를 전역스코프에 배치하면 안되는 이유?

이름 충돌-

예기치않은 작동-비공개변수를 

의도하지 않은 종속성-노출된 비공개함수를 사용하고 의존한다면 향후 리팩토링시 안좋다.

일반(함수)스코프에 숨기기

함수 스코프 지정이 유용한 경우 - 팩토리얼함수, 이미 계산된건 캐싱해 사용=메모이제이션.(캐시는 클로저에 의존)

근데 cache변수는 비공개변수여야 함. >> 중간스코프를 만들어주면 해결!

외부전역스코프--중간함수스코프(cache변수)---함수스코프(factorial)

또는 중간스코프함수를 즉시실행해 클로저로 만들어주면 됨. factorial내부에서만 cache접근가능(숨겨짐)

함수표현식 즉시호출하기

즉시실행함수 표현식(IIFE) : 변수,함수를 숨기는 스코프 만들때!! 익명표현식으로 쓰는게 일반적.

독립형 IIFE

(function() {

console.log(count);

})();

독립형이 아닌 IIFE?

함수의 경계

스코프 정의 목적으로 IIFE사용시 의도치않은 결과..?

이 경우에는 함수대신 블록스코프를 만드는게 좋다

..gpt예시

 

블록으로 스코프 지정 >> 식별자노출을 최소화하기 위해 명시적블록스코프를 사용하자 (기본마인드셋)

중첩블록내에서 let선언 사용하는 방법

블록스코프가 아닌경우 - 객체리터럴, class{}, switch{}

명시적 블록스코프-중괄호 단독사용 { ... } 

(작가왈) 각 변수에 대해 가장 작은 블록을 정의하는것이 좋다. TDZ오류최소화하려면 항상 스코프 최상단에 let,const배치할것,

** switch 문은 블록 스코프를 제공하지 않지만, 각 case는 내부적으로 해당 스코프를 가지고 있습니다. 중괄호가 없어도 case 문은 자체적으로 스코프를 형성합니다.

 

총 6개스코프아녀?? 왜  if(bucket)은 스코프로 안쳐줌? 식별자 기준..5개스코프임! (전역,func,for,block스코프,for) 

 

var와 let

var을 사용하는 이유: 전체함수에 속한 변수(함수스코프)

블록내에서 선언할수있지만 여전히 함수스코프, 몇가지 예외를 제외하곤 함수최상위스코프에서는 var가 좋다

var=이 변수는 함수스코프임을 명확하게 전달할수있기때문!

let은 블록스코프 전달에 더 효과적(작가왈) 목적에 맞게 쓰자.

let의 위치

선언키워드는 의사결정에 영향을주지않는다(작가왈)

그것보다 "이 변수노출을 최소화하면서 요구사항을 충족하는 스코프가 어디인가?"에 따라 변수를 블록스코프,함수스코프 어디에 위치시킬지 결정한다.

for문의 i는 반드시 let으로 선언. (i는 항상 반복문안에서만 사용해야하기때문)

catch와 스코프

블록스코프와 그냥 블록의 차이?

catch블록 오류객체변수선언 없이 사용하면 catch블록은 스코프형성하지 않고 그냥 블록으로 처리된다.

 

블록내 함수선언 >>디버깅 어려움. 완전히 피하자. 블록안에서 함수선언을 절대하지말자!

블록내 함수선언(FiB)

함수선언은 var선언과 동일한가? 함수선언도 var변수와 같이 스코프매커니즘을 따를까?

# 예시 - 어떤 결과가 나올까?

if(false){

function ask(){

console.log("실행될까?");

}

}

ask();

>>실행결과 TypeError: ask is not a function

왜?? ask식별자는 존재하지만 if실행전이라 정의되지않아서 호출실패. >>명세서에 반하는 결과?!

**함수 선언문은 보통 조건문 밖에서 선언될 때, 스코프 내에서 어디서든 호출할 수 있습니다. 하지만 조건문 내부에 있는 함수 선언문은 자바스크립트의 표준에 따라 블록 범위에서 호이스팅되지 않고, 런타임에 해당 블록이 실행될 때 정의됩니다.

+ecma명세서 부록B참고 (기존코드에 문제발생우려가 있어서 예외허용, node도 마찬가지)

+FiB 코너케이스 (브라우저에따라 결과가 달라짐, 브라우저와 비브라우저간의 차이)

#예시 - 누가 호출될까요?

if (true) {
 function ask() {
    console.log("제가 호출?");
  }
}
if (true) {
 function ask() {
    console.log("아님 제가 호출?");
  }
} for(let i=0;i<3;i++) {
 function ask() {
    console.log("아님 제가 호출??");
  }
}
ask();

function ask() {
    console.log("아님 제가 호출?????");
}

>>정답은 3번째!

이렇게 오해의 소지가 다분하니, FiB는 절대 피하자. 블록안에서 함수선언말자.(함수표현식은 괜찮음)

함수선언은 함수의 최상위스코프에서!

성능저하가 문제라면 차라리 필요할경우 함수정의를 오버라이드 하자.

정리

렉시컬스코프규칙에 맞게 코드를 작성하면 변수를 목적에 맞게 체계화할수있다.

변수를 최소한으로 노출하는것!

 

모듈패턴

렉시컬스코프,클로저 기반.

캡슐화와 최소노출원칙(POLE)

캡슐화:데이터와 함수를 묶어 공통의 목적달성. 단일파일로 묶는것도 캡슐화?

캡슐화된 데이터와 함수의 가시성을 제어? (렉시컬스코프를 통해)

캡슐화는 비슷한코드를 그룹화, 비공개사항은 접근제한 >>

가시성제어=캡슐화된거를 보여줄지 말지 결정하는것

변수나 함수를 완전히 안보이게하는것보다 접근을 막아서 접근제어를 통해 보호한다는 의미가 더 맞을듯한데 (맞나?)

UI설계는 컴포넌트단위.

모듈

비공개정보, 공개적접근가능(공개API)

상태를 유지 stateful : 이전의 상태를 기억하고 이를 기반으로 동작합니다. (정보를 유지하며 업데이트)

느슨한 결합?? 더 공부해보기..

 

네임스페이스(무상태 그룹화)

무상태함수를 모아놓은것.

데이터없이 관련된 함수를 그룹으로 묶는것은 모듈관점의 캡슐화가 아님.

네임스페이스는 기능을 하나로 모았지만 모듈은 아님.

무상태함수?

 

  • 무상태 함수는 함수 내부의 상태를 유지하지 않는 것에 중점을 둡니다. 함수 실행 중 상태 변화를 일으키지 않는다는 의미입니다.
  • 상태 독립적인 함수는 외부 상태에 의존하지 않는 함수로, 외부의 변수를 참조하거나 수정하지 않는다는 점에 중점을 둡니다.

 

데이터구조(상태유지 그룹화)

데이터와 상태를 가진 함수를 묶어도 private하지않다면 POLE관점의 캡슐화가 아님.

 

모듈(상태를 가진 접근제어)

상태와 함수를 그룹화 && 가시성제어

클래식모듈패턴? 함수를 직접 반환하면 됨 (함수가 프로퍼티인 객체를 반환하는건?)

모듈패턴에서는 공개API객체에 추가된 프로퍼티만 외부에서 접근가능

IIFE=해당모듈 인스턴스 하나만 필요함. (단일인스턴스=싱글턴)

모듈 팩토리(다중 인스턴스)

싱글턴,멀티턴? 키

모듈팩토리=독립형 함수.

**모듈을 생성하는 함수로, 외부에 필요한 기능만 노출하고 내부의 상태나 로직을 캡슐화하여 안전하게 유지할 수 있도록 돕는 패턴입니다. 모듈 팩토리 함수는 **팩토리 함수(Factory Function)**와 **모듈 패턴(Module Pattern)*을 결합한 형태

즉시 실행 함수 표현식(IIFE)**와 함께 쓰이거나, 클로저를 활용하여 객체와 그 상태를 안전하게 보호하는 역할을 합니다. 외부에서 접근할 수 있는 공개된 메서드와 내부에 숨겨진 비공개 상태를 분리함으로써 캡슐화를 구현합니다.

모듈 팩토리 함수의 특징

  1. 팩토리 함수: 호출될 때마다 새로운 인스턴스(모듈)를 생성하는 함수입니다.
  2. 캡슐화: 모듈 내부에서만 관리되는 상태를 외부에서 접근하지 못하게 하고, 필요한 부분만 외부에 공개합니다.
  3. 클로저: 함수가 반환된 이후에도 모듈 내부의 상태를 유지하기 위해 클로저를 사용합니다.
  4. 공개 인터페이스: 외부에 노출되는 메서드를 반환하여, 모듈이 제공하는 기능을 명확히 합니다.

예시

function createCounter()

{

let count = 0; // 비공개 상태 (외부에서 접근 불가능)

return {

increment() { count++; return count; },

decrement() { count--; return count; },

getCount() { return count; } };

}

const counter1 = createCounter();

console.log(counter1.increment()); // 1

console.log(counter1.increment()); // 2

 

클래식모듈 정의

최소1번이상 실행되는모듈팩토리 함수가 외부스코프에 존재함

모듈 내부스코프에는 모듈상태정보가 최소1개이상이고, 비공개변수

모듈은 최소1개이상 함수를 공개API로 반환. 이함수는 내부스코프의 숨겨진 상태를 클로저를 통해 보존,관리함

>>클래식 모듈은 모듈팩토리나 IIFE로 모듈을 정의. 이 모듈은 다른코드나 모듈과 함께 한 파일내에서 묶일수있음

 

Node.js의 CJS모듈

파일기반. 모듈만들때 별도 파일 정의해야함. 모듈의 모든코드는 기본적으로 비공개.

공개APi는 module.exports객체를 사용함. exports에 추가하면 모듈외부에서도 사용가능함

module.exports = { 내보낼것 정의 }

여러개를 동시에 내보내려면 객체리터럴 정의를 사용하자.

Object.assign( module.exports, {내보낼것} ) >> 모듈aggregation 구현가능

>>공개API를 {}객체리터럴에 정의하고, 얕은복사로 여러속성을 module.exports에 한번에 추가한다.

 

**여러모듈파일을 import해와서 하나의 객체로 내보내는게 가능(모듈aggregation) 자바스크립트에서 모듈은 사실상 객체로 취급됩니다. import * as 구문을 사용하면, 모듈에서 내보낸(export) 것들을 하나의 객체로 가져올 수 있게 됩니다. 이 객체는 모듈이 가진 여러 변수와 함수들을 속성으로 포함하고 있습니다. 그 결과, Object.assign()을 이용해 이 객체들을 병합할 수 있습니다.

 

  • ES6 모듈에서 export된 값들은 실제로는 객체의 속성으로 동작합니다. 예를 들어, import * as mathModule을 사용하면, mathModule은 { add: [Function], subtract: [Function] }와 같은 객체가 됩니다.
  • Object.assign(): Object.assign()은 첫 번째 인수로 주어진 객체에, 나머지 인수로 전달된 객체의 속성들을 복사해 넣습니다. 객체들은 속성(key-value) 쌍으로 이루어져 있기 때문에, mathModule과 stringModule을 객체로 보고 병합할 수 있습니다.

 

작업중인 모듈에 또다른 모듈인스턴스 추가하려면 require()사용하자.

싱글턴 인스턴스처럼 작동. 동일모듈을 몇번이나 require 해도 모두 같은 모듈인스턴스의 참조이다.

require()하면 모듈파일의 전체공개API를 불러옴. 일부분만 필요하다면

var {getName} = require(...) 또는

var getName = require(...).getName

최신 ES모듈

파일기반. 모듈인스턴슨느 싱글턴. 모든건 기본적으로 비공개.

CJS와의 차이점은? 항상 엄격모드로 실행됨. export키워드로 노출, import로 불러옴. 어디서나 사용가능하지만 최상위스코프에 있어야함(다른함수나 블록안에 있으면 안됨)

기본 내보내기 default : 내보낼API객체에 멤버변수가 하나있는 경우, export default

default없이 내보내기(기명 내보내기)

기명 가져오기 : import {getName} //getName을 현재모듈 최상위스코프에 추가.

여러개 가져오기, 이름붙여서 가져오기 : import {getName as getStudentName}

getName이 기본내보내기라면, 괄호없이 import getName

기본내보내기, 기명내보내기 같이 가져오기 : import {default as getName, .. 기명멤버}

네임스페이스 가져오기 : import * as Student

(*사용하면 내보낸 모든멤버 가져와 단일 네임스페이스 식별자 아래 저장할수있다)

정리

모듈은 프로그램 기능고 ㅏ데이터를 구조화,정리하는데 가장 효과적임

POLE은 기본자세, 필요한 최소부분만 공개API에 노출하자.

모듈아래에서 클로저는 렉시컬스코프규칙으로 모듈상태를 유지하는 역할.

 

 

 

 

 

 

'Javascript > YDKJS' 카테고리의 다른 글

부록 A,B  (0) 2024.11.22
유돈노 7장 - 클로저  (0) 2024.10.24
1008 유돈노  (0) 2024.10.08
유돈노  (0) 2024.10.08
YDKJS (유돈노JS) 부록A - 암시적스코프  (0) 2024.10.06

댓글