본문 바로가기
개발 관련/javascript

7. 함수 표현식

by lazysnack 2022. 7. 13.

다루는 내용

  1. 함수 표현식의 특징
  2. 함수와 재귀
  3. 클로저를 이용한 고유(프라이빗) 변수

1. 함수 표현식의 특징

  • 함수를 정의하는 방법은 함수 선언과 함수 표현식 2가지가 있음.

  • 함수 선언에서 뚜렷한 특징은 호이스팅(hoisting)이다.

    sayHi();
    function sayHi() {
        console.log("hi");
    }
    // 함수 선언부를 다른 코드보다 먼저 읽고 실행함
  • 함수 표현식은 일반적인 변수 할당과 거의 비슷하며 함수 이름이 없어 익명 함수로 간주함

    sayHi(); // error
    var sayHi = function() {
        console.log("hi");
    } 
    // 다른 표현식과 마찬가지로 호출 하기 전에 할당해야함
  • 함수 표현식은 다른 함수에서 사용할 수 있도록 함수를 반환하는 형태도 가능

    function compareFunction(propertyName) {
        return function(object1, object2) {
            var val1 = object1[propertyName];
            var val2 = object2[propertyName];
            if(val1 < val2) {
                return -1;
            } else if(val1 > val2) {
                return 1;
            } else {
                return 0;
            }
        };
    }

2. 함수와 재귀

2-1 재귀

  • 함수가 자기 자신을 호출하는 형태

      function factorial(num) {
          if(num <= 1) {
              return 1;
          } else {
              return num * factorial(num-1);
          }
      }
    
      var anotherFactorial = factorial;
      factorial = null;
      console.log(anotherFactorial(4)); // error
      // factorial 은 null 인데 anotherFactorial 이 factorial()을 실행하려 하기 때문
  • arguments.callee 를 호출하면 되지만, 이는 스트릭트 모드에서 접근할 수 없기에, 이름 붙은 함수 표현식을 사용

    var factorial = (function f(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * f(num-1);
        }
    });

2-2 클로저

  • 다른 함수의 스코프에 있는 변수에 접근 가능한 함수

    function compareFunction(propertyName) {
        return function(object1, object2) {
            var val1 = object1[propertyName];
            var val2 = object2[propertyName];
            if(val1 < val2) {
                return -1;
            } else if(val1 > val2) {
                return 1;
            } else {
                return 0;
            }
        };
    }
    
    var compare = compareFunction("name");
    var result = compare({name : "snack"}, {name : "gray"});
    • 외부 함수가 실행을 마치고 익명 함수를 반환하면 익명 함수의 스코프 체인은 외부 함수의 활성화 객체와 전역 변수 객체를 포함하도록 초기화되므로, 익명 함수는 외부 함수의 변수 전체에 접근 가능
    • 외부 함수가 실행을 마치면 실행 컨텍스트의 스코프 체인은 파괴되지만, 활성화 객체는 익명 함수가 파괴될 때까지 메모리에 남음
    • compare = null 을 통해 함수에 대한 참조를 사라지게 하여 GC 가 메모리 회수가 가능하게 함
      • 클로저는 외부 스코프를 보관해야 하므로 메모리를 많이 잡아먹는다. 그러므로 남용은 지양
      • 클로저는 항상 외부 함수의 변수에 마지막으로 저장된 값만 알 수 있음 (전체 변수 객체에 대한 참조를 저장하기 때문)
    function createdFunction() {
       var result = new Array();
    
       for(var i = 0; i < 10; i++) {
           result[i] = function(num) {
               return function() {
                   return num;
               };
           }(i);
       }
       return result;
    }
    • 변수 i 를 익명함수에 매개변수로 넘김
    • 함수 매개변수는 값 형태로 전달되므로 i의 현재 값을 매개변수 num 에 복사
    • 익명함수는 num 을 간직한 클로저를 생성하여 반환
    • this 객체는 런타임에서 함수가 실행 중인 컨텍스트에 묶이는데, 익명함수는 특정 객체에 묶여 있지 않으므로 mode == strict mode ? undefined : window
    • 모든 함수는 호출되는 순간 자동으로 this와 arguments 두 특별한 변수를 갖게되는데, 내부 함수는 결코 외부 함수의 this 와 arguments 에 직접적으로 접근이 불가능, 그러므로 다음과 같이 해서 접근 가능
    var name = "the window";
    
    var object = {
       name : "my object",
    
       getNameFunc : function() {
           var that = this;
           return function() {
               return that.name;
           };
       }
    };
    
    console.log(object.getNameFunc()()); // my object 
    • 블록 레벨 스코프라는 개념이 없기 때문에 익명 함수를 괄호로 감싸서 흉내낼 수 있음 (고유 스코프라고 부르기도 함)
    function outputFunc(count) {
       (function () {
           for(var i=0; i < countl i++) {
               console.log(i);
           }
       })();
       console.log(i); // error
    }
    • 전역 스코프에 추가되는 변수나 함수의 수를 제한하는 용도로 자주 사용 (대규모 어플리케이션에선 전역 스코프에 변수나 함수를 추가하지 않는 편이 좋음)
    • 익명 함수에 대한 참조가 존재하지 않아 클로저의 메모리 문제도 덜함

3. 클로저를 이용한 고유(프라이빗) 변수

  • 자바스크립트에는 private member 이란 개념이 없지만, 고유 변수 라는 개념은 존재

  • 함수 내부에서 정의한 변수는 함수 밖에서 접근할 수 없으므로 모두 고유 변수로 간주 (함수 매개변수, 지역 변수, 내부 함수 등)

  • 다음과 같이 고유 및 특권 맴버를 정의해서 데이터를 직접적으로 수정할 수 없게 보호 가능

    function Person(name) {
        this.getName = function() {
            return name;
        };
    
        this.setName = function(value) {
            name = value;
        };
    }
    
    var person = new Person("snack");
    console.log(person.getName()); // snack
    person.setName("gray");
    console.log(person.getName()); // gray
  • 생성자 패턴에는 인스턴스가 매번 생성되므로, 이를 방지하기 위해 정적 고유 변수를 사용

    (function() {
    
        // 고유 변수와 함수
        var name = "";
    
        function privateFunction() {
            return false;
        }
    
        //생성자
        Person = function(value) {
            name = value
        };
    
        // 공용 메소드와 특권 메소드
        Person.prototype.getName = function() {
            return name;
        };
    
        Person.prototype.setName = function(value) {
            name = value;
        };
    })();
    
    var person1 = new Person("snack");
    console.log(person1.getName()); // snack
    person1.setName("gray");
    console.log(person1.getName()); // gray
    
    var person2 = new Person("john");
    console.log(person1.getName()); // john
    console.log(person2.getName()); // john
    • 각 인스턴스가 독립 변수를 가질 수는 없지만, 프로토타입을 통해 코드 재사용성은 좋아짐
  • 싱글톤 같은 일을 하는 모듈 패턴이 있음

    var application = function() {
        //고유 변수와 함수
        var components = new Array();
    
        //초기화
        components.push(new BaseComponent());
    
        //공용 인터페이스
        return {
            getComponentCount : function() {
                return components.length;
            },
    
            registerComponent : function(component) {
                if(typeof component == "object") {
                    components.push(component);
                }
            }
        };
    }();
    • 애플리케이션 레벨의 정보를 관리할 싱글톤을 두는 경우가 많음
    • 하나의 객체를 반드시 생성하고 몇 가지 데이터를 가지며 또한 고유 데이터에 접근 가능한 공용 메소드를 외부에 노출하도록 초기화해야 할 때 유용
  • 싱글톤 객체가 특정 타입의 인스턴스지만 프로퍼티나 메서드를 추가하여 확장해야 할 때 모듈 확장 패턴 을 사용

    var application = function() {
        //고유 변수와 함수
        var componens = new Array();
    
        //초기화
        components.push(new BaseComponent());
    
        //애플리케이션 인스턴스 생성
        var app = new BaseComponent();
    
        //공용 인터페이스
        app.getComponentCount = function() {
            return components.length;
        }
    
        app.registerComponent = function(component) {
            if(typeof component == "object") {
                components.push(component);
            }
        };
    
        return app;
    }();
    • app 은 application 객체의 로컬 버전

'개발 관련 > javascript' 카테고리의 다른 글

10. DOM  (0) 2022.07.13
8. 브라우저 객체 모델  (0) 2022.07.13
6. 객체 지향 프로그래밍  (0) 2022.07.13
5. 참조 타입  (0) 2022.07.13
4. 변수, 스코프, 메모리  (0) 2022.07.13