スパムが多いので、コメント欄を一時的に消してみました。CSSで消しているだけなので、ロボットだったらかいくぐってくることでしょう。
追記:やはり入ってきたので、コメント欄のHTMLを消してみました。ご不便をおかけします。
〜ご不便をおかけいたします〜
スパムが多いので、コメント欄を一時的に消してみました。CSSで消しているだけなので、ロボットだったらかいくぐってくることでしょう。
追記:やはり入ってきたので、コメント欄のHTMLを消してみました。ご不便をおかけします。
JavaScriptのライブラリーで「CSSも一緒にアップロードしてね」みたいなものが多いですが、jsファイルとcssファイルが別だとサイト管理上結構煩雑になったりします。
ということで、今回はJavaScript側でCSSを書いてしまおうという試みです。
JavaScriptでCSSを書くといっても、style属性を全要素にいちいち書いていたのでは日が暮れてしまうので、stylesheetオブジェクトを作って、そちらにアクセスするようなものを考えてみました。
言い換えれば「GM_addStyleを普通のJavaScriptで使いたい」みたいな企画です。
普段は意識しなくていいと思いますが、たまにハマることがある、newで作るStringオブジェクトとリテラル表記で作る文字列の違いについてです。
var str0 = new String('foo'); // Stringオブジェクトを作成
var str1 = 'foo'; // 文字列リテラルで作成
Stringオブジェクトのstr0は参照型のオブジェクト、文字列リテラルで作ったstr1はプリミティブ型です。
var str0 = new String('foo');
alert(typeof str0); // object
var str1 = 'foo';
alert(typeof str1); // string
まずはタイプが違いますね。
var str0 = new String('foo');
alert(str0 instanceof String); // true
var str1 = 'foo';
alert(str1 instanceof String); // false
リテラルで作ったときはStringのインスタンスではありません。
以上のことから、変数strが文字列かどうかを調べるとき、
(typeof str == 'string' || str instanceof String)
とする必要があります。
var str0 = 'foo'; var str1 = 'foo'; alert(str0 == str1); // true alert(str0 === str1); // true:厳密比較演算子を使ってもいい
予想通りの結果です。
var str0 = new String('foo');
var str1 = new String('foo');
alert(str0 == str1); // false
alert(str0 === str1); // false
ここが要注意です。参照型なので、値の比較は行われず参照の比較となるためfalseになります。
toStringメソッドを使います。
var str0 = new String('foo').toString();
var str1 = new String('foo').toString();
alert(str0 == str1); // true
alert(str0 === str1); // true
ちなみにtoString()メソッドで生成される文字列はStringオブジェクトではありません。
var str = new String('foo').toString();
alert(str instanceof String); // false
alert(typeof str); // string
var str0 = new String('foo');
var str1 = 'foo';
alert(str0 == str1); // true
alert(str0 === str1); // false
自動型変換が行われて値を比較します。厳密比較ではfalseになります。
前記の通りリテラルはStringオブジェクトではありませんが、Stringオブジェクトのメソッドを使うことができます。自動的にStringオブジェクトに型変換されるためだとか某書に書いてあったような気がします。
var res = 'foo'.match(/oo/);
var str0 = new String('foo');
alert(str0.constructor); // function String() { [native code] }
var str1 = 'foo';
alert(str1.constructor); // function String() { [native code] }
どちらのコンストラクタもStringです。str1はStringオブジェクトのインスタンスでないにもかかわらずですが、これも自動型変換されるからでしょうか?
max-widthを使ったちょっとしたサンプルを作る機会があって、IE6でに対応しようと思って検索してみたらIE独自拡張のexpressionを使っているものが多かったです。概要はこんな感じでしょうか。
*html #container {
width : expression( document.body.clientWidth > 1000 ? '1000px' : 'auto' );
}
ただしこの記述については少し気になる所があって、検索に引っかかる割りには指摘をしている記事が見当たらなかったので、その点について書いておきます。
上の例でCSSで要素を指定しておきながら、documentからたどって該当要素を探しているのは少し効率が悪いですね。更に#containerとdocument.bodyでは要素が違うので、場合によっては予期しない動作をするかもしれません。
通常はexpression内で以下のようにthisを使って、selectorの該当要素を参照します。
*html #container {
width : expression( this.clientWidth > 1000 ? '1000px' : 'auto' );
}
更にexpressionは画面サイズを変更したり、マウスを動かしただけでも実行されるため、非常に重くなることがあります。上記程度のソースではそれほど問題ないのかもしれませんが(それも要素が多くなれば分からない)、できるだけ軽くする工夫は必要ですね。これについてはbehaviorを使い、expression内でbehaviorを消すことで、expressionの動作を1回限りに抑えることができます(注、widthの場合は消したら表示が戻ってしまうからbehaviorを使うとも言える)。
*html #container {
behavior: expression( (function( el ) {
el.style.width = el.clientWidth > 1000 ? '1000px' : 'auto';
el.runtimeStyle.behavior = 'none';
})( this ));
}
behaviorを使うとwidthの指定ができないので、要素のスタイル属性のwidthに直接書き込んでいます。
expressionでは2行に渡ってJavaScriptを書くことができない(セミコロン;を入れるとエラーになる)のでfunctionでラップしています。更にfunction内ではthisで要素を参照できない(←こっちはJSの仕様、スコープが変わるとthisも変わる)のでthisを引数で渡しています。
runtimeStyleはstyleよりも優先度が高いので採用していますが、!importantよりは弱いようなので、普通は「el.style.behavior = 'none';」でもいいような気はします。ダメなケースはあると思うので、その辺は臨機応変に。
max-widthの応用です。
*html #container {
behavior: expression( (function( el ) {
el.style.width = el.clientWidth < 500 ? '500px' : 'auto';
el.runtimeStyle.behavior = 'none';
})( this ));
}
*html #container {
behavior: expression( (function( el ) {
el.style.width =
el.clientWidth < 500 ? '500px':
el.clientWidth > 1000 ? '1000px':
'auto';
el.runtimeStyle.behavior = 'none';
})( this ));
}
友人に聞かれた内容のメモ。JavaScriptでは一般的な使い方なのですが、privateやpublicで指定できないので、初めのうちは結構戸惑いますね。
まず、共有プロパティを使ったメソッドが存在するオブジェクトを普通に作ってみます。
var obj = {
// 内部で使うための共有プロパティ
prop0: 0,
// 内部で使うための共有メソッド
method0: function() {
return ++this.prop0;
},
// 外部から使うためのメソッド
method1: function() {
alert(this.method0());
}
}
obj.method1(); // 1と表示
obj.method1(); // 2と表示
ただし、prop0やmethod0は内部だけで使いたいのに、外部からもアクセスできてしまいます。
↓上からの続き
obj.prop0++; // 外部から値を変えられたり obj.method0(); // 外部から実行されたり
純粋に内部で使うための変数や関数を外部に見せてしまうのは良くないですね。JSの場合は特にメソッドのオーバーライドができてしまうから何かとトラブルの元。
obj.method0 = 'foo'; // 外部からオーバーライドされたりする obj.method1(); // this.method0 is not a function
オブジェクトのプロパティやメソッドは外部での使用に限るに越したことはありません。
functionで一段スコープを下げるだけで簡単にプライベート変数を実現できます。
オブジェクトのプライベート変数はエンクロージャの内部変数(つまりオブジェクトの外)で作っておき、オブジェクト自身はエンクロージャからreturnで戻します。
// エンクロージャの戻り値(作成するオブジェクト)をobjに代入
var obj = function() {
// オブジェクトで使うプライベート変数
var prop0 = 0;
// オブジェクトで使うプライベート関数
var func0 = function() {
return ++prop0;
};
// オブジェクト本体(objに返す)
return {
// 外部から使うメソッド1
method1: function() {
alert(func0());
},
// 外部から使うメソッド2
method2: function() {
func0();
alert(func0());
}
};
}(); // ←カッコ()付きなので無名関数(エンクロージャ)が実行される
obj.method1(); // 1と表示
obj.method2(); // 3と表示
// ↑func0を通してprop0の値が保持されている証拠
method1はクロージャとして働きます。その中で使われるfunc0は無名関数実行後も保持されます(レキシカル関数)。
更にfunc0もクロージャなので、その中で使われるprop0も保持されます(レキシカル変数)。こういう工夫ってJSならではですね。
クロージャについてはまたの機会にでも。
珍しく更新頻度が高いですが、前回の記事をチェックしているときに気付いたので、忘れないうちにメモ。
今回の記事は、一般的にこういう使い方も知られています、という内容。
getElementsByTagNameで取ってきたDOMエレメント(HTMLLIElement)の配列風オブジェクトHTMLCollectionや同じくquerySelectorAllのNodeListは、経験上なんとなくArrayクラスのメソッドが使えない気がしてたのですが、(↓並びを逆にしようとする一例)
// li要素列を取得(HTMLCollection)
var els = document.getElementsByTagName('li');
// Arrayクラスのreverseメソッドをelsのメソッドとして実行
Array.prototype.reverse.call(els); // エラー
これ↓はHTMLCollectionやNodeListでも動きました。
// 配列化 els = Array.prototype.slice.call(els); // reverseメソッドを実行 els.reverse();
sliceは引数を渡さないと返り値はそのままという事を利用した配列化です。よくargumentsの配列化で使われる式ですね。
ちなみにArray自身にもArrayクラスのメソッドがあるみたいなので、
els = Array.slice.call(els);
という記述もできるみたい。
HTMLCollectionやNodeListは、Arrayクラスの破壊メソッド(オブジェクト自身を書き換える)はダメでも、非破壊メソッドなら動く。
// for
var ar = [];
for (var i = 0; i < els.length; i++) {
ar[i] = els[i];
}
// while
var i = els.length;
var ar = new Array(i); // []でもいけますが一応。
while (i--) {
ar[i] = els[i];
}