CSSにおけるセレクタの固有性(詳細度)の計算式
固有性(specificity)の計算式
CSS3 ではセレクタの下記手順で固有性(specificity)を求めます(CSS2では否定セレクタが存在しないだけで計算式は同様)。
- ID属性の数を a とする)
- 他の属性セレクタ、擬似クラスの数を b とする
- タイプセレクタ(要素)の数を c とする
- 否定セレクタは引数と同じ数として計算される
- 擬似要素を無視する。
- a-b-cを連結して固有性(specificity)を求める
仕様では "#id" の固有性を 100 のように表しているため、b の数が10を超えると10進数の繰上げが発生しそうですが、連結なので繰り上げは発生しません。
例えば、「a=1, b= 11, c= 15」なら固有性は 1-11-15 のように連結されます。16進数で表すなら "1AF" となり、基数に制限のない数値と解釈してもいいかもしれません。
サンプルによる説明
<style type="text/css"> /* 固有性 = 1-0-0 */ #Sample { background-color: #fee; } /* 固有性 = 0-11-0 */ .a.b.c.d.e.f.g.h.i.j.k.l { background-color: #eef; } </style> </head> <body> <div id="Sample" class="a b c d e f g h i j k l">#Sample.a.b.c.d.e.f.g.h.i.j.k.l</div>
固有性が 1-0-0 であるIDセレクタが優先されます。
DOMオブジェクトとスクリプトエンジンオブジェクト間の循環参照における IE6 SP2- のメモリリークパターン
概要
IE6 SP2- には下記条件でページの unload 時にメモリリークするバグが存在します。
メモリリークするコード
代表的な循環参照パターンは下記の通りです。
// DOM-script間の循環参照パターン[1] (メモリリークする) (function () { var element = document.getElementById('Sample'); element.onclick = function () { // element(DOM) -> onclick(DOM) -> function(script) -> element(DOM) // クロージャでローカル変数(element)への参照を保持する為、メモリリークする }; })(); // DOM-script間の循環参照パターン[2] (メモリリークする) (function () { var element = document.getElementById('Sample'); element.attachEvent('onclick', function () { // element(DOM) -> onclick(DOM) -> function(script) -> element(DOM) // クロージャでローカル変数(element)への参照を保持する為、メモリリークする }); })(); // DOM-script間の循環参照パターン[3] (メモリリークしない) document.onclick = function () { // document(DOM) -> onclick(DOM) -> function(script) -> element(DOM) // グローバルオブジェクト(document)への参照を保持してもメモリリークしない }; // DOM-script間の循環参照パターン[4] (メモリリークしない) document.attachEvent('onclick', function () { // document(DOM) -> onclick(DOM) -> function(script) -> element(DOM) // グローバルオブジェクト(document)への参照を保持してもメモリリークしない }); // script-script間の循環参照パターン[1] (メモリリークしない) var obj = {}; obj.prop = obj; // 同一のscriptエンジン間の循環参照はscriptエンジンがメモリ管理する為、メモリリークしない // DOM-DOM間の循環参照パターン[1] (メモリリークしない) document.document.body.firstChild.parentNode; // document(DOM) -> body(DOM) -> firstChild(DOM) -> parentNode(DOM) === body(DOM) // 同一のDOMプロセッサ間の循環参照はDOMプロセッサがメモリ管理する為、メモリリークしない(そもそも、何もしなくても初めから循環参照しているので、これでメモリリークしたら大変) })();
メモリリークしないコード
循環参照しないパターンにする事でメモリリークパターンではなくなります。
// DOM-script間の循環参照しないパターン[1] (メモリリークしない) (function () { var element = document.getElementById('Sample'); element.onclick = function () { // element(DOM) -> onclick(DOM) -> function(script) -> element(script) // element = null; が実行されるまでは循環参照し、null が代入された時点から循環参照しなくなる }; element.ondblclick = function () { // element(DOM) -> onclick(DOM) -> function(script) -> element(script) // element = null; が実行されるまでは循環参照し、null が代入された時点から循環参照しなくなる }; element = null; // 使い終わったローカル変数に null を代入する事でスクリプトエンジンのオブジェクトにする })(); // DOM-script間の循環参照しないパターン[2] (メモリリークしない) (function () { var element = document.getElementById('Sample'); function handleClick (event) { // handleClick(script) -> element(DOM) // window unload するまでは循環参照している } function handleUnload () { // (※jQuery にも同様の処理が入っています) element.detachEvent('onclick', handleClick); // 「handleClick(script) -> element(DOM)」間の参照を切る事で循環参照しなくなる window.detachEvent('onunload', handleUnload); // 自身の循環参照も切っておく(理由は同上) element = null; // ついでに element(DOM) も element(script) にしておく } element.attachEvent('onclick', handleClick); window.attachEvent('onunload', handleUnload); })(); // DOM-script間の循環参照しないパターン[3] (メモリリークしない) (function () { function handleClick (event) { console.log(event); // 初めから循環参照していない } function init () { var element = document.getElementById('Sample'); element.attachEvent('onclick', handleClick); // element(DOM) -> onclick(DOM) -> handleClick(script) // handleClick はローカル変数(element)を参照不可能な為、循環参照しない } init(); })();
クロージャのメモリ消費量
メモリリークではありませんが、クロージャには「クロージャが保持しているローカル変数は window unload するまでメモリに残り続ける」という性質があり、工夫することでメモリを節約出来ます。
前節の「メモリリークしないコード」ではメモリ消費量は次のようになり、パターン[3] が最もメモリに優しいコードになります。
Function#call で循環参照しないパターン
一時的に参照を保持したいが、下位スコープまで影響を広げたくない場合に Function.prototype.call
を利用すると解決できる場合があります。
'use script'; // DOM-script間の循環参照パターン[5] (メモリリークする) (function (window) { window.attachEvent('onlock', function (event) { // window(DOM) -> onlock(DOM) -> function(script) -> window(DOM) // クロージャでローカル変数(window)への参照を保持する為、メモリリークする }); }(this)); // DOM-script間の循環参照しないパターン[4] (メモリリークしない) (function () { this.attachEvent('onclick', function (event) { // window(DOM) -> onlock(DOM) -> function(script) // クロージャで参照可能なローカル変数(DOM)が存在しない為、循環参照せず、メモリリークしない }); }.call(this)); // Function.prototype.call は下位スコープには影響しない
typeof null === 'object' は ECMAScript 3 の仕様バグ
ECMAScript 3 の typeof 演算子
typeof演算子は対象の型を返す演算子ですが、null に適用すると "object" が返ってきます。
console.log(typeof null === 'object'); // true
では、null は Object 型なのか、というとそうではなくて仕様バグだったりします。
#250 ((Resolved) "typeof null") – ECMAScript Bugs – TracChanged 3 weeks ago by brendan
You know, this all came about because of rushing in early May 1995, which led to a leak of type tag representation shared by null and object types. But null means "no object", so it didn't raise hackles until it was too late to fix in Netscape 2, and after that we were loath to "fix" it and "break the web".
That argument only applies more in degree of web population now.
We have other fish to fry. This one was has been swallowed already. Let's not change typeof null for ES4 and work on more vital issues.
/be
簡単に説明すると、「納期に押されてやっちまったぜ!」的なコメントだそうで…。
ECMAScript 5.1 の typeof 演算子
ECMAScript 5.1 でも typeof 演算子の挙動は変わりませんが、Object型 の解説が寄り詳しくなったので意訳してみました。
型 | typeof 演算子の結果 |
---|---|
Undefined型 | "undefined" |
Null型 | "object" |
Boolean型 | "boolean" |
Number型 | "number" |
String型 | "string" |
Object型 (ネイティブオブジェクトで Call を持たないもの) | "object" |
Object型 (ネイティブオブジェクトで Call を持つもの) | "function" |
Object型 (ホストオブジェクト) | "undefined", "boolean", "number", "string" を除く処理系定義 |
ES.next の typeof 演算子
ECMAScript 5.1 の後継に ES.next があり、上手くいけば ECMAScript 6 として仕様策定される見込みとなっています。
ES.next ではとうとう typeof 演算子の仕様バグが修正されました。
console.log(typeof null === 'null'); // true
Date() と new Date() は等価ではない
概要
Native Object のコンストラクタの中には関数呼び出しとコンストラクタ呼び出しが同じ動作になるものがあります。例えば、Array()
は new Array()
と等価です。
new Array(1, 2, 3); // [1, 2, 3] Array(1, 2, 3); // [1, 2, 3]
対して、Date()
は new Date()
と等価ではありません。
new Date(2011, 0, 1, 0, 0); // Sat Jan 01 2011 00:00:00 GMT+0900 (Japan Standard Time) Date(2011, 0, 1, 0, 0); // 現在時刻 (Google Chrome 12, Firefox4, Opera 11.50, IE8 で確認)
関数呼び出しの Date()
は引数を無視して現在時刻を返しているようです。
ECMAScript 3 では
ECMAScript 3 では次のように書かれています。
http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/15-9_Date_Objects.html#section-15.9.215.9.2 関数として呼ばれる Date コンストラクタ
コンストラクタとしてではなく関数として Date が呼出されるとき、それは現在時間 (UTC) のあらわす文字列を返す。
NOTE 関数呼出し Date(...) と、同じ引数を持つオブジェクト生成式 new Date(...) は、等価ではない。
なるほど。確かに Date
は現在時刻を返しています。次の小節を読んでみます。
http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/15-9_Date_Objects.html#section-15.9.2.115.9.2.1 Date ( [ year [, month [, date [, hours [, minutes [, seconds [, ms ] ] ] ] ] ] ] )
すべたの引数は選択的である; 供給された引数は受け付けるが、それ以外は無視される。文字列が生成され、式 (new Date()).toString() によるものと同様の結果が返される。
"供給された引数は受け付ける" とありますが、受け付けた引数はどのように処理されるのでしょう…?私には矛盾しているように思えます。
(2011/07/31 15:47追記)
@rikuba さんからリプライを頂きました。ありがとうございます。
原文(ECMAScript 3)では次のように書かれています。
http://bclary.com/2004/11/07/#a-15.9.2.115.9.2.1 Date([ year [, month[, date[, hours [, minutes[, seconds[, ms]]]]]]])
All of the arguments are optional; any arguments supplied are accepted but are completely ignored. A string is created and returned as if by the expression (new Date()).toString().
下記は意訳です。
http://bclary.com/2004/11/07/#a-15.9.2.115.9.2.1 Date([ year [, month[, date[, hours [, minutes[, seconds[, ms]]]]]]])
全ての引数はオプションです。供給された全ての引数を受け入れますが、完全に無視されます。文字列(String)が生成され、式
(new Date()).toString()
の評価値が返されます。
結論
Date()
は new Date()
と等価ではありません。その点ははっきりしていますが、Date()
で受け付けた引数の扱いがよくわかりません。謎です…。
Date()
呼び出し時の全ての実引数は無視され、(new Date()).toString()
に等しいString値を返します。
MDN ではfor文の第一要素を初期化式(initial-expression)と説明している
概要
for文の第一要素で変数宣言するコードは次のようになります。
for (var i = 0; i < 10; i++) { alert(i); }
この時、var i = 0
が「式」であるかのように誤解されることがあるようです。
ECMAScript 3 では
ECMAScript 3 では ExpressionNoIn
と VariableDeclarationListNoIn
の2つにわけて説明されています。
for (ExpressionNoIn; Expression ; Expression ) Statement for ( var VariableDeclarationListNoIn; Expression ; Expression ) Statementhttp://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/12_Statements.html#section-12.6
VariableDeclarationListNoIn
では変数文 (VariableStatement) と同じように機能します。これは「式」ではありません。
for (var i = 0, length = 10; i < length; i++) { // 第一要素は VariableDeclarationListNoIn alert(i); }
ExpressionNoIn
では「式」をカンマ演算子で列挙できます。
var i, length; // VariableStatement for (i = 0, length = 10; i < length; i++) { // 第一要素は ExpressionNoIn alert(i); }
MDN では
MDNでfor文の第一要素の説明を見てみます。
構文
for ([initial-expression]; [condition]; [final-expression]) statementhttps://developer.mozilla.org/ja/JavaScript/Reference/Statements/forParameters
initial-expression
(代入式を含む) 式または変数宣言。たいていは、カウンタ変数を初期化するために使われます。この式では、var キーワードを用いて新しい変数を任意で宣言してもかまいません。これらの変数はループにローカルなものではありません。すなわち、これらは for ループが属するスコープと同じスコープ内にあります。この式の結果は捨て去られます。
"式または変数宣言" は正当な説明だと思いますが、大本は initial-expression
とあるので変数宣言も式であると勘違いされる可能性があります。
また、"この式では、var キーワードを用いて新しい変数を任意で宣言してもかまいません。" という記述は極めてわかりにくく、「var キーワードを使って代入式を宣言している」ようにも読み取れます。
『JavaScript第5版』では
『JavaScript第5版』(いわゆるサイ本)には次のように書かれています。
JavaScript第5版 P906.8 for文
...(中略)...
for文の書式は次のとおりです。for (initialize; test; increment) statementfor文の働きは、for文と同じ処理を行うwhile文と比較するとよくわかると思います。
initialize; while(test) { statement increment; }ループを行う前に initialize 式を1回だけ評価します。通常、initialize 式は代入などの副作用を伴います。JavaScript では、var文を使って変数を宣言することもできます。ループカウンタ変数の宣言と初期化が同時にできて便利です。...
やや複雑ですが、「initialize 式」と「変数宣言」をわけて解説されていることがわかります。「initialize 式」は英語にすれば "initial-expression" になりますから、MDN の 語源 出典はここにあるのかもしれません。ただ、"initial-expression" に変数宣言も含めるのは『JavaScript第5版』の著者の意図に反していると思います。
MDNから生じた勘違い
下記のような勘違いが生まれたことがありました。(説明の都合上、文面を多少換えています)
- 「
for (var i = 0, length = 10; i < length; i++)
という書き方もあるよ。」 - 「カンマ演算子を使っているのか。」
- 「カンマ演算子なんて出てきてないよ。」
- 「var a=1, b=2, c=3; をカンマ演算子というのか。勉強になった。」
- 「違うよ!全然違うよ!」
- 「MDCのfor文の説明には『for ([initial-expression]; [condition]; [final-expression]) (代入式を含む) 式または変数宣言。』とあるんだけど…。」(※現在のMDNは当時MDCという名前でした)
- 「var がついてなければ式。var がついていれば文。」
- 「なるほど。勉強になった。」
「変数宣言が式である -> 式であるならカンマ演算子が使えるはず」という流れ。確かに式ならカンマ演算子を使えますからそう解釈する気持ちは理解できます。
結論
for文の第一要素d変数初期化を行う場合、var を伴わない変数宣言は「式」ですが var を伴う変数宣言は「式」ではありません。var を伴う変数宣言を「文」と呼べるかは少し怪しい気もします(「文」なら "VariableStatement" を定義すればよく、わざわざ "var VariableDeclarationListNoIn" を定義しているところをみると「文」と区別されているように思えます)が、少なくとも「式」ではないと思います。
結論としては、MDN の "initial-expression" は「式のみを入れられる」という誤解を生むと思います。
ECMAScript 5 規定の undefined は書き換え不可能
概要
次のようなコードをよく見ます。
undefined = 1; // 1 (function () { var undefined; // 同名のローカル変数を定義 alert(undefined); // undefined })();
ところが、ECMAScript 5.1 規定の undefined は書き換え不可能([[Writable]]: false)です。
15.1.1.3 undefined # Ⓣ Ⓡ
http://es5.github.com/#x15.1.1.3
The value of undefined is undefined (see 8.1). This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.
従って、ローカル変数を定義する必要がなくなります。
undefined = 1; // 1 alert(undefined); // undefined (OK = Firefox 5 / NG = Google Chrome 12, Opera 11.50, IE8)
ローカル変数 undefined を定義する手法の問題点
ローカル変数 undefined
は [[Writable]]: true ですので、ローカル変数定義後に値を書き換えることを許してしまいます。
undefined = 1; // 1 alert(undefined); // undefined (function () { var undefined; // 同名のローカル変数を定義 undefined = 1; alert(undefined); // 1 })();
書き換え不可能なグローバル変数を定義する
ECMAScript 5.1 規定の Object.defineProperty
を実装しているブラウザなら書き換え不可能なプロパティ undefined
を定義できます。
/** * グローバルコード */ if (typeof Object.defineProperty === 'function') { Object.defineProperty(this, 'undefined', {value: void 0, writable: false, enumerable: false}); // [[Writable]]: false, [[Enumerable]]: false } else { undefined = void 0; } undefined = 1; // 1 alert(undefined); // undefined
参考リンク
Tweetログ
備忘録です。
http://jsfiddle.net/3DS2v/5/
8.6.1 Property Attributes - Annotated ES5
http://goo.gl/l3xVD #ES5
DOM Events の「バブルアップ」の語源は MDN
バブルアップの語源
DOM L2 Events には親要素にイベントが伝播していくイベントバブル動作(Event bubbling)があり、これを「バブルアップ」と解説されているサイトをたまに見ますが、語源はMDNにあったようです。
(英語版には "bubble up" とあり、翻訳により発生した語句ではないことがわかります。)
上記の例では、modifyText() が addEventListener() を用いて登録された click イベントのリスナーになっています。table 中のどこをクリックしても、そのハンドラまでバブルアップし、modifyText() が実行されます。
https://developer.mozilla.org/ja/DOM/element.addEventListener#.e4.be.8b
DOM L2 Events では
DOM L2 Events では "上方向の伝播(upward propagation)" と説明されています。
そのとき,バブル動作イベントは,EventTargetの親連鎖を上位の方向にたどり,それに続く各EventTarget上に登録されたイベントリスナに対して検査を行うことによって見出される付加的なイベントリスナを誘発する。この上方向の伝播は,Documentまでそれを含んで継続される。
http://www.y-adagio.com/public/standards/tr_dom2_events/events.html#Events-flow-bubbling
感想
"上方向の伝播(upward propagation)" に表記を直した方が個人的にはわかりやすいと思います。
ただでさえイベントバブル動作は誤解されやすいというのに「バブルアップ」という新しい用語を作っても混乱が増すだけかなーと。