これでできる! クロスブラウザJavaScript入門

第4回 JavaScriptの基礎知識#1

この記事を読むのに必要な時間:およそ 4 分

こんにちは,太田です。前回はクロスブラウザのパターンについてまとめました。今回はより具体的にJavaScriptの基礎的な部分からそこそこJavaScriptに慣れた方でも間違いやすいポイントを中心に解説します。

JavaScriptの背景知識

JavaScriptは(未だに)誤解されがちな言語です。まずはJavaScriptの背景から解説していきます。

(広義の)JavaScriptとはEcma Internationalによって策定されているECMA-262という規格(ECMAScript)を実装した処理系で実行される言語を指します。遠回りな表現になっていますが,これはJavaScriptのややこしさの一端を表しています。つまり,JavaScriptそれ自体に仕様があるわけではない,ということです。ECMAScriptと呼ばれる言語の仕様があって,その仕様に準拠した言語を(広義の)JavaScriptと(慣例的に)呼んでいるだけなのです。

ECMAScriptは比較的「ゆるい」言語です。ECMAScriptで規定された範囲については実装側がサポートすることを要求していますが,それ以外の部分について実装側で型やオブジェクトなどを追加することを許可しています。これはそもそも,ECMAScriptはNetscapeのJavaScriptとIEのJScriptをベースに共通部分を仕様化することで生まれた言語という歴史的な経緯の結果でもあり,同時にJavaScriptの進化(混乱?)の要因でもあります。

さて,上記でNetscapeのJavaScriptと書きました。そう,JavaScriptは元々Netscapeのものでした。Netscapeは開発もサポートも終了してしまっていますが,その系譜はMozillaに引き継がれています。先程「⁠広義の)JavaScript」とも書きましたが,これに対する「狭義のJavaScript」とはNetscape,MozillaのJavaScriptを指します。

よって,単にJavaScriptと書くとMozillaのJavaScriptを指すのか,クロスブラウザなJavaScriptを指すのかはっきりしない,ということです。実際に,MozillaのJavaScriptエンジンであるSpiderMonkeyはJavaScript 1.5,1.6(Firefox 1.5)⁠1.7(Firefox 2)⁠1.8(Firefox 3)⁠1.8.1(Firefox 3.5)⁠1.8.2(Firefox 3.6)とバージョンアップを続けていますが,このバージョン番号が広義のJavaScriptのバージョンと混同されてしまうといったことはよくあります(今回は扱いませんが,FirefoxのJavaScriptは独自の進化を遂げ,ECMAScriptとはかなり距離ができています。ECMAScriptがMozillaのJavaScriptから離れたとも言えます)⁠なお,この連載では単にJavaScriptと書いたときはもちろん広義のJavaScriptを指します。これに対して,例えばMozilla Developer Center - MDCにおけるJavaScriptとは,多くの場合でMozillaのJavaScriptを指しています。⁠Mozillaの)JavaScript 1.5はECMA-262 3rd editionとほぼ同等で,Opera,Safari,ChromeなどもJavaScript 1.5をひとつの指標としてJavaScriptエンジンを実装している面があるので,⁠OperaのJavaScriptエンジンはJavaScript 1.5に対応」といった表現も間違いではないのがややこしいところです。

JavaScriptのオブジェクトと型

JavaScriptのデータ型は大きく分けて「プリミティブ値」「オブジェクト」の2種類に分けることができます。プリミティブ値を細かく分けると,数値,文字列,真偽値,null,undefinedの5種類です(nullとundefinedはやや特殊ですがここではまとめておきます)⁠オブジェクトはキー(文字列)とバリュー(プリミティブ値とオブジェクト)で表現される複合データで,配列や関数などもオブジェクトに含まれます。

JavaScriptのprototype

JavaScriptはprototypeベースの言語です。null,undefined以外のデータはprototypeを介して拡張することが可能です。これはプリミティブな値も例外ではありません。

例えば下記のようにString.prototypeを拡張することで,文字列リテラルにも任意のメソッドを追加することができます。

String.prototype.trimの実装

if (!String.prototype.trim) {
  String.prototype.trim = function(){
    return this.replace(/^\s+|\s+$/g, '');
  }
}
' string \u00a0 \t \v \f '.trim() // -> 'string'

なお,String.prototype.trimはECMA-262 5th editionで追加されたメソッドです。Chrome 4やFirefox 3.6ではすでにネイティブに実装されています(Chrome,Firefoxではtrim以外にもtrimLeft,trimRightも実装していますが,標準ではありません)⁠

こういったprototypeの拡張は便利ですが,問題もあります。prototypeに追加されたプロパティはfor inで列挙されてしまうという問題です。for inを使うことがないモノについてはほとんど問題ありませんが,ObjectやArrayなどでは重大な問題です。

Array.prototypeの拡張とIEでのfor in

if (!Array.prototype.forEach) {
  Array.prototype.forEach = function(func, that) {
    for (var i = 0, len = this.length; i < len; i++) {
      if (i in this) {
        func.call(that, this[i], i, this);
      }
    }
  };
}
var a = [1,2];
a.forEach(function(v,i,a){
  alert(v);// 1, 2
});
for (var i in a) {
  alert(i);// 0, 1, forEach
}
for (var i in a) {
  if (a.hasOwnProperty(i)) {
    alert(i);// 0, 1
  }
}

IEで上記のコードを実行すると途中でforEachがアラートされます。hasOwnPropertyというメソッドで自分自身がもつプロパティかどうかチェックすることで回避することはできますが,なるべくならシンプルに書きたいところでしょう。よって,⁠ネイティブな)prototypeを拡張する場合はfor inが使われていない,使ってしまうことがないと保証できる場合に限る必要があります。逆にfor inを使う場合はprototypeが拡張されていないかどうかに注意する必要があります。

Object.prototypeについては,拡張すると影響範囲が非常に大きいため禁止としたほうがよいでしょう。なお,ECMAScript 5では安全にprototypeを拡張する方法が用意されているので,Object.prototypeに任意のメソッドを追加することが可能になります。

なお,これらはネイティブなprototypeを拡張する場合の注意ですので,自前で定義した関数のprototypeを拡張するのはまったく問題ありません。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/

コメント

コメントの記入