찾다
웹 프론트엔드JS 튜토리얼JavaScript_기본 지식을 바탕으로 클라이언트측 메시지 프레임워크의 설계 원칙을 자세히 설명합니다.

와~ 위험한 질문이군요? 본질적인 것에 대한 우리의 이해는 해결해야 할 문제에 대한 우리의 이해에 따라 확실히 달라집니다. 그래서 저는 거짓말을 하지 않을 것입니다. 제가 1년 전에 이해한 내용의 본질은 불행하게도 불완전합니다. 왜냐하면 제가 쓰려고 하는 내용이 현재 거의 6개월 동안 저와 함께 해왔다고 확신하기 때문입니다. 따라서 이 기사는 JavaScript에서 클라이언트 측 메시징 패턴을 성공적으로 사용하기 위한 몇 가지 핵심 사항을 제가 발견한 내용을 간략하게 보여줍니다.

1.) 중재자와 관찰자의 차이점을 이해하세요
대부분의 사람들은 이벤트/메시지 메커니즘을 설명할 때 "게시자/구독자"(pub/sub)를 사용하는 것을 좋아하지만 이 용어는 추상화와 잘 연결되지 않는 것 같습니다. 물론 근본적으로 어떤 것은 다른 것이 게시한 이벤트를 구독합니다. 그러나 게시자와 구독자가 캡슐화되는 수준으로 인해 좋은 패턴이 무뎌질 수 있습니다. 그렇다면 차이점은 무엇입니까?


관찰자

관찰자 패턴은 한 명 이상의 관찰자가 개체를 관찰하는 것과 관련됩니다. 일반적으로 이 객체는 모든 관찰자의 추적을 기록하며 일반적으로 관찰자가 등록한 콜백 메서드를 저장하는 목록을 사용하며 관찰자는 알림을 받기 위해 구독합니다. 참고: (아, 말장난 의도, 얼마나 사랑하는지) (번역자 주: 관찰 관찰, 참고)

var observer = {
 listen : function() {
  console.log("Yay for more cliché examples...");
 }
};
var elem = document.getElementById("cliche");
elem.addEventListener("click", observer.listen);

참고할 사항은 다음과 같습니다.

  • 이 객체에 대한 직접적인 참조가 필요합니다
  • 이 객체는 내부 상태를 유지하고 관찰자의 콜백 추적을 저장해야 합니다
  • * n은 실제로 무한대가 아니지만, 이 논의의 목적상 우리가 결코 도달할 수 없는 한계를 의미합니다.

중개자
중재자 패턴은 객체와 관찰자 사이에 '제3자'를 도입하여 둘을 효과적으로 분리하고 통신 방식을 캡슐화합니다. 중재자의 API는 "게시", "구독", "구독 취소"처럼 간단할 수도 있고, 좀 더 의미 있는 의미 뒤에 이러한 메서드를 숨기기 위해 도메인 전체 구현이 제공될 수도 있습니다. 내가 사용한 대부분의 서버 측 구현은 단순하기보다는 도메인 전체에 적용되는 경향이 있지만 일반 중재자를 제한하는 규칙은 없습니다! 일반적으로 보편적 중개자는 정보 중개자라는 생각이 있습니다. 두 경우 모두 결과는 동일합니다. 특정 객체와 관찰자는 더 이상 서로를 직접 알지 못합니다.



你可能会想,除了特别纯粹的中介者实现,特定对象不再负有保存订阅者列表的责任,而且“时光老人”(FatherTime)与“开发者”(Developer)实例永远没法真正互相知道。他们只是共享了一个信息——将如我们今后所见,这是一个很重要的合约。 “很好,Jim。这对我而言仍然是发布者/订阅者,那么重点呢?我选择某个方向真的会有区别吗?”哦,继续吧,亲爱的读者们,继续吧。

2.) 了解什么时候使用中介者和观察者

使用本地的观察者和中介者,即写在组件当中的,而中介者看起来又像远程的组件间通信。不管怎样。我对待这种情况的原则虽然是——tl;dr(too long; don't read)(太长,不读了)。但无论如何,反正串联在一起最好。

要我简捷地说真是麻烦,就像把几个月来的细致体验压缩到装不下140个字的沟里。现实中回答这个问题肯定不简洁。所以有一个长版本的解释:

    观察者除了关心数据映射之外还有必要引用别的项目吗?例如Backbone.View视图有各种理由直接引用它的模型。这是非常自然的关系,视图不仅要在模型改变时进行渲染,还需要调用模型的事件处理。如果段首的问题答案是”yes“,那观察者就是有意义的。
    如果观察者和观察对象的关系仅仅是依赖数据,那我愿意使用中介pub/sub方式。两个Backbone.View视图或模型之间的通信,用观察者是合适的。比如控制导航菜单的视图发出的信息,是面包屑(breadcrumb)挂件需要的(响应当前的层级)。挂件不需要引用导航视图,它只需要导航视图提供信息。更关键的,导航视图也许不是唯一的信息来源,别的视图可能也可以提供。此时,中介pub/sub模式是最理想的——而且自身扩展性良好。

看起来这样又好又全面,但是其实还有一个露点:如果我给对象定义一个本地事件,既想要观察者直接调用,又可以被订阅者间接访问到,怎么办?这就是我为什么说要串联在一起:你推送或者桥接本地事件到消息组去吧。需要些更多代码?很有可能——但是总比你把观察对象传递给所有观察者,一直紧耦合下去的情况好。然后,我们可以很好地继续以下两点...


3.) 选择性的“提交”本地事件到总线

最开始我几乎只用观察者模式来在JavaScript中触发事件。这是我们一次又一次遇到的模式,但更流行的客户端辅助库行为方式根本上来说是混合中介者的,给我们提供了就像它们是观察者模式的API。我最初写postal.js的时候,开始走进“为所有事物搭中介”的阶段。在我写的原型与构造函数中,分布各处的发布与订阅的调用并不罕见。当我从这个改变中自然的解耦受益时,非基础的代码开始似乎充满了相关于基础的部分。构造函数到处都要带上一个通道,订阅被当作新实例的一部分被创建,原型方法直接发布一个数值到总线(甚至本地的订阅者都不能直接的而必须监听总线以获得信息)。将这些明显关于总线的东西纳入app的这些部分,开始像是代码的味道。代码的“叙述”似乎总是被打断,如“噢,将这个向所有订阅者发布出去”,“等等!等等!监听这个通道那个事情。好,现在继续吧”。我的测试忽然开始需要依赖总线来做低层次的单元测试。而这感觉有点不对劲。

钟摆摆动的指向了中间,我认识到我应该保持一个“本地API”,并且在需要的时候通过一个中介者为应用扩展其可以触及的数据。 例如,我的backbone视图与模型,仍然用普通的Backbome.Events行为来给本地观察者发送事件(就是说,模型的事件被它相应的视图所观察)。当app的其它部分需要知道模型的变化时,我开始通过这些行将本地事件与总线桥接起来:
 

var SomeModel = Backbone.Model.extend({
 initialize: function() {
  this.on("change:superImportantField", function(model, value) {
   postal.publish({
    channel : "someChannel",
    topic : "omg.super.important.field.changed",
    data : {
    muyImportante: value,
    otherFoo: "otherBar"
    }
   });
  });
 }
});

重要的是要认识到,当有可能透明的推送事件到消息总线时,本地事件和消息必须被认为是分开的合约——至少概念上如此。换句话说,你要能够修改“内部的/本地的”事件而不破坏消息合约。这是要在脑海中记住的重要事实——否则你就是为紧耦合提供了一个新的途径,在一个方法上走反了!

所以理所当然,上述的模型是可以在没有消息总线的情况下被测试。而且如果我移去桥接在本地事件与总线之间的逻辑,我的视图与模型依然工作得毫无不畅。但是,这可是七行的例子(尽管格式化了)。 仅仅桥接四个事件就需要几乎三十行的代码。

噢,你怎样才能二者兼顾呢—— 在适合直接观察者时本地通知,同时使涉及事件可以扩展,以便你的对象不必给所有对象都发送一圈——不需要代码膨胀。通知怎样才能很少的代码又有更多的味道呢?

4.)在你的构架中隐藏样板

这并不是说上面的例子中的代码 —— 将事件接入总线 —— 的语法或概念是错误的(假设你接受本地和远程/桥接事件的概念)。然而,这是一个很好的体现在代码基础之上培养良好习惯的作用的例子。有时我们会听到类似“代码实在太多了”的抱怨(特别是当 LOC 作为代码质量的唯一判定者时)。 当这种情况下,我表示赞同。 它是一个可怕的样板。  下面是我在桥接 Backbone 对象的本地事件到 postal.js 时使用的模式:
 

// the logic to wire up publications and subscriptions
// exists in our custom MsgBackboneView constructor
var SomeView = MsgBackboneView.extend({
 
 className : "i-am-classy",
 
 // bridging local events triggered by this view
 publications: {
 // This is the more common 'shorthand' syntax
 // The key name is the name of the event. The
 // value is "channel topic" in postal. So this
 // means the bridgeTooFar event will get
 // published to postal on the "comm" channel
 // using a topic of "thats.far.enough". By default
 // the 1st argument passed to the event callback
 // will become the message payload.
 bridgeTooFar : "comm thats.far.enough",
 
 // However, the longhand approach works like this:
 // The key is still the event name that will be bridged.
 // The value is an object that provides a channel name,
 // a topic (which can be a string or a function returning
 // a string), and an optional data function that returns
 // the object that should be the message payload.
 bridgeBurned: {
  channel : "comm",
  topic : "match.lit",
  data : function() {
   return { id: this.get("id"), foo: 'bar' };
  }
 },
 
 // This is how we subscribe to the bus and invoke
 // local methods to handle incoming messages
 subscriptions: {
  // The key is the name of the method to invoke.
  // The value is the "channel topic" to subscribe to.
  // So this will subscribe to the "hotChannel" channel
  // with a topic binding of "start.burning.*", and any
  // message arriving gets routed to the "burnItWithFire"
  // method on the view.
  burnItWithFire : "hotChannel start.burning.*"
 },
 
 burnItWithFire: function(data, envelope) {
  // do stuff with message data and/or envelope
 }
 
 // other wire-up, etc.
});


显然你可以用几种不同的方式做这些——选择总线式的框架——这要比样板方式少很多无关内容,而且为Backbone开发人员所熟知。当你同时控制事件发送器和消息总线的实现时,桥接要更容易。这里有个将monologue.js发送器桥接到postal.js的例子: 
 

// using the 'monopost' add-on for monologue/postal:
// assuming we have a worker instance that has monologue
// methods on its prototype chain, etc. The keys are event
// topic bindings to match local events to, and if a match is
// found, it gets published to the channel specified in the
// value (using the same topic value)
worker.goPostal({
 "match.stuff.like.#" : "ThisChannelYo",
 "secret.sauce.*" : "SeeecretChannel",
 "another.*.topic" : "YayMoarChannelsChannel"
});

以不同的方式使用样板是令人愉快的好习惯。现在我可以分别独立的测试我的本地对象,桥接代码,甚至测试二者合一的生产&消费期待的消息过程等等。

同样重要的是要注意到,如果我需要在上述的场景访问普通的postal API,没有什么可以阻止我这么做。没有丢失灵活性这么就等于成功了


5.) 消息是合约——要明智的选择实现方式

有两种将数据传递给订阅者的方法——也许可以给他们贴上更“官方”的标签,我将如此描述他们:

  •     “0-n 参数”
  •     “封套” (或“单对象载荷“)

看看这些例子:
 

// 0-n args
this.trigger("someGuyBlogged", "Jim", "Cowart", "JavaScript");
// envelope style
this.emit("someGuyBlogged", {
 firstName: "Jim",
 lastName: "Cowart",
 category: "JavaScript"
});
/*
 In an emitter like monologue.js, the emit call above
 would actually publish an envelope that looked similar
 to this:
 {
  topic: "someGuyBlogged",
  timeStamp: "2013-02-05T04:54:59.209Z",
  data : {
   firstName: "Jim",
   lastName: "Cowart",
   category: "JavaScript"
  }
 }
*/

经过一段时间,我发现封套方式比0-n参数方式要少很多很多麻烦(与代码)。"0-n参数"途径的挑战主要在于两个原因(就我的经验而言):第一,很典型的是“当事件触发时,你还记得要传递哪一个参数吗?不记得?好,我想我会看看触发的源头”。不是一个真正意义上的好方法,对吗?但它可以打断代码的正常流程。你可以用一个调试工具,检测执行条件下的参数值并由此推断基于这些数值的”标签“,但哪个更简单呢——看到一个”1.21“的参数值,困惑于它的意义,或者检测一个对象并发现{千兆瓦:1.21}。第二个原因是由于伴随事件传送可选的数据,以及当方法签名变得更长带来的痛苦。


"说实话,Jim,你这是在搭车棚。"或许是的,但是一段时间以来我一直看到代码的基础在扩充与变形,简单的包含一两个参数的原始事件,在其间包含了可选的参数以后开始变得畸形:
 

// 最开始是这样的
this.trigger("someEvent", "a string!", 99);
// 有一天, 它变得包含了一切
this.trigger("someEvent", "string", 99, { sky: "blue" }, [1,2,3,4], true, 0);
// 可是等等——第4和第5个参数是可选的,因此也可能传的是:
this.trigger("someEvent", "string", 99, [1,2,3,4], true, 0);
// 噢,你还检查第5个参数的真/假吗?
// 哎呦!现在是早先的参数了……
this.trigger("someEvent", "string", 99, true, 0);

선택 사항인 데이터가 있는 경우 해당 데이터에 대한 테스트는 없습니다. 그러나 코드가 더 적게 필요하고 확장성이 더 높아야 하며 일반적으로 개체가 전달될 때 개체에 대해 수행되는 종류의 테스트를 수행할 수 있도록 자체 설명이 가능해야 합니다(멤버 이름 덕분에). 구독자 콜백 메소드를 하나씩. 나는 여전히 "0-n 매개변수"를 사용해야 하는 경우에 이 방법을 사용하지만, 그것이 나에게 달렸다면 이벤트 발신자와 메시지 버스 모두에 대해 항상 봉투 접근 방식을 사용하는 것입니다. (편향된 모습을 보여줍니다. Monologue와 Postal은 동일한 봉투 데이터 구조를 공유하며, Monologue가 사용하지 않는 채널은 제거됩니다.)

따라서 - 가입자에게 데이터를 전송하는 데 사용되는 구조는 "계약"의 일부라는 점을 인식해야 합니다. 래퍼 방향에서는 추가 매개변수를 추가하지 않고 추가 메타데이터를 사용하여 이벤트를 설명할 수 있습니다. 이렇게 하면 각 이벤트 및 구독자에 대해 메서드 서명(계약의 일부)이 일관되게 유지됩니다. 또한 메시지 구조의 버전을 쉽게 지정할 수도 있습니다(또는 필요한 경우 다른 봉투 수준 메시지를 추가할 수도 있습니다). 이 방향으로 갈 경우 일관된 봉투 구조를 사용해야 합니다.


6.) 메시지 '토폴로지'는 생각보다 중요합니다

여기에는 묘책이 없습니다. 하지만 주제와 채널의 이름을 지정하는 방법과 메시지 페이로드를 구성하는 방법에 대해 신중하게 생각해야 합니다. 나는 두 가지 방법 중 하나로 모델을 매핑하는 경향이 있습니다. 단일 데이터 채널을 사용하면 주제 앞에 모델 이름이 붙고 그 뒤에 고유 ID가 붙으며, 해당 모델은 해당 작업({modelType.id.operation})에 의해 처리됩니다. , 또는 모델 자체 채널의 경우 주제는 {id.operation}입니다. 모델이 데이터를 요청할 때 이 동작에 자동으로 응답하는 것이 지속적인 관행입니다. 그러나 버스의 모든 작업이 요청되는 것은 아닙니다. 앱에 간단한 이벤트가 게시될 수 있습니다. 이벤트를 설명하기 위해 테마의 이름을 지정합니까(이상적으로는)? 아니면 구독자의 성향이 있는 행동을 설명하기 위해 주제에 이름을 지정하는 함정에 빠졌습니까? 예를 들어 "route.changed" 또는 "show.customer.ui" 주제가 포함된 메시지입니다. 하나는 이벤트를 나타내고 다른 하나는 순서를 나타냅니다. 이러한 결정을 내릴 때 신중하게 생각하십시오. 명령은 나쁘지 않지만 요청/응답 또는 명령이 필요하기 전에 얼마나 많은 이벤트를 설명할 수 있는지 놀라게 될 것입니다.

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

웹 개발에서 JavaScript의 주요 용도에는 클라이언트 상호 작용, 양식 검증 및 비동기 통신이 포함됩니다. 1) DOM 운영을 통한 동적 컨텐츠 업데이트 및 사용자 상호 작용; 2) 사용자가 사용자 경험을 향상시키기 위해 데이터를 제출하기 전에 클라이언트 확인이 수행됩니다. 3) 서버와의 진실한 통신은 Ajax 기술을 통해 달성됩니다.

JavaScript 엔진 이해 : 구현 세부 사항JavaScript 엔진 이해 : 구현 세부 사항Apr 17, 2025 am 12:05 AM

보다 효율적인 코드를 작성하고 성능 병목 현상 및 최적화 전략을 이해하는 데 도움이되기 때문에 JavaScript 엔진이 내부적으로 작동하는 방식을 이해하는 것은 개발자에게 중요합니다. 1) 엔진의 워크 플로에는 구문 분석, 컴파일 및 실행; 2) 실행 프로세스 중에 엔진은 인라인 캐시 및 숨겨진 클래스와 같은 동적 최적화를 수행합니다. 3) 모범 사례에는 글로벌 변수를 피하고 루프 최적화, Const 및 Lets 사용 및 과도한 폐쇄 사용을 피하는 것이 포함됩니다.

Python vs. JavaScript : 학습 곡선 및 사용 편의성Python vs. JavaScript : 학습 곡선 및 사용 편의성Apr 16, 2025 am 12:12 AM

Python은 부드러운 학습 곡선과 간결한 구문으로 초보자에게 더 적합합니다. JavaScript는 가파른 학습 곡선과 유연한 구문으로 프론트 엔드 개발에 적합합니다. 1. Python Syntax는 직관적이며 데이터 과학 및 백엔드 개발에 적합합니다. 2. JavaScript는 유연하며 프론트 엔드 및 서버 측 프로그래밍에서 널리 사용됩니다.

Python vs. JavaScript : 커뮤니티, 라이브러리 및 리소스Python vs. JavaScript : 커뮤니티, 라이브러리 및 리소스Apr 15, 2025 am 12:16 AM

Python과 JavaScript는 커뮤니티, 라이브러리 및 리소스 측면에서 고유 한 장점과 단점이 있습니다. 1) Python 커뮤니티는 친절하고 초보자에게 적합하지만 프론트 엔드 개발 리소스는 JavaScript만큼 풍부하지 않습니다. 2) Python은 데이터 과학 및 기계 학습 라이브러리에서 강력하며 JavaScript는 프론트 엔드 개발 라이브러리 및 프레임 워크에서 더 좋습니다. 3) 둘 다 풍부한 학습 리소스를 가지고 있지만 Python은 공식 문서로 시작하는 데 적합하지만 JavaScript는 MDNWebDocs에서 더 좋습니다. 선택은 프로젝트 요구와 개인적인 이익을 기반으로해야합니다.

C/C에서 JavaScript까지 : 모든 것이 어떻게 작동하는지C/C에서 JavaScript까지 : 모든 것이 어떻게 작동하는지Apr 14, 2025 am 12:05 AM

C/C에서 JavaScript로 전환하려면 동적 타이핑, 쓰레기 수집 및 비동기 프로그래밍으로 적응해야합니다. 1) C/C는 수동 메모리 관리가 필요한 정적으로 입력 한 언어이며 JavaScript는 동적으로 입력하고 쓰레기 수집이 자동으로 처리됩니다. 2) C/C를 기계 코드로 컴파일 해야하는 반면 JavaScript는 해석 된 언어입니다. 3) JavaScript는 폐쇄, 프로토 타입 체인 및 약속과 같은 개념을 소개하여 유연성과 비동기 프로그래밍 기능을 향상시킵니다.

JavaScript 엔진 : 구현 비교JavaScript 엔진 : 구현 비교Apr 13, 2025 am 12:05 AM

각각의 엔진의 구현 원리 및 최적화 전략이 다르기 때문에 JavaScript 엔진은 JavaScript 코드를 구문 분석하고 실행할 때 다른 영향을 미칩니다. 1. 어휘 분석 : 소스 코드를 어휘 단위로 변환합니다. 2. 문법 분석 : 추상 구문 트리를 생성합니다. 3. 최적화 및 컴파일 : JIT 컴파일러를 통해 기계 코드를 생성합니다. 4. 실행 : 기계 코드를 실행하십시오. V8 엔진은 즉각적인 컴파일 및 숨겨진 클래스를 통해 최적화하여 Spidermonkey는 유형 추론 시스템을 사용하여 동일한 코드에서 성능이 다른 성능을 제공합니다.

브라우저 너머 : 실제 세계의 JavaScript브라우저 너머 : 실제 세계의 JavaScriptApr 12, 2025 am 12:06 AM

실제 세계에서 JavaScript의 응용 프로그램에는 서버 측 프로그래밍, 모바일 애플리케이션 개발 및 사물 인터넷 제어가 포함됩니다. 1. 서버 측 프로그래밍은 Node.js를 통해 실현되며 동시 요청 처리에 적합합니다. 2. 모바일 애플리케이션 개발은 재교육을 통해 수행되며 크로스 플랫폼 배포를 지원합니다. 3. Johnny-Five 라이브러리를 통한 IoT 장치 제어에 사용되며 하드웨어 상호 작용에 적합합니다.

Next.js (백엔드 통합)로 멀티 테넌트 SAAS 애플리케이션 구축Next.js (백엔드 통합)로 멀티 테넌트 SAAS 애플리케이션 구축Apr 11, 2025 am 08:23 AM

일상적인 기술 도구를 사용하여 기능적 다중 테넌트 SaaS 응용 프로그램 (Edtech 앱)을 구축했으며 동일한 작업을 수행 할 수 있습니다. 먼저, 다중 테넌트 SaaS 응용 프로그램은 무엇입니까? 멀티 테넌트 SAAS 응용 프로그램은 노래에서 여러 고객에게 서비스를 제공 할 수 있습니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

VSCode Windows 64비트 다운로드

VSCode Windows 64비트 다운로드

Microsoft에서 출시한 강력한 무료 IDE 편집기

맨티스BT

맨티스BT

Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.

ZendStudio 13.5.1 맥

ZendStudio 13.5.1 맥

강력한 PHP 통합 개발 환경

Dreamweaver Mac版

Dreamweaver Mac版

시각적 웹 개발 도구

MinGW - Windows용 미니멀리스트 GNU

MinGW - Windows용 미니멀리스트 GNU

이 프로젝트는 osdn.net/projects/mingw로 마이그레이션되는 중입니다. 계속해서 그곳에서 우리를 팔로우할 수 있습니다. MinGW: GCC(GNU Compiler Collection)의 기본 Windows 포트로, 기본 Windows 애플리케이션을 구축하기 위한 무료 배포 가능 가져오기 라이브러리 및 헤더 파일로 C99 기능을 지원하는 MSVC 런타임에 대한 확장이 포함되어 있습니다. 모든 MinGW 소프트웨어는 64비트 Windows 플랫폼에서 실행될 수 있습니다.