본문으로 건너뛰기

JS 학습노트 11_고급 기법

무료2015-04-13#JS#js高级技巧

본 문서에서는 타입 검출, 스코프 안전 생성자, 지연 로딩, 함수 바인딩, 함수 커링 (함수 중첩), 변조 방지 객체, 함수 스로틀링, 옵저버 패턴 등의 고급 기법을 소개합니다

##1. 타입 검출

typeof 는 때때로 불합리한 반환값을 반환합니다. 예를 들어 RegExp 객체는 object 를 반환합니다. 테스트 코드:

var regex = /^what$/i;
regex = new RegExp('^what$');
alert(typeof regex);

instanceof 는 페이지에 여러 frame 이 있을 때 사용할 수 없습니다. 서로 다른 frame 의 객체에 대해 instanceof 는 false 를 반환합니다

Object.prototype.toString.call(value) === '[object Array/Function...]' 를 사용하여 타입 검사를 수행할 수 있습니다. 또한 네이티브 객체와 사용자 정의 객체를 구분하는 데에도 사용할 수 있습니다. 예를 들어:

[object JSON]//네이티브 JSON 객체
[object Object]//사용자 정의 JSON 객체

주의:IE 에서 COM 객체 형태로 구현된 함수 객체는 toString 이 [object Function] 을 반환하지 않습니다

##2. 스코프 안전 생성자

new 연산자로 생성자를 호출하면 새로 생성된 객체에 속성이 추가되지만, 생성자를 직접 호출하면 전역 객체 window 에 속성이 추가됩니다

전역 스코프를 오염시키지 않기 위해 다음과 같은 생성자를 사용할 수 있습니다:

/* 전역 스코프를 오염시킬 수 있음
function Student(name){
  this.name = name;
}
*/
//스코프 안전 생성자
function Student(name){
  if(this instanceof Student){
    this.name = name;
  }
  else{
    return new Student(name);
  }
}

위의 생성자는 생성자를 직접 호출하여 전역 객체에 의도치 않게 속성을 추가하는 것을 방지할 수 있지만, 이 방식으로 상속을 구현하면 문제가 발생할 수 있습니다. 타입 검사로 인해 상속이 실패할 수 있습니다

##3. 지연 로딩 (중복된 분기 검사 방지)

  1. 첫 번째 분기 검사 실행 시 기존 함수를 덮어씁니다. 예를 들어:

    function detect(){
      if(...){
        detect = function(){
          //
        }
      }
      else if(...){
        detect = function(){
          //
        }
      }
      else...
    }
    
  2. 즉시 실행되는 익명 함수를 사용하여 익명 함수를 반환함으로써 지연 로딩을 구현할 수 있습니다. 예를 들어:

    var detect = (function(){
      if(...){
        return function(){
          //
        }
      }
      else if(...){
        return function(){
          //
        }
      }
      else...
    })();
    

첫 번째 방식은 첫 호출 시 성능 손실이 있지만 이후 호출에서는 성능 손실이 없습니다. 두 번째 방식은 첫 호출 시에도 성능 손실이 없습니다. 시간 소모적인 작업을 코드 첫 로딩 시에 배치했기 때문입니다

##4. 함수 바인딩 (실행 환경 지정)

다음 함수를 사용하여 함수의 실행 환경을 지정하고 새 함수를 생성할 수 있습니다:

function bind(fun, context){
  return function(){
    return fun.apply(context, arguments);
  }
}

기존 함수를 기반으로 새 함수를 쉽게 생성할 수 있습니다. [IE9+] 에는 네이티브 bind 메서드가 있습니다. 예를 들어 var newFun = fun.bind(obj);

주의: 함수 바인딩에는 메모리 소비가 많고 실행이 느리다는 단점이 있습니다

##5. 함수 커링 (함수 중첩이라고도 하며, 일부 매개변수 지정 가능)

커링 함수를 생성하는 일반적인 방법:

function curry(fun){
  var args = Array.prototype.slice.call(arguments, 1);//첫 번째 매개변수 fun 제거, 지정된 매개변수 값 얻기
  return function(){
    var innerArgs = Array.prototype.slice.call(arguments);//내부 arguments 객체를 배열로 변환 (concat 메서드 사용 위해)
    var finalArgs = args.concat(innerArgs);//매개변수 목록 결합
    return fun.apply(null, finalArgs);//결합된 매개변수 목록을 fun 에 전달
  }
}

또는 bind 메서드를 강화하여 커링 구현:

function bind(fun, context){
  var args = Array.prototype.slice.call(arguments, 2);//첫 2 개 매개변수 제거
  return function(){
    var innerArgs = Array.prototype.slice.call(arguments);//curry 와 동일
    var finalArgs = args.concat(innerArgs);//curry 와 동일
    return fun.apply(context, finalArgs);//실행 환경과 매개변수 지정
  }
}

주의: 커링과 bind 모두 추가 오버헤드가 존재합니다. 남용하지 마십시오

##6. 변조 방지 객체

  1. 확장 불가 객체 (새 속성 추가 불가)

    var obj = {a : 1, b : 2};
    alert(Object.isExtensible(obj));//true
    Object.preventExtensions(obj);//obj 를 확장 불가로 설정
    alert(Object.isExtensible(obj));//false
    obj.c = 3;
    alert(obj.c);//undefined
    

주의: 확장 불가 설정 작업은 취소할 수 없습니다(되돌릴 수 없음). 설정 후 새 속성을 추가할 수 없지만 기존 속성 수정/삭제는 가능합니다

  1. 밀봉 객체 (기존 속성만 수정 가능, 삭제 또는 추가 불가)

    var obj = {a : 1, b : 2};
    Object.seal(obj);//밀봉 객체로 설정
    alert(Object.isSealed(obj));//true
    obj.c = 3;
    alert(obj.c);//undefined
    delete obj.a;//엄격 모드에서 오류
    alert(obj.a);//1
    
  2. 동결 객체 (읽기 전용, 접근자 속성은 쓰기 가능)

    var obj = {a : 1, b : 2};
    Object.freeze(obj);//밀봉 객체로 설정
    alert(Object.isFrozen(obj));//true
    obj.a = 3;
    alert(obj.a);//1
    

위의 구현은 모두ES5 에서 추가된 부분입니다. 브라우저 지원성은 알 수 없으며, 로컬 테스트에서는*[IE8-] 는 지원하지 않습니다*. Chrome 과 FF 는 지원합니다

##7. 함수 스로틀링

시간이 소요되는 큰 작업을 작은 부분으로 나누고 setTimeout 으로 실행을 제어합니다

장점: 페이지 응답 속도가 향상됩니다

단점: 논리적 일관성이 없어지고 구현 난이도가 증가합니다. 또한 완전한 트랜잭션이 분리되므로 트랜잭션 제어 구현이 어렵습니다

##8. 옵저버 패턴

사용자 정의 이벤트를 사용하여 옵저버 패턴을 구현할 수 있습니다:

function EventTarget(){
  this.handlers = {};    
}

EventTarget.prototype = {
  constructor: EventTarget,

  addHandler: function(type, handler){
    if (typeof this.handlers[type] == "undefined"){
      this.handlers[type] = [];
    }

    this.handlers[type].push(handler);
  },
  
  fire: function(event){
    if (!event.target){
      event.target = this;
    }
    if (this.handlers[event.type] instanceof Array){
      var handlers = this.handlers[event.type];
      for (var i=0, len=handlers.length; i < len; i++){
        handlers[i](event);
      }
    }
  },

  removeHandler: function(type, handler){
    if (this.handlers[type] instanceof Array){
      var handlers = this.handlers[type];
        for (var i=0, len=handlers.length; i < len; i++){
          if (handlers[i] === handler){
            break;
          }
        }
          
        handlers.splice(i, 1);
      }
  }
};

사용 방법은 다음과 같습니다:

function handleMessage(event){
  alert("Message received: " + event.message);
}
//새 객체 생성
var target = new EventTarget();
//이벤트 핸들러 추가
target.addHandler("message", handleMessage);
//이벤트 트리거
target.fire({ type: "message", message: "Hello world!"});
//이벤트 핸들러 제거
target.removeHandler("message", handleMessage);
//다시 이벤트 트리거, 이벤트 핸들러가 없어야 함
target.fire({ type: "message", message: "Hello world!"});

댓글

아직 댓글이 없습니다

댓글 작성