DOMを操作するための改善されたJavaScriptライブラリの作成について

JQueryは現在、DOMを操作するための事実上のライブラリです。 一般的なMV *フレームワーク(Backboneなど)と一緒に使用でき、多くのプラグインと非常に大きなコミュニティがあります。 一方、JavaScriptの人気が高まっており、多くの開発者は、標準APIがどのように機能し、追加のライブラリを追加せずに単純に使用できるのか疑問に思っています。



jQueryを使用していた末期に、このライブラリにさまざまな問題があることに気付き始めました。 それらのほとんどは基本的なものであるため、下位互換性を失うことなく修正することはできません。これはもちろん重要です。 私は、他の多くの人と同じように、ライブラリをしばらく使用し続け、迷惑な癖に毎日会いました。



その後、 ダニエル・ブフナーSelectorListenerを作成し、ライブ拡張機能のアイデアが生まれました。 より良いアプローチを使用して、目立たず独立したDOMコンポーネントを作成できる機能セットの作成について考え始めました。 タスクは、既存のソリューションをレビューし、より理解しやすく、テスト可能で、小さいながらも自給自足のライブラリを作成することでした。



ライブラリに便利な機能を追加する



ライブ拡張機能のアイデアは、better-domプロジェクトの開発貢献しましたが、それ以外にもライブラリをユニークにする興味深い機能があります。 それらを簡単に見てみましょう。



ライブ拡張機能


jQueryには、ライブイベントの概念があります。 舞台裏では、イベント委任を使用して、既存および将来の要素を処理します。 ただし、多くの場合、より高い柔軟性が必要です。 たとえば、初期化中にウィジェットが既存の要素と対話または置換する追加要素をドキュメントツリーに追加する必要がある場合、ライブイベントは機能しません。 この問題を解決するために、ライブ拡張機能を紹介します。



目標は、拡張機能を一度宣言し、その後、ウィジェットの複雑さに関係なく将来のコンテンツで機能するようにすることです。 これは、Webページを宣言的に作成できるため、AJAXアプリケーションに適しているため、重要な機能です。



簡単な例を考えてみましょう。 私たちのタスクは、完全にカスタマイズ可能なツールチップを実装することだとしましょう。 :hover



疑似セレクター:hover



、マウスカーソルに応じてツールチップの位置が変わるため、適切で:hover



ません。 イベントの委任も適切ではありません。ページ上のすべての要素のmouseover



mouseover



mouseleave



を聞くには高すぎます。 これがライブエクステンションが登場する場所です。

 DOM.extend("[title]", { constructor: function() { var tooltip = DOM.create("span.custom-title"); //  textContent     // title       tooltip.set("textContent", this.get("title")).hide(); this //   tooltip .set("title", null) //       .data("tooltip", tooltip) //    .on("mouseenter", this.onMouseEnter, ["clientX", "clientY"]) .on("mouseleave", this.onMouseLeave) //     DOM .append(tooltip); }, onMouseEnter: function(x, y) { this.data("tooltip").style({left: x, top: y}).show(); }, onMouseLeave: function() { this.data("tooltip").hide(); } });
      
      





CSSの.custom-title



セレクターを使用して、ツールチップのスタイルを設定できるようになりました。

 .custom-title { position: fixed; border: 1px solid #faebcc; background: #faf8f0; }
      
      





ただし、 title



属性を持つ新しい要素がページに追加されると、楽しい部分が始まります。 これらは、初期化関数を呼び出さずに拡張機能によって取得されます



ライブ拡張機能は自給自足型であるため、将来のコンテンツを操作するために特定の機能を使用する必要はありません。 したがって、DOMの既存のライブラリと組み合わせて 、UIコードを多くの小さな独立した部分に分割することにより、アプリケーションロジックを簡素化できます。



結論として、 Webコンポーネントに関するいくつかの言葉。 「 デコレータ 」と呼ばれる仕様の1つおよびセクションは、同様の問題を解決するために設計されています。 現在、マークアップと特別な構文を使用して、リスナーを子にハングさせます。 しかし、これはまだ非常に初期のドラフトです。

デコレータは、Webコンポーネントの他のセクションとは異なり、まだ仕様がありません。


ネイティブアニメーション


Appleのおかげで 、CSSは現在、 優れたアニメーションをサポートしています。 以前は、アニメーションはsetInterval



setTimeout



を使用してJavaScriptで実装されていました。 それはクールなことでしたが、今...少し悪い習慣。 ネイティブアニメーションは常によりスムーズになります。通常は高速で消費電力が少なく、サポートしていないブラウザには表示されません。



better-domにはanimate



メソッドはありません: show



hide



およびtoggle



のみです。 CSSで非表示要素の状態をキャプチャするために、ライブラリは標準化されたaria-hidden



属性を使用します。



アプローチを説明するために、前に書いた簡単なツールチップアニメーションを追加しましょう。

 .custom-title { position: fixed; border: 1px solid #faebcc; background: #faf8f0; /*  */ opacity: 1; -webkit-transition: opacity 0.5s; transition: opacity 0.5s; } .custom-title[aria-hidden=true] { opacity: 0; }
      
      





show



hide



内部hide



aria-hidden



属性はその値をfalse



またはtrue



変更します。 CSSを使用してアニメーションを表示するにはこれで十分です。



より良いdomを備えたより多くのアニメーションの例。



組み込みのテンプレートエンジン


HTML文字列はかさばります。 代替品を探し始めたとき、素晴らしいEmmetプロジェクトを見つけました。 現在、テキストエディターのプラグインとして非常に人気があり、簡潔でコンパクトな構文を持っています。 比較する:

 body.append("<ul><li class='list-item'></li><li class='list-item'></li><li class='list-item'></li></ul>");
      
      





同等です

 body.append("ul>li.list-item*3");
      
      





より良い方法では、引数としてHTML文字列を受け入れるメソッドは、エメットの略語もサポートします。 略語パーサーは高速なので、パフォーマンスの低下を心配する必要はありません。 テンプレートをプリコンパイルする機能もあり、必要に応じて使用できます。



国際化サポート


UIウィジェットの設計にはローカリゼーションが必要になることがよくありますが、これは必ずしも簡単な作業ではありません。 多くの人がこの問題を独自の方法で解決しました。 より良いdomでは、言語変更することはCSSセレクターの状態を変更することより難しくないことを願っています。



イデオロギーの観点から見ると、言語の切り替えはコンテンツの「表示」を変更するようなものです。 CSS2には、このようなモデルの説明に役立ついくつかの疑似セレクター:lang



and :before



ます。 以下のコードをご覧ください。

 [data-i18n="hello"]:before { content: "Hello Maksim!"; } [data-i18n="hello"]:lang(ru):before { content: " !"; }
      
      





秘Theは、 content



プロパティが現在の言語に従って変化することです。これは、 html



要素のlang



属性の値によって決まります。 data-i18n



属性を使用して、より一般的な表記を使用できます。

 [data-i18n]:before { content: attr(data-i18n); } [data-i18n="Hello Maksim!"]:lang(ru):before { content: " !"; }
      
      





もちろん、このようなCSSコードは見栄えがよくないため、better-domには2つのDOM.importStrings



i18n



DOM.importStrings



ます。 1つ目はdata-i18n



属性を対応する値で更新するために使用され、2つ目は特定の言語の文字列をローカライズします。

 label.i18n("Hello Maksim!"); // label  "Hello Maksim!" DOM.importStrings("ru", "Hello Maksim!", " !"); //     "ru",  label   " !" label.set("lang", "ru"); //  label  " !"    
      
      





パラメータ化された文字列もサポートされています:キー文字列に${param}



変数を追加するだけです:

 label.i18n("Hello ${user}!", {user: "Maksim"}); // label  "Hello Maksim!"
      
      





ネイティブAPIの改善



通常、標準を遵守したいと考えています。 しかし、時々、標準は完全に友好的ではありません。 DOMは非常に紛らわしいため、便利にするには、便利なAPIでラップする必要があります。 さまざまなライブラリーによって多くの改善が行われましたが、いくつかの改善を行うことができます。



ゲッターとセッター


ネイティブDOMには、異なる動作が可能な要素の属性とプロパティの概念があります 。 ページにマークアップがあるとします:

 <a href="/chemerisuk/better-dom" id="foo" data-test="test">better-dom</a>
      
      





ネイティブDOMの敵意を説明するために、少し操作してみましょう。

 var link = document.getElementById("foo"); link.href; // => "https://github.com/chemerisuk/better-dom" link.getAttribute("href"); // => "/chemerisuk/better-dom" link["data-test"]; // => undefined link.getAttribute("data-test"); // => "test" link.href = "abc"; link.href; // => "https://github.com/abc" link.getAttribute("href"); // => "abc"
      
      





そのため、属性値は HTMLの対応する行等しくなりますが 、同じ名前の要素プロパティは 、たとえば上記の例で完全なURLを生成するなど、特別な動作をする場合があります。 この違いは時々混乱する可能性があります。



実際には、このような分離がいつ役立つか想像するのは困難です。 さらに、開発者は、使用している値を常に監視する必要があるため、不必要な複雑さが増します。



優れた点では、物事はより単純です。 各要素にはスマートなゲッターとセッターのみがあります。

 var link = DOM.find("#foo"); link.get("href"); // => "https://github.com/chemerisuk/better-dom" link.set("href", "abc"); link.get("href"); // => "https://github.com/abc" link.get("data-attr"); // => "test"
      
      





最初のステップで、メソッドは要素のプロパティを検索し、定義されている場合は操作に使用します。 それ以外の場合は、対応する属性で機能します。 ブール属性( checked



selected



など)の場合、単にtrue



またはfalse



使用できfalse



。 要素のこれらのプロパティを変更すると、対応する属性が更新されます(ネイティブの動作)。



イベント処理の改善


イベント処理は、DOMのコーディングの重要な部分です。 私が発見した根本的な問題の1つは、要素リスナーにイベントオブジェクトが存在するため、テスト対象のコードが好きな開発者が最初の引数をウェットにするか、このハンドラーで使用されるイベントプロパティを受け入れる追加関数を作成することです。

 var button = document.getElementById("foo"); button.addEventListener("click", function(e) { handleButtonClick(e.button); }, false);
      
      





これにより、実際に余分な関数呼び出しが発生し、追加されます。 変化する部分を引数として強調するとどうなるでしょうか。これはクロージャーを取り除きます:

 var button = DOM.find("#foo"); button.on("click", handleButtonClick, ["button"]);
      
      





デフォルトでは、イベントハンドラーは配列["target", "defaultPrevented"]



ため、これらのプロパティを読み取るために最後の引数を追加する必要はありません。

 button.on("click", function(target, canceled) { //   });
      
      





遅延バインディングもサポートされていますトピックに関するPeter Michauxの記事を読むことをお勧めします )。 これは、偶然にも標準に存在する従来のイベントハンドラに対するより柔軟な代替手段です。 on



メソッドとoff



メソッドを頻繁に呼び出す必要がある場合に役立ちます。

 button._handleButtonClick = function() { alert("click!"); }; button.on("click", "_handleButtonClick"); button.fire("click"); //   "clicked" button._handleButtonClick = null; button.fire("click"); //   
      
      





結論として、標準では存在し、ブラウザーで異なる動作をするclick()



focus()



submit()



などのメソッドはありません。 それらを呼び出す唯一の方法は、 fire



メソッドを使用することです。このメソッドは、どのハンドラーもfalse



返さない場合にデフォルトの動作を実行します。

 link.fire("click"); //    link.on("click", function() { return false; }); link.fire("click"); //        
      
      





機能的メソッドのサポート


ES5は、 map



filter



some



などの配列のいくつかの便利なメソッドを標準化しました。 標準化された方法でコレクションの操作を実行できます。 その結果、今日では、 アンダースコアLo-Dashのようなプロジェクトがあり、これらのメソッドを古いブラウザで使用できます。



better-domの各要素(またはコレクション)には、すぐに使用できる以下のメソッドがあります。



 var urls, activeLi, linkText; urls = menu.findAll("a").map(function(el) { return el.get("href"); }); activeLi = menu.children().filter(function(el) { return el.hasClass("active"); }); linkText = menu.children().reduce(function(memo, el) { return memo || el.hasClass("active") && el.find("a").get() }, false);
      
      





jQueryの問題を解決する



以下の問題のほとんどは、後方互換性を失わずにjQueryで修正することはできません。 新しいライブラリを作成することが決定されたもう1つの理由。



$マジック機能


関数$



(ドル)が「魔法」であることは誰もが聞いたことがあるでしょう。 1文字のみで構成される名前はあまり明確ではなく、関数は言語に組み込まれたステートメントのように見えます。 それが、経験の浅い開発者が必要な場所で単純に呼び出す理由です。



舞台裏では、 $



はかなり複雑な機能です
。 特にmousemove



scroll



などのイベント内で頻繁に実行すると、UIの応答性が低下する可能性があります。



jQueryオブジェクトのキャッシングを促進する多くの記事にもかかわらず、開発者は引き続き$



を埋め込みます。 これは、ライブラリの構文がこのコーディングスタイルを促進するためです。



この関数の別の問題は、2つの完全に異なるタスクを担当することです。 人々はすでにこの構文に慣れていますが、これは一般的な場合の関数設計の良い習慣ではありません。

 $("a"); // =>   ,    “a” $("<a>"); // =>   <a>  jQuery 
      
      





より良いdomでは、$関数の責任範囲はいくつかの方法でカバーされていますfind[All]



and DOM.create



find[All]



メソッドは、CSSセレクターによって要素を検索するために使用されます。 DOM.create



は、メモリ内に新しいアイテムを作成します。 関数名は、これらの関数の機能を明確に示しています。



角括弧演算子の値


ドル関数の呼び出しが多すぎるという問題のもう1つの理由は、角括弧演算子です。 新しいjQueryオブジェクトが作成されると、関連するすべての要素が数値プロパティに保存されます。 このプロパティの値には、jQueryラッパーではなく、 ネイティブ要素のインスタンスが含まれていることに注意することが重要です。

 var links = $("a"); links[0].on("click", function() { ... }); // ! $(links[0]).on("click", function() { ... }); //   
      
      





この機能により、jQueryまたは別のライブラリ(アンダースコアなど)のすべての関数メソッドでは、現在の要素を反復関数内で$()



ラップする必要があります。 そのため、開発者は、ライブラリがDOMの操作に使用されるという事実にもかかわらず、自分がどのオブジェクト(ネイティブまたはvraper)で動作するかを常に覚えておく必要があります。



より良い方法では、角括弧演算子はライブラリオブジェクトを返すので、ネイティブ要素を忘れることができます 。 それらにアクセスする唯一の合法的な方法は、特別なlegacy



メソッドを使用することです。

 var foo = DOM.find("#foo"); foo.legacy(function(node) { //   Hammer   swipe Hammer(node).on("swipe", function(e) { //   swipe }); });
      
      





しかし、実際には、非常にまれなケースで必要になります。たとえば、ネイティブ関数または別のDOMライブラリとの互換性が必要な場合です(上記の例のHammerなど)。



falseを返す問題


私が本当に驚いたことの1つは、イベントリスナーでreturn false



return false



という奇妙な処理でした。 W3C標準に従って、ほとんどの場合 、この値はデフォルトの動作をオーバーライドする必要があります。 jQueryでは、 return false



イベントの委任がさらに停止します



ここにはいくつかの問題があります。

  1. stopPropagation()



    を単独で呼び出すと、互換性の問題が発生する可能性があります。 彼は他のリスナーがそのようなイベントの発生に関して彼らの仕事をする能力を壊します
  2. ほとんどの開発者(経験豊富な開発者でも)は、この動作を期待していません


jQueryコミュニティが標準に違反することを決めた理由は不明です。 また、better-domはこのエラーを繰り返しません。イベントハンドラー内でreturn false



return false



と、予想どおりpreventDefault()



のみが呼び出されます。



findとfindAll


アイテムの検索は、ブラウザで最も高価な操作の1つです。 querySelector



querySelectorAll



2つのネイティブ関数を使用して実装できます。 2つの違いは、最初の一致後に最初の検索が停止することです。



この機能により、適切な場合に反復回数を大幅に削減できます。 私のテストでは、速度の向上は最大20倍になります。 また、ドキュメントツリーのサイズに応じてギャップが増加することも予想できます。



jQueryには、一般にquerySelectorAll



を使用するfind



メソッドがありfind



。 現在まで、 querySelector



を使用して最初の適切な要素のみを検索するメソッドはありません。



better-domにfind



find



findAll



2つの異なるメソッドがあります。 上記のquerySelector



-optimizationを使用できます。 潜在的な利益を評価するために、最後の商用プロジェクトのソースコードのエントリ数で選択しました。



確かに、 find



メソッドの方がはるかに人気があります。 これは、ほとんどの場合querySelector



-optimizationがquerySelector



ことを意味します。したがって、クライアントでのコードのパフォーマンスを目に見える形で向上させることができます。



おわりに



ライブ拡張機能を使用した開発により、フロントエンドでの作業が本当に楽になります。 UIを多くの小さなパーツに分割すると、より独立した(=信頼できる)ソリューションを作成するのに役立ちます。 しかし、上記からわかるように、better-domは彼らだけのものではありません(ただし、これは本来の主な目標でしたが)。



開発中に、私は1つの重要なことに気付きました。現在の標準が完全に満たされていない場合、または改善方法についてアイデアがある場合は、それらを実装し、機能することを証明してください 。 そして、それはとても楽しいです!



better-domライブラリに関する詳細情報は常にGitHubにあります



All Articles