【jQuery】コードリーディングの練習【addClass()】
コードリーディングを始めるきっかけ
最近Webエンジニアとして働き出したばかりなので、しっちゃかめっちゃかなアウトプットを見かねた職場の上司に 「インプットが少ないね、本を読んだり調べ物したりする他にも、他人が書いたコードを読むのとかおすすめだよ。jQueryとか読みやすいかも」 と、アドバイスをもらい始めてみました。
組み込まれているメソッドがどのような動作をしているのかを、自分がきちんと理解できているのか確認の意味を込めて記事を書くことにしました。
ひとまずとっても読みやすそうな addClass() から
addClass: function( value ) {
var classNames, cur, curValue, className, i, finalValue;
if ( typeof value === "function" ) {
return this.each( function( j ) {
jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
} );
}
classNames = classesToArray( value );
if ( classNames.length ) {
return this.each( function() {
curValue = getClass( this );
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
if ( cur ) {
for ( i = 0; i < classNames.length; i++ ) {
className = classNames[ i ];
if ( cur.indexOf( " " + className + " " ) < 0 ) {
cur += className + " ";
}
}
// Only assign if different to avoid unneeded rendering.
finalValue = stripAndCollapse( cur );
if ( curValue !== finalValue ) {
this.setAttribute( "class", finalValue );
}
}
} );
}
return this;
},
上記はaddClass()単体の全体像ですが、パッと見ただけで把握できるほど実力がないので、細かく細かく見ていきます。
value が関数だったら
最初に、if (typeof value === "function") { ...
の閉じ括弧部分まで見ます
addClass: function( value ) {
var classNames, cur, curValue, className, i, finalValue;
if ( typeof value === "function" ) {
return this.each( function( j ) {
jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
} );
}
まず、大前提として、addClass
はキーであり、値は引数にvalue
を渡された関数になります。
その後、undefinedの値を持ったclassNames
、cur
、curValue
、className
、i
、finalValue
の変数を定義し、関数の引数にわたってきたvalueが関数であれば(うーん、文章にするとややこしい!)、取得したDOM要素(this
※以下this
のみ)をeach
で回した結果を返します。
このeach
では、jQueryオブジェクト
にaddClassメソッド
を再帰的に呼び出している。
また、call( ...
でthis
をvalue
にバインドし、getClass( this )
でクラス名を取得した結果がvalue
に返された上で、その結果がaddClass
の引数に渡されている。
この仕組みは、要素を順に処理する必要がある際に使用されています。
.z-index-1 {
z-index: 1;
}
.z-index-2 {
z-index: 2;
}
.z-index-3 {
z-index: 3;
}
$(document).ready(function() {
$('.element-class').addClass(function(index) {
return 'z-index-' + (index + 1);
});
});
下処理
もう少しで大詰め!
続きを見ていきたいのですがその前に知らない顔ぶれがいらっしゃったので、そちらを先に確認していきます。
最初にclassesToArray()
function classesToArray( value ) {
if ( Array.isArray( value ) ) {
return value;
}
if ( typeof value === "string" ) {
return value.match( rnothtmlwhite ) || [];
}
return [];
}
こちらでは、引数に渡されたvalue
が配列ならそのままreturn
、文字列ならmatchメソッド
で正規表現に対する称号結果を受け取り、配列にして返す、value
がそれ以外なら空配列をreturn
。
次にstripAndCollapse()
function stripAndCollapse( value ) {
var tokens = value.match( rnothtmlwhite ) || [];
return tokens.join( " " );
}
export default stripAndCollapse;
こちらもちょっと似ているが、matchメソッド
の照合結果をスペースで連結して返してる(つまり、返り値はArray
ではなくString
)
では、本題に戻ってまいりました。
classNames = classesToArray( value );
if ( classNames.length ) {
return this.each( function() {
curValue = getClass( this );
cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
classNames
変数にはvalue
を配列にした結果が入り、空配列でなければthis
をeach
で回し、getClass( this )
でクラス名を取得します。
その後、this
のnodeType
が1であれば(div
やp
などのElement Node
)、スペースで連結された文字列全体の前後にさらにスペースを入れてcur
変数に代入します。
クラスを追加
あとは結構スッキリ見やすく簡単に書かれていたのでこちらも巻いていきます!
if ( cur ) {
for ( i = 0; i < classNames.length; i++ ) {
className = classNames[ i ];
if ( cur.indexOf( " " + className + " " ) < 0 ) {
cur += className + " ";
}
}
// Only assign if different to avoid unneeded rendering.
finalValue = stripAndCollapse( cur );
if ( curValue !== finalValue ) {
this.setAttribute( "class", finalValue );
}
}
} );
}
return this;
}
まず、cur
(this
のクラス類が合体した文字列(単一の場合もある)) が存在すれば(厳密に言うと、cur
が正しく処理されていれば)、classNames
をfor
で回す。
次に、cur
の中身を調査します!
この調査はaddClass
の引数に渡した要素(文字列だとか)がcur
にあるかないかの調査。
要約すると「追加したいクラスがthis
のクラス類と被ってないかチェック」です。
indexOf
で検索した結果が0未満(-1)、であれば被ってないので、既存のクラス類が入った変数にクラスを追加します。
その後、既存クラス類と追加したいクラス類が入っているcur
を再度stripAndCollapse
で整形(正規表現で照合→スペースで連結)して、finalValue
に代入。
そして、最終チェックです!!
this
のクラスと、合体したcur
が入ったfinalValue
が違う値であれば、無事にthis
のクラス属性にfinalValue
がセットされます。
結論
必要ないほどに詳しく調べてしまった気もしますが、効率的なやり方は徐々に身につくものだと考えて、これからも色々見ていこうと思いました。(本当は効率的なやり方気になる〜!)
また、初めて記事なるものを書いたので、難しかったです。とほほ。
参考
Discussion