Úvod do dcl

Jsem neuvěřitelně poctěn, že Eugene Lazutkin je autorem blogu Davida Walshe. Eugene napsal velkou část vektorového grafického kódu pro knihovnu dojox/gfx (a následné zdroje mapování a kreslení) Dojo Toolkit, což je knihovna, kterou považuji za úžasně úžasnou. Eugene se rozhodl napsat o dcl , ultraflexibilní, malá knihovna OOP JS.

dcl je minimalistický, ale kompletní JavaScriptový balíček fornode.js a moderní prohlížeče. Implementuje OOP s mixiny + AOP na úrovni "tříd" i na úrovni objektů a pracuje v přísných i nepřísných režimech.

Nejjednodušší způsob, jak se něco naučit, je ponořit se do toho. Pojďme implementovat jednoduchý widget založený na reaktivním šablonování:když změníme parametry widgetu, okamžitě se projeví na webové stránce.

Za předpokladu, že spustíme náš kód ve formátu AMD v prohlížeči, bude náš „kódový shell“ vypadat takto:

require(
  ["dcl", "dcl/bases/Mixer", "dcl/mixins/Cleanup", "dcl/advices/memoize"],
  function(dcl, Mixer, Cleanup, memoize){
    // our code goes here
  }
);

Jako první krok nakódujme náš datový model:

var Data = dcl(Mixer, {
  declaredClass: "Data",
  updateData: function(data){
    dcl.mix(this, data);
  }
});

Naši třídu jsme odvodili pomocí jednoduché dědičnosti z Mixeru, který je dodáván s dcl .Mixer je velmi jednoduchý základ. Vše, co dělá, je kopírování vlastností prvního argumentu konstruktoru do instance.

V tomto jednoduchém příkladu bychom samozřejmě mohli zavolat updateData() z našeho konstruktoru, ale předpokládejme, že konstruktor a aktualizátor mohou dělat (trochu) odlišné věci a chceme je ponechat odděleně.

declaredClass je zcela nepovinné, přesto se doporučuje specifikovat (jakýkoli jedinečný lidsky čitelný název je v pořádku), protože jej používají ladící pomocníci obsažení v `dcl`.

Nyní nakódujme náš šablonovací modul nano velikosti, který nahrazuje řetězce jako tento:${abc} s vlastnostmi převzatými přímo z instance (this.abc v tomto případě). Něco takového:

var Template = dcl(null, {
  declaredClass: "Template",
  render: function(templateName){
    var self = this;
    return this[templateName].replace(/\$\{([^\}]+)\}/g, function(_, prop){
      return self[prop];
    });
  }
});

Určujeme, jakou šablonu použít, podle názvu, což je název vlastnosti na instanci objektu, a vyplní řetězec šablony pomocí vlastností specifikovaných na objektu.

Toto je další ukázka jediné dědičnosti:naše Template je založen na obyčejné vanilce Object , jako každý objekt JavaScriptu, což je označeno pomocí null jako základ.

Co ještě potřebujeme? Potřebujeme způsob, jak spravovat náš uzel DOM:

var Node = dcl([Mixer, Cleanup], {
  show: function(text){
    if(this.node){
      this.node.innerHTML = text;
    }
  },
  destroy: function(){
    if(this.node){
      this.node.innerHTML = "";
    }
  }
});

Výše uvedený kód poskytuje způsob, jak zobrazit některé HTML, a vymaže jeho prezentaci, když destroy() widget.

Používá dvě báze:již zmíněný Mixer se používá k získání vlastnosti během inicializace (node v tomto případě) a Cleanup, který je opět dodáván s dcl .Poslední zřetězení všech destroy() metody dohromady a poskytuje jednoduchý základ pro správu vyčištění, takže všechny zdroje mohou být řádně zlikvidovány.

To, co jsme až do tohoto bodu udělali, je, že jsme přišli s velmi malými ovladatelnými ortogonálními součástmi, které odrážejí různé strany našeho widgetu a lze je kombinovat v různých konfiguracích. Pojďme je teď dát všechny dohromady:

var NameWidget0 = dcl([Data, Template, Node], {
  declaredClass: "NameWidget0",
  template: "Hello, ${firstName} ${lastName}!"
});

var x = new NameWidget0({
  node:      document.getElementById("name"),
  firstName: "Bob",
  lastName:  "Smith"
});

x.show(x.render("template")); // Hello, Bob Smith!
x.updateData({firstName: "Jill"});
x.show(x.render("template")); // Hello, Jill Smith!

Funguje to, ale není to příliš koherentní a příliš podrobné. Nebojte se, brzy to napravíme.

Někteří čtenáři si pravděpodobně všimli, že nyní máme tři báze:Data , Template a Node a dva z nich (Data a Node ) jsou založeny na Mixer .Jak to funguje? Funguje to dobře, protože pod dcl používá linearizační algoritmus nadtřídy C3 (stejný, jaký používá Python), který odstraňuje duplikáty a třídí báze, aby zajistil, že jejich požadované pořadí je správné. V tomto případě jedna kopie Mixin by měl být před oběma Data a Node . Přečtěte si více o tomto tématu v dokumentaci dcl().

Nyní pojďme řešit nedostatky naší implementace #0:

  • Jakmile je vytvořen widget, měli bychom zobrazit text.
  • Jakmile budou data aktualizována, měli bychom zobrazit text.

Oba požadavky jsou jednoduché a zdá se, že vyžadují staromódní superhovory:

var NameWidget1 = dcl([Data, Template, Node], {
  declaredClass: "NameWidget1",
  template: "Hello, ${firstName} ${lastName}!",
  constructor: function(){
    this.showData();
  },
  updateData: dcl.superCall(function(sup){
    return function(){
      sup.apply(this, arguments);
      this.showData();
    };
  }),
  showData: function(){
    var text = this.render("template");
    this.show(text);
  }
});

var x = new NameWidget1({
  node:      document.getElementById("name"),
  firstName: "Bob",
  lastName:  "Smith"
});
// Hello, Bob Smith!

x.updateData({firstName: "Jill"}); // Hello, Jill Smith!

Mnohem lepší!

Pojďme se podívat na dvě nové věci:konstruktor a supercall. Oba mají být supervolání, ale vypadají jinak. Například konstruktor nevolá svou super metodu. Proč? Protože dcl řetězcové konstruktory automaticky.

updateData() je přímočará:nejprve volá super, pak metodu k aktualizaci vizuálu. Ale je deklarován pomocí vzoru dvojité funkce. Proč? Ze dvou důvodů:efektivita běhu a snadné ladění. Přečtěte si o tom vše v dokumentaci dcl.superCall() a Supercalls v JS.

I když tato implementace vypadá dobře, není ani zdaleka „v pořádku“. Buďme chytří a těšme se:v reálném životě bude naše implementace modifikována a rozšiřována generacemi vývojářů. Někteří se pokusí na tom stavět.

  • Naše volání na číslo showData() instruct nebude posledním provedeným kódem, jak jsme očekávali. Budou po něm volány konstruktory odvozených tříd.
  • updateData() budou přepsány a někteří programátoři mohou zapomenout zavolat super. Opět mohou aktualizovat data ve svém kódu po našem kódu s názvem showData() výsledkem jsou zobrazená zastaralá data.

Je zřejmé, že můžeme psát dlouhé komentáře dokumentující naše "implementační rozhodnutí" a navrhující budoucím programátorům způsoby, jak to udělat správně, ale kdo čte dokumenty a komentáře, zvláště když píše "průmyslový" kód v tísni?

Bylo by hezké vyřešit tyto problémy čistým elegantním způsobem. Je to vůbec možné? Samozřejmě. Proto máme AOP.

Přepišme náš pokus č. 1:

var NameWidget2 = dcl([Data, Template, Node], {
  declaredClass: "NameWidget2",
  template: "Hello, ${firstName} ${lastName}!",
  constructor: dcl.after(function(){
    this.showData();
  }),
  updateData: dcl.after(function(){
    this.showData();
  }),
  showData: function(){
    var text = this.render("template");
    this.show(text);
  }
});

var x = new NameWidget2({
  node:      document.getElementById("name"),
  firstName: "Bob",
  lastName:  "Smith"
});
// Hello, Bob Smith!

x.updateData({firstName: "Jill"}); // Hello, Jill Smith!

Nejen, že jsme získali (o něco) menší kód, ale nyní máme zaručeno, žeshowData() je voláno po všech možných konstruktorech a po každém vyvolání updateData() , který lze zcela nahradit kódem, který může používat supervolání. Je nám to vlastně jedno --- jen jsme specifikovali kód, který bude proveden *po* tom, co tam vložili jiní programátoři.

Nyní si představte, že náš uživatel chce kliknout na jméno a získat vyskakovací okno s podrobnějšími informacemi, např. záznamem HR této osoby. Bylo by rozumné uchovávat informace na jednom místě, a přesto je vykreslovat jinak. A už na to máme opatření:můžeme přidat další vlastnost šablony a zavolat render() s jeho názvem:

var PersonWidget1 = dcl(NameWidget2, {
  declaredClass: "PersonWidget1",
  detailedTemplate: "..."
});

var x = new PersonWidget1({
  node:      document.getElementById("name"),
  firstName: "Bob",
  lastName:  "Smith",
  position:  "Programmer",
  hired:     new Date(2012, 0, 1) // 1/1/2012
});
// Hello, Bob Smith!

var detailed = x.render("detailedTemplate");

Ve výše uvedeném příkladu jsem pro stručnost vynechal definici podrobné šablony. Ale můžete vidět, že můžeme přidat více informací o osobě a můžeme definovat různé šablony, když nastane potřeba.

Představte si, že jsme profilovali naši novou implementaci a ukázalo se, že nazýváme render() přímo i nepřímo velmi často a přináší určitá měřitelná zpoždění. Můžeme předběžně vykreslit šablonu při každé aktualizaci dat, přesto to zní jako spousta práce pro několik složitých šablon a některé z nich ani nebudou použity. Lepším řešením je implementovat nějaký druh líného ukládání do mezipaměti:zrušíme platnost mezipaměti při každé aktualizaci, přesto vytvoříme řetězec pouze na požádání.

Tyto změny se samozřejmě týkají jak Data a Template . Nebo to lze provést downstream v NameWidget nebo PersonWidget . Nyní se podívejte výše a prosím, neprovádějte tyto změny:dosud jsme se snažili udržet naše „třídy“ ortogonální a ukládání do mezipaměti je jednoznačně ortogonální záležitostí.

dcl již poskytuje jednoduché řešení:memoize rady. Použijme to v našem příkladu:

var PersonWidget2 = dcl(NameWidget2, {
  declaredClass: "PersonWidget2",
  detailedTemplate: "...",
  // memoization section:
  render:     dcl.advise(memoize.advice("render")),
  updateData: dcl.advise(memoize.guard ("render"))
});

S těmito dvěma řádky jsme přidali naše render() výsledek je uložen do mezipaměti pro každou první hodnotu parametru (v našem případě „template“ nebo „detailedTemplate“) a pokaždé, když zavoláme updateData(), mezipaměť bude zrušena .

V tomto článku jsme představili dcl balík. Pokud jej plánujete použít ve svém projektu Node.js, nainstalujte jej takto:

npm install dcl

Pro vaše projekty založené na prohlížeči doporučuji použít volo.js:

volo install uhop/dcl

Kód je open source na github.com/uhop/dclunder Nové licence BSD a AFL v2.

Tento článek se nezabýval mnoha dalšími věcmi, které poskytuje dcl :

  • Ve starších projektech pomocí inherited() se vyhněte vzoru dvojité funkce supervolání.
  • Používejte AOP na úrovni objektu --- dynamicky přidávejte a odebírejte rady v libovolném pořadí.
  • U každé metody zadejte automatické řetězení „před“ a „po“.
  • Používejte pomocníky pro ladění, které jsou součástí dcl .
  • Využijte malou knihovnu předpřipravených rad a mixů, které poskytuje dcl .

Pokud se o tom chcete dozvědět více nebo jste jen zvědaví, spoustu informací najdete v dokumentaci.

Šťastné kódování DRY!