カプセル化
カプセル化とは、プログラムのデータ、振る舞い、機能を組み合わせ、全体にはめ込み、直接使えるインターフェースを提供することです。内部でどのように構成され、設計されているのかがよくわからなくても、簡単に先に進んで使うことができます。
カプセル化されたものは通常、他の人が簡単にそれを使えるように、インターフェースを与えなければなりません。
カプセル化の目的は、プログラミングを単純化することと、プログラムのセキュリティを強化することです。
例をあげる
- 自宅でライトをオンにしたい、内部回路がどのように設計されているかを知る必要はありません、どのように電気が動作するか、電球がどのように設計されているか、どのようにそれをインストールするには、単に知っている必要があります:スイッチを押すと、ライトが点灯しています。ここでマスターの電気技師や装飾会社は、回路設計を支援するために、電球がパッケージに相当し、スイッチが与えられたインタフェースに相当するインストールします。
- ノート パソコンの CPU の内部, グラフィック カード, マザーボード, メモリ, ハード ドライブと他のハードウェア, ノート パソコンの使用, CPU を知る必要はありません, グラフィック カードをロードする方法です, マザーボードの回路を設計する方法です, ちょうど知っている必要があります: マウスとキーボードを介してコンピューターを操作することができます。ここでは、コンピューターのハードウェアのメーカーが一緒に組み立て, パッケージに相当する, とマウスとキーボードの USB インターフェイスを介して行のコンピューターを操作するには、インターフェイスに似ています。
DOM
ネイティブの DOM 操作構文は通常長く、覚えにくく、使い勝手が悪いので、DOM 操作をカプセル化することができます。
いくつかの用語
- ライブラリ:プログラミングは、他の人が使用することをお勧め関数のいくつかの良い使用することができ、この関数は、より多くのアップを使用するために他の人に提供することができる場合は、ツールの倉庫を形成することができます。一般的なJSライブラリは次のとおりです:jQuery、Underscoreなど;
- フレームワーク:ライブラリがどんどん大きくなり、機能がどんどん豊かになり、その使い方を知るために多くのメソッドを学ぶ必要がある場合、この時点でのライブラリは、VueやReactのようなフレームワークと呼ぶことができます;
注:上記の概念については、必ずしも1つずつ対応するために固定用語を使用する必要はありません、異なる人々が呼び出すの異なる方法を持っている可能性がありますので、意味を理解することができ、それが呼び出される方法でもつれする必要はありません。
オブジェクト・スタイルによるDOMのカプセル化
オブジェクト・スタイルは、名前空間スタイルとも呼ばれます。
なぜオブジェクト・スタイルなのでしょうか?なぜなら、ここでのカプセル化は常に提供されたグローバルオブジェクト:window.domを中心に展開されるからです;
使用する際には、domにdotをつけて、その方法を示す必要があります;
そのため、独自のDOMライブラリをラップする場合は、まずdomをグローバルオブジェクトとして宣言します;
グローバルオブジェクトの宣言
// オブジェクトをグローバルに宣言する
// オブジェクトをウィンドウにマウントする
dom = window.dom
書き方
// 1.
window.dom.create = function(){...}
window.dom.remove = function(){...}
// 2.
dom.create = function(){...}
dom.remove = function(){...}
// 3.
window.dom = {
	create:function(){...},
 remove:function(){...}
}
// 4.
window.dom = {
	create(){...},
 remove(){...}
}
書き込みの上記のいくつかの方法は、効果を達成することができ、誰もがまた、独自の習慣を持っている、私は個人的に書き込みの第四の方法を好むが、この記事を使用する利便性を把握するために:
function fn(){
	...
}
これは書くための形ですが、実際にテストしたり使ったりするときは、変換してwindow.domに書けばいいのです。
インクリメント
ノードの作成
function create(string){
 return document.createElement(string);
}
この方法で要素を作成するのは比較的簡単で、div 要素を作成するには dom.create('div') を直接使用します;
create
上記の方法は機能を実装していますが、以下のような欠点があります:
ラベルの内容を設定したい場合、まずdivタグを作成し、そのdivタグのinnerTextを変更しないと実現できないのですが、ワンステップでできる方法はないでしょうか?
例えば、こう書いてください:
dom.create('<div>hi</div>')
まずdivをコンテナとして使用し、文字列パラメータをコンテナdivのinnerHTMLに代入します。
function create(string){
 const container = document.createElement('div');
 container.innerHTML = string;
 return container;
}
上記のコンテナはdivタグですが、divタグの中にtdタグを入れるのはHTMLの文法に反するなど、すべてのタグをdivの中に入れられるわけではありません;
では、任意のラベルを保持するタブはありますか?
それが**テンプレート**です;
template英語訳はテンプレートで、HTMLではテンプレートタグの内容は、任意のラベルとコンテンツを置くことができます。しかし、ページのパース時に、パーサーはテンプレートタグの内容を処理しますが、テンプレートタグの内容はページにレンダリングされません。
そのため、ここではテンプレート・タグを適切な場合に使用します。
また、jsのテンプレートのプロトタイプ・チェインの最初のレベルには、content属性があり、これは読み取り専用のドキュメント・フラグメント、つまり、テンプレート・タグで表されるDOMツリーを含むタグ内のコンテンツのフラグメントです。
ここでは、コンテナをテンプレート・タグに変更します:
function create(string){
 const container = document.createElement('template');
 container.innerHTML = string;
 return container.content.firstChild;
 // テンプレートはページ上にレンダリングされないが、それでも入力は必要だ。
 // つまり、コンテンツ.firstChild返された結果からテンプレートを削除する
}
コードを書くとき、余分なスペースに注意を払わなかったり、余分なリターンを押してしまったりすることがあります。
trim() メソッドを使用すると、文字列の両端からスペースを取り除くことができます;
function create(string) {
 	const container = document.createElement('template');
 	container.innerHTML = string.trim(); 
 	return container.content.firstChild;
}
// 使用例:
dom.create('<div> </div>')
新しい兄弟を追加
自分自身の前にノードを追加することは、新しい兄弟を追加することと同じです;
// parentNode.insertBefore(newNode,referenceNode)
// なぜ親ノードでinsertBeforeを呼び出すのか?兄弟や姉妹を追加するのはあなたではなく、親次第だからだ。
// newNodeは挿入されるノード、referenceNode は参照ノードで、new を呼び出した後、ノードは参照ノードの前に置かれる。
// html:
<div id="div1">
 <div id="div2">2</div>
 </div>
// js:
let div3 = document.createElement('div');
div3.innerText = '3';
div2.parentNode.insertBefore(div3,div2)
// ページ効果:
// html:
<div id="div1">
	<div>3</div>
 <div id="div2">2</div>
 </div>
 
// DOM 
function before(node,node2){
 	node.parentNode.insertBefore(node2,node)
}
// nodeはノード、node2は新しく生成されたノードである。
// 使用例:
dom.before(div1,div2)
新しい兄弟を追加
新しい兄弟を加えるのはinsertBeforeで、新しい兄弟を加えるのはinsertAfterですか?
しかし、insertAfter構文はないので、そうではありません:
// これにより、node2の前にnode2が挿入される。
parentNode.insertBefore(node2,node)
// では、ノードの行の次のノードの前にnode2を挿入することは、ノードの後にnode2を挿入することと同じではないのか?
parentNode.insertBefore(node2,node.nextSibling)
ちょっと回りくどい言い方なので、図を書いて示しましょう:
// つまり、DOMは次のようにラップする:
function after(node,node2){
 	node.parentNode.insertBefore(node2,node.nextSibling)
}
// 使用例:
dom.before(div1,div2)
新しい息子の追加
// ネイティブ DOM に新しい子ノードを追加する:
// parentNode.appendChild(node)
// ノードは事前に作成されていなければならない。
// DOM 
function append(parentNode,node){
 	parentNode.appendChild(node)
}
// 使用例:
let div1 = dom.create('<div>1</div>')
let div2 = dom.create('<div>2</div>')
dom.append(div1,div2)
お父さんを追加
// アイデア: まず自身の前にノードを挿入し、次にこのノードを appendChild() する。
// ここでの2つのノードはまだ事前に作成する必要がある。
function wrap(parent,node){
 	dom.before(node,parent)
 dom.append(parent,node)
}
// 使用例:
let div1 = dom.create('<div>1</div>')
let div2 = dom.create('<div>2</div>')
dom.wrap(div1,div2)
削除
ノードの削除
// ネイティブDOMでノードを削除するには、2つの方法がある:
// 1.親ノードが子ノードを削除する(プロトタイプ・チェイン・ノードを呼び出す.prototype (removeChild in)
parentNode.removeChild(childNode)
// 2.子ノードの自己削除(プロトタイプ・チェインを呼び出す 要素.prototype (で削除する)
node.remove()
// 実は、2つ目のメソッドも十分シンプルなのだが、1つ目のメソッドを使えば、まだカプセル化することができる:
function remove(node){
 	node.parentNode.removeChild(node)
}
// ノードがある場所から削除されても、後で再び使われたり、別の場所で参照されたりすることがある:
function remove(node){
 	node.parentNode.removeChild(node) 
 return node;
}
// 使用例:
dom.remove(div1)
子孫ノードの削除
// 最も単純な方法: すべての子孫ノードを直接削除する
function empty(node){
 	node.innerHTML = ''
}
// 上記の場合でも、ノードがある位置で削除されても、後で参照されたり、別の場所で使用されたりすることがある:
// アイデア:すべての子ノードの先頭を毎回削除し、それを配列に入れ、最後にこの配列に戻す。
function empty(node){
	const {childNodes} = node
 // 新しい構文で、const childNodesと等価である。= node.childNodes,iのようなもの+= 1  
 const array = []
 for(let i = 0;i < childNodes.length;i++){
 	dom.remove(childNodes[i])
 array.push(childNodes[i])
 }
 return array
}
// 子ノードの長さはリアルタイムで変化するので、結果は間違っているはずだ!
//  
function empty(node){
	const {childNodes} = node
 const array = []
 let x = node.firstChild
 while(x){
 	array.push(dom.remove(node.firstChild)) // 先ほどremoveと書いたが、returnはここと同じ。
 x = node.firstChild
 }
 return array
}
// 使用例:
dom.empty(div1)
変更
属性の読み取りと書き込み
 // ネイティブDOMは、setAttributeでタグの属性を設定し、getAttributeで属性を読み取ることができる。
 // ここでのattributeはHTMLドキュメント内のタグの属性を表し、nameは属性の名前、valueは属性の値を表す。
 // 復習:jsのプロパティはプロパティで表される
 
 // 属性を読む:
 function getAttr(node,name){
 	return node.getAttribute(name)
 }
 
 // 属性を変更する:
 function setAttr(node,name,value){
 	node.setAttribute(name,value)
 }
オーバーロード
上記のread属性とwrite属性は十分簡潔ではありません。
function attr(node,name,value){
	if(arguments.length === 2){
 	return node.getAttribute(name)
 }else if(arguments.length === 3){
 	node.setAttribute(name,value)
 }
}
// 引数を2つ入力すると、属性
// 3つのパラメータを入力する場合、属性を変更したい
// 使用例:
dom.attr(div1,'title') //div1 の title 属性を取得する
dom.attr(div1,'title',''new title') // div1 の title 属性を 'new title' に変更する。
このように、引数の数によって異なるコードを記述する方法をオーバーロードと呼びます;
もっと詳しく言うと、オーバーロードとは、同じ関数名を使っていても、関数のパラメータの数や型が異なっている関数のことで、関数のパラメータに応じて呼び出されることで、異なる関数を区別することができます。
テキストコンテンツの読み書き
// テキストの内容は、ネイティブDOMでこのように変更できる:
// node.innerText = 'xxx' またはノード.textContent = 'xxx'
// もちろん、値を代入しなければ、ただテキストを読んでいるだけだ。
// つまり、このようにラップすることができる:
function text(node,string){
	if(arguments === 2){
 	node.innerText = string
 }else if(arguments === 1){
 	return node.innerText
 }
}
適応の問題
innerTextとtextContentが全く同じ働きをするなら、なぜ同じようなAPIを2つ設計するのでしょうか?それには歴史的な理由があります:
MicrosoftのIEがブラウザ市場を支配していた初期の頃、他のブラウザがtextContentをサポートしていたのに対し、IEはラベル内のテキストコンテンツを変更するinnerTextのみをサポートしていました。私たちが知っているように、初期のフロントエンド開発者は、IEの互換性が非常に困難であることによって苦しめられた、同様の理由です。だからここで適応の問題を考えるために:
function text(node,string){
	if(arguments === 2){
 	if('innerText' in node){
 	node.innerText = string
 }else{
 	node.textContent = string
 }
 }else if(arguments === 1){
 	if('innerText' in node){
 	return node.innerText
 }else{
 	return node.textContent
 }
 }
}
// 使用例:
dom.text(div1) //div1 のテキストの内容を取得する
dom.text(div1,'Hello') // div1のテキストコンテンツを書き換える
しかし現在では、ブラウザは基本的に両方のAPIをサポートしているので、好きな方を使うことができますし、さらに重要なのは、異なるブラウザに適応するという考え方に慣れておくことです;
HTMLコンテンツの読み書き
// ネイティブDOMはhtmlコンテンツを変更する:
node.innerHTML = 'xxx' 
// 値を割り当てずにhtmlコンテンツを読み込む
//  
function html(node,string){
	if(arguments.length === 2){
 	node.innerHTML = string
 }else if(arguments.length === 1){
 	return node.innerHTML
 }
}
// 使用例:
dom.html(div1) //div1 タグ内の HTML コンテンツを読み取る
dom.html(div1,'<span> </span>') //div1タグ内のHTMLコンテンツを書き換える
スタイルの変更
// ネイティブDOMの読み込み、スタイルの変更
node.style.name
node.style.name = 'value'
//  
function style(node,name,value){
	if(arguments.length === 3){
 	node.style[name] = value
 }else if(arugments.length === 2){
 	return node.style[name]
 }
}
// 使用例:
dom.style(div1,'color') //div1タグのスタイルでcolorの値を取得する
dom.style(div1,'color','red') //div1タグのスタイルでcolorの値を変更する
// このように書くのが好きな人もいる: dom.style(div1,{color:'red'}),最適化を継続する
function style(node,name,value){
	if(arguments.length === 3){
 	node.style[name] = value
 }else if(arugments.length === 2){
 	if(typeof name === 'string'){
 	//第2引数のデータ型が文字列の場合、style属性の値を返す
 renturn node.style[name]
 }else if(typeof name === 'object'){
 	//入力された第2パラメータのデータ型がオブジェクトの場合、以下のコードが実行される。
 	const object = name //nameの値をオブジェクト変数に代入する
 for(let key in object){ //属性の値を表すオブジェクト変数にキーを入れる
 	node.style[key] = object[key]
 }
 }
 }
}
// 使用例:
dom.style(div1,{color:'red'})
// ここで、name、object、keyが少し回りくどいので、図を用いて説明するとわかりやすいだろう:
instanceof
typeofで入力変数のデータ型を、instanceofで判定対象がオブジェクトであることを判定できます;
instanceofはinstanceと訳され、オブジェクトが誰のインスタンスであるかを判断したり、コンストラクタのprototype属性がインスタンスオブジェクトのprototypeチェーンに現れるかどうかを検出するために使用されるオブジェクト演算子です。そのため、オブジェクトのデータ型を検出するためにも使用できます。
使用法: obj1 instanceof obj2
obj1 が obj2 のインスタンスであれば真を、そうでなければ偽を返します;
instanceofとtypeofの違い:
- フォームの違い:と
- typeofは変数のすべてのデータ型を判定でき、戻り値は文字列、戻り値は数値、文字列、記号、bool、null、undefined、関数、オブジェクト、instanceofはオブジェクトデータ型にのみ適用されます;
- typeof が 'object' 文字列を返すのは、リッチオブジェクトのインスタンスを判定するときだけです。instanceof を使ってオブジェクトを判定する場合は、true か false のどちらかを返します;
- instanceofは、オブジェクトのプロトタイプ・チェインを見下ろすことで、異なるオブジェクトのインスタンスを判断することができますが、typeofはそれができません;
ですから、このコードも instanceof を使って書き換えることができます:
function style(node,name,value){
	if(arguments.length === 3){
 	node.style[name] = value
 }else if(arguments.length === 2){
 	if(typeof name === 'string'){
 return node.style[name]
 }else if(name instanceof Object){
 	// 比較対象: typeof name=== 'object'
 	const object = name 
 for(let key in object){
 	node.style[key] = object[key]
 }
 }
 }
}
クラスの追加と削除
// クラスのネイティブDOM操作:
node.classList.add(className) //クラスを追加する
node.classList.remove(className) //クラスを削除する
node.classList.contains(className)//ノードが特定のクラスを持っているかどうかを検出する
//  
windom.dom = {
	class:{
 	add(node,className){
 	node.classList.add(className)
 },
 remove(node,className){
 	node.classList.remove(className)
 },
 has(node,className){
 	return node.classList.contains(className)
 }
 }
}
// 使用例:
dom.class.add(div1,'red')
dom.class.remove(div1,'red')
dom.class.has(div1,'red')
イベントリスナーの追加と削除
// ネイティブDOMイベントリスナー
// を自身の属性でクリックする:
node.onclick = function(){...} //マウスクリックイベントを追加する
node.onclick = null //マウスクリックイベントを削除する
// プロトタイプチェーンのイベントターゲット.prototypeメソッドの
node.addEventListener(click,function(){...}) //マウスクリックイベントを追加する
node.removeEventListener(click,function(){...}) //マウスクリックイベントを削除する
//  
function on(node,eventName,fn){
	node.addEventListener(eventName,fn)
}
function off(node,eventName,fn){
	node.removeEventListener(eventName,fn)
}
// 使用例:
dom.on(div1,'click',function(){console.log('hi')})
function sayHi(){console.log('hi')}
dom.on(div1,'click',sayHi)
dom.off(div1,'click',sayHi)
// offの場合、事前に関数名を設定しておく必要がある。そうしないと、無名関数の使用は無効になる:
// バグのデモ
dom.on(div1,'click',function(){console.log('hi')})
dom.off(div1,'click',function(){console.log('hi')})
// これは書き捨ての方法ではないが、クリックしても元々追加した機能は実行される。
チェック
ラベルを取得
// ネイティブ DOM で要素を見つける方法:
doucment.getElementById('id')
document.getElementsByClassName('div')[0]
document.getElementsByClassName('red')[0]
document.querySelector('#id')
document.querySelectorAll('.red')[0]
//  
function find(selector){
	return document.querySelectorAll(selector)
}
// 使用例:
dom.find('#test')[0] //これは擬似配列を返すので、添え字を
//  
// 次のコードで、divタグからpタグを取り出したいとする。
<div id='test'>
	<p class='red'> </p>
</div>
//  
function find(selector,scope){ //scope 範囲の意味
	return (scope || document).querySelectorAll(selector)
}
// 使用例:
let test = dom.find('#test')[0]
dom.find('.red',test)
親要素と子要素の取得
これは比較的簡単で、コードに直行します:
// 親要素を取得する
function parent(node){
	return node.parentNode
}
// 子要素を取得する
function children(node){
	return node.children
}
// 使用例:
dom.parent(div1)
dom.children(div1)
兄弟要素の取得
// ネイティブDOMはノード.parentNode.children 兄弟要素を取得することができるので、次のようにカプセル化できる:
function siblings(node){
	return node.parentNode.children
}
// これはすべての兄弟要素を取得するが、自分自身も含まれるため、自分自身を除外する必要がある。
// ここでのアイデアは、フィルタリングメソッドの配列:filterを使用することだが、返されるのは擬似配列なので、まず結果を配列に戻してからフィルタリングする必要がある
function siblings(node){
	return Array.from(node.parentNode.children).filter((n)=> n !== node)
 // 配列を使用する.from 配列にする
 // filter((n)=> n !== node) これは、nがnodeと等しくない場合は配列に留まり、nodeと等しい場合はフィルタリングされることを意味する。
}
// 使用例:
dom.siblings(div1)
兄と弟の要素
// ネイティブDOMは次のノードを取得する: node.nextSibling
// ネイティブDOMは前のノードを取得する: node.previousSibling
// これは要素ではなくノードであることに注意。フェッチされるものはテキスト・ノードである可能性があり、欲しいものはテキスト・ノードではないので、ラップするときは除外すること:
// 弟要素を取得する
function next(node){
	let x = node.nextSibling
 while(x && x.nodeType === 3){
 	// xが存在し、ノードタイプがテキストノードである3の場合、テキストノードでなくなるまで次のノードを探し続ける
 x = x.nextSibling
 }
 return x
}
// 兄要素を取得する
function pervious(node){
	let x = node.previousSibling
 while(x && x.nodeType === 3){
 	x = x.previousSibling
 }
 return x
}
nodeTypeの詳細はこちら:
すべてのノードを反復処理
function each(nodeList,fn){
	for(let i = 0;i<nodeList.length;i++){
 	fn.call(null,nodeList[i]) // thisnullに設定する
 }
}
// 使用例:
// html:
<div id="div1">
 <div id="div2">2</div>
 <div id="div3">3</div>
</div>
// js:
let divList = document.querySelector('#div1').children
dom.each(divList,(n) => n.style.color = 'red')
要素のランキングを取得
// アイデア: 与えられた要素のすべての兄弟要素を見つけ、ループを繰り返し、ループが自分の要素に達したときに数値を返す
function index(node){
	const list = dom.children(node.parentNode)
 let i //ループの外側でiを宣言する理由は、letで宣言した場合、スコープがループの内側に限定され、ループの外側に戻ることができないからである。
 for(i = 0;i<list.length;i++){
 	if(list[i] === node){
 	break;
 }
 }
 return i
}





