Chapter 6 - 함수 심화학습

Advance JS Function

JS Chapter 6

6.3 변수의 유효범위와 Closure

  • JS: 함수지향 언어 => 유동적 (동적으로 생성, 인수로 넘기기, 생성 외 곳에서 함수 호출 가능)

    • let, const (modern 방식)으로 진행 (var: 과거의 잔재)
  • 코드블록 내에서 정의 => 그 안에서만 사용 가능 (밖에서는 ReferenceError)

    중첩 함수

  • 함수 안의 함수
  • 새로운 객체의 프로퍼티 형태나 중첩 함수 그 자체로 반환 가능
  • 그래도 외부 변수 접근 가능
function makeCounter() {
  let count = 0;
  return function() {
    return count++;
  }
}

  • makeCounter 내 중첩함수 실행

    let counter = makeCounter();
    
    alert( counter() ); // 0
    alert( counter() ); // 1
    alert( counter() ); // 2
    
    • \(Q\): 각 counter() 독립적인지, count 변수에 어떤 값이 할당되는지

렉시컬 환경

단계 1. 변수

  • Lexical Environment: 내부숨김연관객체 (internal hidden associated obj) 로서 JS에서 실행 중인 함수, 코드블록, 스크립트 전체에 존재
    • 구성:
      1. 환경 레코드 (Environment Record): 모든 지역 변수를 Property로 저장하는 객체 (this)와 같은…
      2. 외부 렉시컬 환경 (Outer Lexical Environment) 에 대한 참조
  • 변수: 환경레코드의 프로퍼티
  • 변수 GET/Modify: 환경 레코드의 프로퍼티를 가져오거나 변경

  • 전역 렉시컬 환경: 스크립트 전체와 관련된 렉시컬 환경
                          //      lexical environment
    let phrase = "Hello"; // ---- | phrase: "Hello" |  ---(outer)--> null
    alert(phrase)
    
    • | box | : 저장되는 환경 레코드
    • -->: 외부 렉시컬 환경에 대한 참조
      • null인 이유: 전역 렉시컬 환경은 외부 참조 X
  • 코드 실행, 이어져 나가면 렉시컬 환경은 변화
    1. 스크립트 시작 : (pre-populate) 스크립트 내 선언된 변수 전체가 lexcial 환경에 올라감
      execution start     ------- | phrase: <uninitialized> | ---(outer)--> null
      
      • uninitialized: JS engine이 인지하긴 하지만 let 전까지 참조 불가능
    2. 변수에게 let (할당 전이기에 undefined), 여기부터 사용 가능
      let phrase;         ------- | phrase: undefined |
      
    3. phrase에 값 할당
      phrase = "Hello";   ------- | phrase: "Hello" |
      
    4. phrase 값 변경
      phrase = "Bye";     ------- | phrase: "Bye" |
      

      Lexical Environment (명세서에만 존재) only used to explain JS, 이론상의 객체이다

      • 직접 렉시컬 환경 얻기/조작 불가능
      • JS Engine들이 렉시컬 환경을 최적화 (rmv unused var…etc)

단계 2. 함수 선언문

  • 값 임 (like variables)
  • Function declaration 으로 선언한 함수 (unlike variables)
    • 바로 초기화됨
    • 렉시컬 환경 만들어지자마자 사용 가능 (선언되기도 전에!)
      1. 스크립트 시작
        execution start     ------- | phrase: <uninitialized>; example: function | ---(outer)--> null
        
      2. phrase값 선언, 함수 선언문으로 example fn 선언
        let phrase = "Hello";         ------- | phrase: "Hello" |
        function example(name){
        alert(...);
        }
        
  • function expression으로 선언된건 안됨 (js let example = function(name)...)

단계 3. 내부와 외부 렉시컬 환경

Full-width image Javascript Info

  • 변수 접근: 내부 렉시컬 함수에서 먼저 찾기 -> 없으면 참조된 외부 렉시컬로 범위를 넓힘 (전역 렉시컬 환경 확장까지 반복)

  • 전역렉시컬 환경에서도 변수 못 찾으면 에러 발생 (in 엄격 모드)

    • 비엄격 모드에서는 새로운 전역변수가 만들어진다 (하위호환 위한 기능)

단계 4. 함수를 반환하는 함수

  • 각 함수는 [[Environment]]라는 숨김 property 존재
    • 함수가 만들어진 곳의 렉시컬 환경에 대한 참조 저장
    • => 호출 장소와 무관하게 출생지 기억할 수 있는 이유 (변하지 않음)

Full-width image

  • counter을 찾았으니 해당 단계에서 ++해줘야 함
  • 변숫값 갱신 = 변수 저장된 렉시컬 환경에서 이루어짐 Full-width image

  • counter()을 여러번 호출하면 count가 증가하는 이유 Full-width image

Javascript Info

Closure

외부 변수를 기억하고 이 외부 변수에 접근 할 수 있는 함수

  • JS에서는 모든 함수가 자연스럽게 클로저가 됨 (다른 언어들은 비교적 어려움) new Function: 예외

  • 정리: 함수 본문에서는 사용해야 할 변수가 외부에 있으면 해당 변수의 [[Environment]] property를 사용해 접근함

Garbage Collection

  • 함수 호출 끝나면 관련 렉시컬 함수 메모리에서 제거 (호출 완료 시 변수 참조 불가능한 이유)
  • JS의 모든 객체는 도달 가능한 상태일 때만 메모리에 유지

  • 호출 끝나도 도달 가능한 중첩함수 ([[Environment]]에 외부 함수 렉시컬환경 정보 저장됨 => 도달 가능 상태!)
function f() {
  let value = 123;
  return function() {
    alert(value);
  }
}

let g = f();
  • g.[[Environment]]f() 호출 시 만들어지는 렉시컬 환경 정보 저장

  • 유의점: f() 여러번 호출 & 저장 => 각 렉시컬 환경 모두 메모리에 유지됨
    let arr = [f(), f(), f()]; // => 각 f() 호출할때 저장됨 
    
    • 렉시컬 환경 객체 : (like other obj) 도달 불가능 시 메모리에서 삭제
    • 해당 객체를 참조하는 중첩함수 하나라도 있으면 안 사라짐
  • (선) 중첩함수 메모리에서 삭제 (후) 감싸는 렉시컬 환경 (and value) 제거
    let g = f(); // g 존재 시 연관 렉시컬 환경 다 메모리에 존재
    g = null; // 도달 불가능 상태 => 메모리에서 삭제
    

최적화 프로세스

  • 이론: if 함수 alive then 외부 변수 역시 메모리에 유지
  • 실제: JS Engine keeps OPTIMIZING (변수 사용 분석, 미사용 판단 => 메모리에서 제거)
    • 때문에 디버깅 시 이론상 도달가능하지만 ReferenceError 뜰 때가 많다

6.5 전역 개체

  • 언제 어디서나 사용가능한 변수/함수 생성 가능 (언어 자체, 호스트 환경에 기본 내장되어 있는 경우 많음)
    • ex: Array, window.innerHeight...
    • 브라우저 환경: window, Nodejs 환경: global, 요즘 통일 트렌드: globalThis
      // ex: in window: both are SAME
      alert("Hello"); 
      window.alert("Hello");
      
  • var 로 선언한 전역함수/변수 => 전역 객체의 property 등극
    var gVar = 5;
    alert(window.gVar);
    
    • 단, 하위호환성 떄문에 동작하지만 권장 X (Modern JS style아님)
    • let 사용시 전역객체 통해 접근 불가능
      let gLet = 5;
      alert(window.gLet); // undefined 
      
  • 꼭 만들어야 한다면..중요 변수 권장 전역처리 방법
    window.currentUser = {
      name: "John"
    }
    
    • 모든 스크립트에서 아래처럼 접근 가능
      alert(currentUser.name) // John
      
    • 지역변수 currentUser 이미 존재하지만 전역객체꺼 쓰고 싶다면:
      alert(window.currentUser.name) // John
      
  • 전역 변수 되도록 사용하지 않는 것이 좋음 (input output 명확한 함수들이 테스트, 에러 방지에 좋다)

폴리필 사용하기

  • 직접 함수를 만들어 전역객체에 추가하는 방식
  • 예: 최신 브라우저는 Promise를 지원하지만 구식 브라우저에서는 폴리필을 만들어야 함
    if(!window.Promise){ // Promise 지원 안하면
      window.Promise = ... // 직접 구현
    }
    

6.6 객체로서의 함수와 기명 함수 표현식

  • JS에서 함수 = 값, type: Object

name property

function sayHi() { alert("Hi"); }
alert(sayHi.name) // sayHi

// 익명함수
let sayHi = function() { alert("Hi"); }
alert(sayHi.name) // sayHi

// 기본값 사용 이름할당
function f(sayHi = function() {}) {
  alert(sayHi.name); // sayHi 
}
f();
  • Contextual Name: JS 명세, 이름 없는 함수의 이름을 지정할떄 이 context에서 이름을 가져옴

  • 단, 객체 메서드는 익명 시 함수처럼 자동 할당 X

    • name property에 빈 문자열 저장됨

length property

  • 함수 매개변수의 개수 반환 (나머지 ...other 은 미포함)
  • length로 다른 함수 안에서 동작하는 함수의 타입 검사로 사용 가능
    • aka polymorphism (다형성) : 인수 종류에 따라 인수를 다르게 처리하는 방식 ```js function ask(question, …handlers) { let isYes = confirm(question);

      for(let handler of handlers) { if (handler.length == 0) { if (isYes) handler(); } else { handler(isYes); } }

      }

    // 사용자가 OK를 클릭한 경우, 핸들러 두 개를 모두 호출함 // 사용자가 Cancel을 클릭한 경우, 두 번째 핸들러만 호출함 ask(“질문 있으신가요?”, () => alert(‘OK를 선택하셨습니다.’), result => alert(result)); ```

Custom property

  • property는 변수가 아님
    • 아래 sayHi.counter은 변수가 아니라 property 이다 ```js function sayHi() { alert(“Hi”);

    // 함수를 몇 번 호출했는지 세봅시다. sayHi.counter++; } sayHi.counter = 0; // 초깃값

sayHi(); // Hi sayHi(); // Hi

alert( 호출 횟수: ${sayHi.counter}회 ); // 호출 횟수: 2회


- 클로저 대신 함수 프로퍼티 사용하기:
  ```js
  function makeCounter() {

  // let count = 0 대신 아래 메서드(프로퍼티)를 사용함

  function counter() {
      return counter.count++;
    };

    counter.count = 0;

    return counter;
  }

  let counter = makeCounter();
  alert( counter() ); // 0
  alert( counter() ); // 1
  • count 외부 렉시컬 환경 X, 함수 프로퍼티에 바로 저장됨
  • 차이점:
    • 클로저 사용 시: 외부 코드에서 count 접근 불가
    • 함수 프로퍼티 사용 시: count를 함수에 바인딩, 외부에서 값 수정 가능
      function makeCounter() {
        function counter() {
          return counter.count++;
        };
        counter.count = 0;
        return counter;
      }
      
      let counter = makeCounter();
      counter.count = 10;
      alert( counter() ); // 10
      
    • => 목적에 따라 선택해 사용하기

기명 함수 표현식

NFE (Named Function Expression): 이름이 있는 함수 표현식