ずっと工事中

〜ご不便をおかけいたします〜

コメント欄を一時的に消してみました。

スパムが多いので、コメント欄を一時的に消してみました。CSSで消しているだけなので、ロボットだったらかいくぐってくることでしょう。

追記:やはり入ってきたので、コメント欄のHTMLを消してみました。ご不便をおかけします。

stylesheetオブジェクトを使ってGM_addStyleみたいにCSSを書いてみる [JavaScript]

JavaScriptのライブラリーで「CSSも一緒にアップロードしてね」みたいなものが多いですが、jsファイルとcssファイルが別だとサイト管理上結構煩雑になったりします。

ということで、今回はJavaScript側でCSSを書いてしまおうという試みです。

JavaScriptでCSSを書くといっても、style属性を全要素にいちいち書いていたのでは日が暮れてしまうので、stylesheetオブジェクトを作って、そちらにアクセスするようなものを考えてみました。

言い換えれば「GM_addStyleを普通のJavaScriptで使いたい」みたいな企画です。

new String()と文字列リテラルについてのメモ [JavaScript]

普段は意識しなくていいと思いますが、たまにハマることがある、newで作るStringオブジェクトとリテラル表記で作る文字列の違いについてです。

var str0 = new String('foo');	// Stringオブジェクトを作成
var str1 = 'foo';		// 文字列リテラルで作成

Stringオブジェクトのstr0は参照型のオブジェクト、文字列リテラルで作ったstr1はプリミティブ型です。

typeof

var str0 = new String('foo');
alert(typeof str0);		// object

var str1 = 'foo';
alert(typeof str1);		// string

まずはタイプが違いますね。

instanceof

var str0 = new String('foo');
alert(str0 instanceof String);		// true

var str1 = 'foo';
alert(str1 instanceof String);		// false

リテラルで作ったときはStringのインスタンスではありません。

Stringオブジェクトを含めて文字列かどうかを調べる

以上のことから、変数strが文字列かどうかを調べるとき、

(typeof str == 'string' || str instanceof String)

とする必要があります。

文字列の比較

リテラルで作成した場合は、

var str0 = 'foo';
var str1 = 'foo';

alert(str0 == str1);	// true
alert(str0 === str1);	// true:厳密比較演算子を使ってもいい

予想通りの結果です。

new Stringで作成した場合は、

var str0 = new String('foo');
var str1 = new String('foo');

alert(str0 == str1);	// false
alert(str0 === str1);	// false

ここが要注意です。参照型なので、値の比較は行われず参照の比較となるためfalseになります。

new Stringで作成した場合にも値の比較をしたい。

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オブジェクトのメソッドを使うことができます。自動的にStringオブジェクトに型変換されるためだとか某書に書いてあったような気がします。

var res = 'foo'.match(/oo/);

constructorプロパティ

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オブジェクトのインスタンスでないにもかかわらずですが、これも自動型変換されるからでしょうか?

IE6のmax-widthとmin-widthと、expressionについての覚え書き [CSS, JavaScript]

max-widthのよくあるサンプル

max-widthを使ったちょっとしたサンプルを作る機会があって、IE6でに対応しようと思って検索してみたらIE独自拡張のexpressionを使っているものが多かったです。概要はこんな感じでしょうか。

*html #container {
	width : expression( document.body.clientWidth > 1000 ? '1000px' : 'auto' );
}

ただしこの記述については少し気になる所があって、検索に引っかかる割りには指摘をしている記事が見当たらなかったので、その点について書いておきます。

CSSで該当した要素はthisで参照できる

上の例でCSSで要素を指定しておきながら、documentからたどって該当要素を探しているのは少し効率が悪いですね。更に#containerとdocument.bodyでは要素が違うので、場合によっては予期しない動作をするかもしれません。
通常はexpression内で以下のようにthisを使って、selectorの該当要素を参照します。

*html #container {
	width : expression( this.clientWidth > 1000 ? '1000px' : 'auto' );
}

expressionはbehaviorとセットで使って1度だけ実行する

更に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';」でもいいような気はします。ダメなケースはあると思うので、その辺は臨機応変に。

min-width

max-widthの応用です。

*html #container {
	behavior: expression( (function( el ) {
		el.style.width = el.clientWidth < 500 ? '500px' : 'auto';
		el.runtimeStyle.behavior = 'none';
	})( this ));
}

max-width + min-width

*html #container {
	behavior: expression( (function( el ) {
		el.style.width = 
			el.clientWidth < 500 ?	'500px':
			el.clientWidth > 1000 ? '1000px':
						'auto';
		el.runtimeStyle.behavior = 'none';
	})( this ));
}

オブジェクトで共有するプライベート変数や関数を作る方法 [JavaScript, クロージャ]

友人に聞かれた内容のメモ。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ならではですね。

クロージャについてはまたの機会にでも。

DOMのノードリスト(HTMLCollectionやNodeList)の配列化 [JavaScript]

珍しく更新頻度が高いですが、前回の記事をチェックしているときに気付いたので、忘れないうちにメモ。

今回の記事は、一般的にこういう使い方も知られています、という内容。

Arrayクラスのメソッドをノードリストのメソッドとして実行

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クラスの破壊メソッド(オブジェクト自身を書き換える)はダメでも、非破壊メソッドなら動く。

ちなみに

  • なぜ配列化したいのかというと、Arrayクラスには便利なメソッドが沢山あるから。しかもDOMでアクセスするよりも速いらしいです。
  • HTMLCollectionは動的オブジェクト(例えば取得後でも該当要素を動的に追加すると、HTMLCollectionの要素も増える)、NodeListは静的オブジェクト(取得した瞬間から変化しない)です。
  • 一般的には配列化するときはループでコピーします。
// 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];
}

ギャラリー
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
記事検索
Recent Comments
訪問者数

    • ライブドアブログ