タイトルの通りなんですが、どう記述すればいいでしょうか。
どこかで見たことあるんで、出来るのは知ってるんですが、わかりません。
お願いします。
基本的には、そういう関数を作成して呼び出す形式になります。
取得する場合:
http://white.sakura.ne.jp/~piro/xul/tips/x0013.html
設定する場合:
function setInnerText ( element, text ) {
var c;
while ( c = element.lastChild ) element.removeChild( c );
element.appendChild( element.ownerDocument.createTextNode(text) );
}
のような感じです。
テキストノードが一つだけが含まれている場合は、
「firstChild.nodeValue」で読み書きできる。
<p onclick="alert(this.firstChild.nodeValue)">test</p>
タグが含まれいてテキストノードのみを取得する場合は、
「innerHTML」で取得した内容のタグ部分を正規表現で置換するという方法もある。
<p onclick="alert( this.innerHTML.replace(/<[^>]*>/g,'') )"><em>t</em>est</p>
> タグが含まれいてテキストノードのみを取得する場合は、
> 「innerHTML」で取得した内容のタグ部分を正規表現で置換するという方法もある。
確認したわけではないが、set、get を問わず、これが一番速いかもしれない (非笑)。
IE の振る舞いを真似るには少し工夫がいるが。
「取得する場合」の参照先にあるコードは、「突っ込んでください」とでも言っているように感じるのは私だけじゃあないよね、ここの管理人のおとうさん。放っておくのももったいないほど良質なミスをしているから、勇気を振り絞って (嘘) 突っ込むことにする。
普通は、未知の文字列を「+」で結合しない。
一概には言えないが、配列に格納した細切れの文字列を join('') したほうが良い。「+」結合方式の方が速い場合はいくらでもあるが、しかし、条件次第ではアプリケーションをフリーズさせるほど遅くなるので、特に未知の文字列取得には使うべきではない。因みにうちの経理担当者さえ、この程度のルールは守ってヘッポコ VBA を書いているようだ。
喩えで誤魔化す。長方形のタイルを長い直線上に並べるとする。開始地点にタイルを置いて 1 枚ずつ運んでは継ぎ足していく場合、タイルの並びが長くなればなるほど往復に時間を取られるようになる。だから人は荷車とかを使って、一度に全てのタイルを運びながら並べていく。
試しにアドレス欄に以下の 2 つを入力して結果を比較してみると分かるかも (特に IE)。
javascript:var start=(new Date).getTime(),finish,b='bbbbbbbbbb',i=1e4,s='';while(i--)s+=b;finish=(new Date).getTime();alert(finish-start);
javascript:var start=(new Date).getTime(),finish,b='bbbbbbbbbb',i=1e4,s,a=[];while(i--)a[a.length]=b;s=a.join('');finish=(new Date).getTime();alert(finish-start);
次に。ループには少なくとも1つの条件は必要だが、それを増やそうとする馬鹿はいない、という事実。
極めて単純に、Node.ELEMENT_NODE が Node の半分あるとする。この時、この半数あるものについては 3 つもの条件処理が課せられている。信じられない (非笑)。効率悪すぎるだろう、これでは。
時々、
myFunction(o){..}
o.myMethod(){..}
が同じだと書いてある間違った解説に出くわすことがあるが、少なくとも JavaScript と ECMAScript においては違う。JScript でさえ、大概は同じなのかもしれないが、ネスケとの互換が十分に考慮されていた時代の古いオブジェクトに関しては違う。特にオブジェクト指向スクリプト言語では、後者の書き方を採用することでスクリプト側での遅い条件分岐を回避して効率を上げることが出来る。これに関しては下記のスクリプトを参考にしてほしい。
ところで。
しょうもないことだけど、innerText のような読み書き可能なプロパティは setter と getter で実装すれば良いのでは? 結果的に記述が統一されるだけでも十分に有用であると思うのは私だけ?
最後に小汚くて中途半端なものを。body 終了タグ直前で。
(ただし、HTMLElement.prototype.innerText setter に関しては、些細な高速化の為に >>1 の素晴らしいスクリプトにある Element.prototype.ownerDoocument を変更している。この変更は誉められたものではない。)
if(
typeof window.Node!='undefined'&&
window.Comment&&
window.Text&&
window.HTMLElement&&
!HTMLElement.prototype.innerText
){
Node.prototype.loadInnerText=new Function(
"const "+
"nodeList=this.childNodes,"+
"count=nodeList.length;"+
"for(var i=0; i<count; i++){"+
"try{"+
"nodeList[i].loadInnerText();"+
"}"+
"catch(typeError){"+
"nodeList[i].constructor.prototype.loadInnerText=Node.prototype.loadInnerText;"+
"i--;"+
"}"+
"}"
);
new Function(
"const "+
"empties=new Array,"+
"html='HTML',"+
"element_prototype_loadInnerText='Element.prototype.loadInnerText=';"+
"if(!arguments.constructor.prototype.pop)"+
"arguments.constructor.prototype.pop=Array.prototype.pop;"+
"while(arguments.length)"+
"empties.push(html, arguments.pop(), element_prototype_loadInnerText);"+
"empties.push('function(){}');"+
"try{"+
"eval(empties.join(''));"+
"}"+
"catch(referenceError){}"
)('Area', 'Base', 'BaseFont', 'BR', 'TableCol', 'Frame', 'HR', 'Image', 'Input', 'IsIndex', 'Link', 'Meta', 'Param', 'Script', 'Style');
Comment.prototype.loadInnerText=new Function();
Text.prototype.loadInnerText=new Function(
"HTMLElement.SPLIT.push(this.nodeValue);"
);
eval(
"HTMLElement.prototype.innerText setter=function($innerText){"+
"while(this.hasChildNodes())"+
"this.removeChild(this.firstChild);"+
"this.appendChild(document.createTextNode($innerText));"+
"};"+
"HTMLElement.prototype.innerText getter=function(){"+
"HTMLElement.SPLIT.length=0;"+
"this.loadInnerText();"+
"return HTMLElement.SPLIT.join('');"+
"};"+
"HTMLScriptElement.prototype.innerText setter=function(){"+
"throw new Error(HTMLScriptElement.MESSAGE);"+
"};"+
"HTMLScriptElement.prototype.innerText getter=function(){"+
"return HTMLScriptElement.EMPTY;"+
"};"
);
HTMLElement.SPLIT=new Array;
HTMLScriptElement.MESSAGE='\u672A\u77E5\u306E\u5B9F\u884C\u6642\u30A8\u30E9\u30FC\u3067\u3059\u3002';
HTMLScriptElement.EMPTY='';
}
/*
何をムキになっているんだ、俺は >>3。よく読んだら将来有望な子供じゃないか。反省する。
*/
分かりにくかったかもしれないから、ヘッポコ解説。
階層構造。
Node
Element
HTMLElement
HTMLAnchorElement
..
..
CharacterData
Comment
Text
..
..
innerText を得ようと HTML を辿り、あるノードに着く。ノードというが、それは通常、HTML**Element、Text、Comment の何れかであると断言しても大間違いではない。そこでどのような処理をするかはここで挙げた 3 種類のオブジェクトの型 (他に言葉を思いつかない) で決定される。
Comment
ここでは何かをする必要は全く無い。
Text
仮に配列に innerText (の断片) を格納していくのであれば、これの nodeValue を格納する。
HTML**Element
空要素であれば、何かをする必要は全く無い。
HTMLScriptElement と HTMLStyleElement でも、何かをする必要は全く無い。
そうではない場合は次に辿るべきノードへと移動する為の処理が遂行されなければならない。
従って、あるノード (仮に node という変数名とする) での処理は次のように分岐される。
if(node instanceof Text)..
else if(
node instanceof Comment||
node instanceof HTMLMetaElement||
.. 空要素、script、style
)..
else ..
instanceof はオブジェクトの型だけを判別するから、比較的高速 (最速?) に処理される。しかし、else if(..).. の条件で詰まる。空要素と HTMLScriptElement と HTMLStyleElement と Comment を合わせると 16 回の条件分岐になる。つまり、最後の else .. に辿り着くまでに 17 回もの instanceof を調べなければならない。高速に処理されるとはいえ、これでは寧ろ遅くなるだろう (試したわけではない)。
そこで、発想自体は同じことになるのだが、同じ名前のメソッドを作って、オブジェクトの型によってその処理を変える、という手法を採る。実例で言えば、Array.prototype.slice と String.prototype.slice のように。
階層構造より、「おおまか」から「細部」へと上書きする。
Node.prototype.loadInnerText=
function(){
次のノードへ移る処理。
ループ内部でオブジェクトの型を検証する必要は無い。
必要あるオブジェクトに関しては以下でこの名前のメソッドをうわがきするから。}
HTML**Element.prototype.loadInnerText=
..
Comment.prototype.loadInnerText=
function(){
何もしない。}
Text.prototype.loadInnerText=
function(){
nodeValue を取り出す処理。}
例えば HTMLDivElement は上書きされていないから、当然、階層構造の上部にある Node に実装した「次のノードへ移る処理」である loadInnerText を継承している。
このように事前に DOM が用意してくれている、言わば「条件分岐」を利用して、実際のループ処理のスコープに入る前に「条件分岐」を済ませておく。
余談。CSS をどう書くかを思い出して、比較してみる。一々「個々のオブジェクト (style 属性)」をどうのこうのするのではなく、「もとからあるオブジェクトの型 (セレクタ)」を「おおまか」から「細部」に分類して、上書きしていく。。。やっていることは似ているし、概要としては差異無し。もし CSS をそつなく書けるのであれば、JavaScript で DOM をどうのこうのする時にも同様に放逸な (lazy) 方法を採ろうとは思わないだろうか。特に今回のようにそのやり方がはまる場合には。
結局、この用意が出来たら、
HTMLElement.prototype.innerText getter=function(){
..
this.loadInnerText();
..
}
という風に使うことで innerText 取得は実装される。
innerText 書き込みに関しては、>>1 とか >>2 とかのようなやり方以外には思いつかない。
ここで、HTMLScriptElement と HTMLStyleElement の innerText を書き込もうとした時にエラーを起こす IE を猿真似する。ここでもこのような違いを持たせたい時、「おおまか」から「細部」に向かって「上書き」する。
HTMLScriptElement.prototype.innerText setter=
HTMLStyleElement.prototype.innerText setter=
function(){throw new Error(..);}
とでも上書きすればよかろうと思う。
探索が苦手なので、助かりました。(ていうか自分では作れない)
setter, getterていうのもはじめて知りました。
ありがとうございました。完了さしていただきます。
完了後になんですが。
MozillaならTreeWalkerを使う手もありますね。忘れてました。
function getInnerText ( element ) {
var text = new Array, textNode;
var walker = element.ownerDocument.createTreeWalker( element, NodeFilter.SHOW_TEXT, null, true );
while ( textNode = walker.nextNode() ) text.push( textNode.nodeValue );
return text.join('');
}
ご参考まで。
「動くものは使う」という態度を肯定するならば、そうするのも悪くないのかもしれない。
当然 >>6 が最速だが、hasFeature('Traversal', 2.0) が false のものに対しては使うのを躊躇うし、思わず import org.apache.xerces... とかなんとか誤記してしまいそうだな。
頭を使うとしたら、SHOW_ALL 時の filter 記述だけなので、探索をつくるのに思考力がついていかない場合にも自力で作ることが出来る。古い Mozilla が淘汰された時 (ただの独自判断) からの実践投入ということになるのだろう。