>  기사  >  웹 프론트엔드  >  AmplifyJS 구성 요소_기본을 사용한 JavaScript 프로그래밍 가이드

AmplifyJS 구성 요소_기본을 사용한 JavaScript 프로그래밍 가이드

WBOY
WBOY원래의
2016-05-16 15:48:24944검색

이벤트 유통의 역할

페이지에 다양한 인터랙티브 기능을 추가할 때 우리에게 익숙한 가장 간단한 방법은 이벤트를 페이지 요소에 바인딩한 다음 이벤트 처리 기능에서 원하는 작업을 수행하는 것입니다. 다음과 같은 코드:

element.onclick = function(event){
  // Do anything.
};

우리가 하려는 작업이 복잡하지 않다면 실제 논리 함수의 코드를 여기에 배치할 수 있습니다. 나중에 수정해야 할 경우 이 이벤트 처리 기능이 있는 위치로 이동하여 수정하세요.

게다가 적절한 코드 재사용을 위해 논리 함수의 일부를 함수로 분할할 수도 있습니다.

element.onclick = function(event){
  // Other code here.
  doSomethingElse();
};

여기서 doSomethingElse 함수에 해당하는 함수는 다른 곳에서 사용될 수도 있으므로 이렇게 나누어 보겠습니다. 또한 좌표 설정 등의 기능이 있을 수 있으며(함수를 setPosition이라고 가정) 브라우저 이벤트 객체 이벤트에서 제공하는 포인터 위치 등의 정보도 사용해야 합니다.

element.onclick = function(event){
  // Other code here.
  doSomethingElse();
  setPosition(event.clientX, event.clientY);
};

여기서 권장되지 않는 접근 방식은 이벤트 개체를 setPosition에 직접 전달하는 것입니다. 이는 논리적 기능과 이벤트 수신의 책임을 분리하는 것이 좋은 습관이기 때문입니다. 이벤트 처리 기능 자체가 브라우저 이벤트 개체에 액세스하도록 허용하면 코드 결합이 줄어들고 독립적인 테스트 및 유지 관리가 용이해집니다.

그렇다면 기능이 점점 더 복잡해지면 어떻게 될까요? 이전 접근 방식을 따르면 다음과 같습니다.

element.onclick = function(event){
  doMission1();
  doMission2(event.clientX, event.clientY);
  doMission3();
  // ...
  doMissionXX();
};

이런 식으로 사용해도 괜찮지만, 이 경우 실제로는 좀 더 우아한 글쓰기 방법을 고려해 볼 수 있습니다.

element.onclick = function(event){
  amplify.publish( "aya:clicked", {
    x: event.clientX,
    y: event.clientY
  });
};

이 양식은 이벤트 배포입니다. 여기서 이벤트는 브라우저 기본 이벤트(이벤트 개체)가 아니라 논리적 수준의 사용자 지정 이벤트를 나타냅니다. 위에서 클릭한 aya:는 캐주얼하게(정말요?) 작성한 맞춤 이벤트 이름입니다.

아직 끝이 아닙니다. 이전의 복잡한 기능을 완료하려면 사용자 정의 이벤트를 수행할 작업과 연결해야 합니다.

amplify.subscribe( "aya:clicked", doMission1);
// ...
amplify.subscribe( "aya:clicked", doMission2);
// ...

다시 돌아온 것 같나요? 사실이지만 작동합니다. 한편으로는 브라우저 기본 이벤트의 청취가 분리되고 견고해졌습니다. 예를 들어 여러 기능이 축소되는 등 향후 논리 기능이 변경되면 사용자 정의 이벤트의 관련 코드 부분만 삭제하면 됩니다. 더 이상 네이티브 이벤트에 대해 걱정할 필요가 없습니다. 반면, 논리적 기능의 조정은 더욱 유연해졌습니다. 구독을 통해 어떤 코드 위치에서도 기능을 추가할 수 있고, 분류 관리(맞춤형 이벤트 이름)를 스스로 수행할 수 있습니다.

간단히 말하면 이벤트 배포는 사용자 정의 이벤트의 중복 계층을 추가하여 코드 모듈 간의 결합을 줄입니다(간단한 논리 함수만 있는 경우 중복이라고 생각할 것입니다). 정리되어 후속 유지 관리가 더 쉬워집니다.

잠깐만, 여러 차례 해외 여행을 다녀온 내 앞에 그 유명인 증폭은 무엇을 하는 걸까?

좋아요, 드디어 소개할 차례네요.
AmplifyJS

이벤트 배포에는 특정 방법을 구현해야 합니다. 이벤트 배포를 위한 디자인 패턴 중 하나는 게시/구독입니다.

AmplifyJS는 주로 Ajax 요청, 데이터 저장, 게시/구독(각각 독립적으로 사용할 수 있음)의 세 가지 기능을 제공하는 간단한 JavaScript 라이브러리입니다. 그 중 발행/구독이 핵심 기능이고, 해당 이름은 amplify.core 입니다.

2015728151503102.jpg (342×85)

amplify.core는 게시/구독 디자인 패턴을 간결하고 명확하게 구현한 것으로, 댓글을 포함해 총 100줄이 넘습니다. Amplify의 소스 코드를 읽은 후에 게시/구독 디자인 패턴을 구현하는 방법을 더 잘 이해할 수 있습니다.
코드 개요

amplify.core 소스코드의 전체적인 구조는 다음과 같습니다.

(function( global, undefined ) {

var slice = [].slice,
  subscriptions = {};

var amplify = global.amplify = {
  publish: function( topic ) {
    // ...
  },

  subscribe: function( topic, context, callback, priority ) {
    // ...
  },

  unsubscribe: function( topic, context, callback ) {
    // ...
  }
};

}( this ) );

보시다시피 amplify는 게시, 구독, 구독 취소의 세 가지 메소드가 있는 amplify라는 전역 변수(global의 속성으로)를 정의합니다. 또한 구독은 게시/구독 모드와 관련된 모든 사용자 정의 이벤트 이름 및 관련 기능을 저장하는 로컬 변수 역할을 합니다.
게시

게시하려면 사용자 정의 이벤트 이름(또는 단순히 주제라고 함)인 주제를 지정해야 합니다. 호출 후 특정 주제와 관련된 모든 함수가 순차적으로 호출됩니다.

publish: function( topic ) {
  // [1]
  if ( typeof topic !== "string" ) {
    throw new Error( "You must provide a valid topic to publish." );
  }
  // [2]
  var args = slice.call( arguments, 1 ),
    topicSubscriptions,
    subscription,
    length,
    i = 0,
    ret;

  if ( !subscriptions[ topic ] ) {
    return true;
  }
  // [3]
  topicSubscriptions = subscriptions[ topic ].slice();
  for ( length = topicSubscriptions.length; i < length; i++ ) {
    subscription = topicSubscriptions[ i ];
    ret = subscription.callback.apply( subscription.context, args );
    if ( ret === false ) {
      break;
    }
  }
  return ret !== false;
},

[1],参数topic必须要求是字符串,否则抛出一个错误。

[2],args将取得除topic之外的其他所有传递给publish函数的参数,并以数组形式保存。如果对应topic在subscriptions中没有找到,则直接返回。

[3],topicSubscriptions作为一个数组,取得某一个topic下的所有关联元素,其中每一个元素都包括callback及context两部分。然后,遍历元素,调用每一个关联元素的callback,同时带入元素的context和前面的额外参数args。如果任意一个关联元素的回调函数返回false,则停止运行其他的并返回false。
subscribe

订阅,如这个词自己的含义那样(就像订本杂志什么的),是建立topic和callback的关联的步骤。比较特别的是,amplify在这里还加入了priority(优先级)的概念,优先级的值越小,优先级越高,默认是10。优先级高的callback,将会在publish的时候,被先调用。这个顺序的原理可以从前面的publish的源码中看到,其实就是预先按照优先级从高到低依次排列好了某一topic的所有关联元素。

subscribe: function( topic, context, callback, priority ) {
    if ( typeof topic !== "string" ) {
      throw new Error( "You must provide a valid topic to create a subscription." );
    }
    // [1]
    if ( arguments.length === 3 && typeof callback === "number" ) {
      priority = callback;
      callback = context;
      context = null;
    }
    if ( arguments.length === 2 ) {
      callback = context;
      context = null;
    }
    priority = priority || 10;
    // [2]
    var topicIndex = 0,
      topics = topic.split( /\s/ ),
      topicLength = topics.length,
      added;
    for ( ; topicIndex < topicLength; topicIndex++ ) {
      topic = topics[ topicIndex ];
      added = false;
      if ( !subscriptions[ topic ] ) {
        subscriptions[ topic ] = [];
      }
      // [3]
      var i = subscriptions[ topic ].length - 1,
        subscriptionInfo = {
          callback: callback,
          context: context,
          priority: priority
        };
      // [4]
      for ( ; i >= 0; i-- ) {
        if ( subscriptions[ topic ][ i ].priority <= priority ) {
          subscriptions[ topic ].splice( i + 1, 0, subscriptionInfo );
          added = true;
          break;
        }
      }
      // [5]
      if ( !added ) {
        subscriptions[ topic ].unshift( subscriptionInfo );
      }
    }

    return callback;
  },

[1],要理解这一部分,请看amplify提供的API示意:

amplify.subscribe( string topic, function callback )
amplify.subscribe( string topic, object context, function callback )
amplify.subscribe( string topic, function callback, number priority )
amplify.subscribe(
  string topic, object context, function callback, number priority )

可以看到,amplify允许多种参数形式,而当参数数目和类型不同的时候,位于特定位置的参数可能会被当做不同的内容。这也在其他很多JavaScript库中可以见到。像这样,通过参数数目和类型的判断,就可以做到这种多参数形式的设计。

[2],订阅的时候,topic是允许空格的,空白符将被当做分隔符,认为是将一个callback关联到多个topic上,所以会使用一个循环。added用作标识符,表明新加入的这个元素是否已经添加到数组内,初始为false。

[3],每一个callback的保存,实际是一个对象,除callback外还带上了context(默认为null)和priority。

[4],这个循环是在根据priority的值,找到关联元素应处的位置。任何topic的关联元素都是从无到有,且依照priority数值从小到大排列(已排序的)。因此,在比较的时候,是先假设新加入的元素的priority数值较大(优先级低),从数组尾端向前比较,只要原数组中有关联元素的priority数值比新加入元素的小,循环就可以中断,且可以确定地用数组的splice方法将新加入的元素添加在此。如果循环一直运行到完毕,则可以确定新加入的元素的priority数值是最小的,此时added将保持为初始值false。

[5],如果到这个位置,元素还没有被添加,那么执行添加,切可以确定元素应该位于数组的最前面(或者是第一个元素)。
unsubscribe

虽然发布和订阅是最主要的,但也会有需要退订的时候(杂志不想看了果断退!)。所以,还会需要一个unsubscribe。

unsubscribe: function( topic, context, callback ) {
  if ( typeof topic !== "string" ) {
    throw new Error( "You must provide a valid topic to remove a subscription." );
  }

  if ( arguments.length === 2 ) {
    callback = context;
    context = null;
  }

  if ( !subscriptions[ topic ] ) {
    return;
  }

  var length = subscriptions[ topic ].length,
    i = 0;

  for ( ; i < length; i++ ) {
    if ( subscriptions[ topic ][ i ].callback === callback ) {
      if ( !context || subscriptions[ topic ][ i ].context === context ) {
        subscriptions[ topic ].splice( i, 1 );
        
        // Adjust counter and length for removed item
        i--;
        length--;
      }
    }
  }
}

读过前面的源码后,这部分看起来就很容易理解了。根据指定的topic遍历关联元素,找到callback一致的,然后删除它。由于使用的是splice方法,会直接修改原始数组,因此需要手工对i和length再做一次调整。
Amplify使用示例

官方提供的其中一个使用示例是:

amplify.subscribe( "dataexample", function( data ) {
  alert( data.foo ); // bar
});

//...

amplify.publish( "dataexample", { foo: "bar" } );

结合前面的源码部分,是否对发布/订阅这一设计模式有了更明确的体会呢?
补充说明

你可能也注意到了,AmplifyJS所实现的典型的发布/订阅是同步的(synchronous)。也就是说,在运行amplify.publish(topic)的时候,是会没有任何延迟地把某一个topic附带的所有回调,全部都运行一遍。
结语

Pub/Sub是一个比较容易理解的设计模式,但非常有用,可以应对大型应用的复杂逻辑。本文简析的AmplifyJS是我觉得写得比较有章法而且简明切题(针对单一功能)的JavaScript库,所以在此分享给大家。

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.