こんにちは。道路に飛び出した猫に向かって危ないって叫ぼうとしたら思わず「ニャーッ!!」って叫んでしまった技術部技術支援グループのtakaiです。
ちなみに猫は無事でした。よかったですね。

さて、今回はミクシィで実施している技術研修について紹介しようと思います。

大事なこと

ミクシィの技術研修は主に新卒のエンジニア職向けに実施しています。
肝心なコンセプトは既に部長が書いてしまいましたが大事なことなのでもう1度言います。

現在ミクシィでは以下のコンセプトのもと、技術研修を行なっています。

  • 関係各所、チーム、チーム横断でのタスクに関して 迷惑をかけずに自分で判断できる/あるいは正しく判断を仰げる状態までの成長
  • 現状の技術的問題点や課題を把握し、改善策や改善のためのプランニングができる
  • 各項目への知識体系の羅針盤を提供して、自学自習によってより高度な領域まで発展することができる

「ミクシィでしか使えないこと」の教育は最小限に抑え「エンジニアとして普遍的なこと」に重点を置いて教育をしています。

どうしてこのような研修コンセプトになったのでしょうか?
事業に特化した技術というのは時代の流れや環境の変化で変わってしまう可能性があります。
この変化に対応するにはエンジニアとして軸になる普遍的な知識が必要不可欠だからです。
また、普遍的な知識があれば何か障害があったとしても、また迷うことがあったとしても落ち着いて対応できます。

技術研修

次に「エンジニアとして普遍的なこと」を目的に作られた研修をご紹介します。
科目は多々あるのですが大きくカテゴライズすると以下3点に分けれらます。

  • 基礎編:エンジニアとしての基礎的な技術を学ぶ研修
  • 応用編:エンジニアとして普遍的な技術を学ぶ研修
  • その他:業務に特化した技術、業務を遂行するのに必要な知識を学ぶ研修

さらに各カテゴリの内容について説明します。

基礎編

LAMP開発に必要なUNIXやエディタ系のレクチャから始まります。
またバージョン管理システムやテスト手法についての講義もあります。
バージョン管理システムやテスト手法の講義については手順だけでなく、どうして必要なのか?という背景まで掘り下げて説明をすることで、根本的な理解を促しています。
近年ではJavaScriptの開発も多くなって来ていますので、JavaScript開発の基礎についてのメニューも用意しています。
また運用エンジニアによるサーバー周りのお話やDBの設計、テーブルのスケール設計などの講義もあります。
基礎編を受けるだけでWeb開発のベースはバッチリ身につきます。

応用編

応用編は、より普遍的な知識を学びます。
オブジェクト指向の開発を始め、オブジェクト指向設計やパッケージ凝集設計などのOOP、OOA、OODに必要な基礎知識の講義があります。
また、部長がご紹介しましたセキュリティプログラミングのトレーニング。
さらにはベンチマークテストなどパフォーマンスチューニングに役立つメニューもあります。
より深くて濃い知識を身につけることができます。

その他

ミクシィでの広告開発や配信の仕組み、課金システム周りの業務に特化した講義を始め、業務ルールを学びます。
こちらを受講することでミクシィ独自のルールや仕組みを理解することができます。

特徴

前述したことが最大の特徴ではあるのですが、ミクシィの技術研修にはもう一つ特徴があります。それは100%内製だということです。
研修の講師とサポートするチューターは社員のエンジニアが担当し、教材や課題を考えるのも社員です。

内製化することのメリットとしては必要に応じてカスタマイズが可能です。
現状、まだカスタマイズの自由度は低いですが、今後は細分化しより受講者のスキルや方向性に併せた研修を実施できるように改善していきたいと考えています。

さらに内製化することで受講者だけではなく、講師やチューターにもメリットがあります。
それは教えること自体が講師やチューターの勉強にもつながっているのです。
フランスの哲学者であるジョセフ・ジュベールが「教えることは2度学ぶことである」と言っていますが、まさしく実践しているのです。

内製化というのは資料を作ったり、課題を考えたりなど大変な面もあります。
また、自分がちゃんと理解していなければ人に教えるなんてとても務まりません。
その下準備としてまずは理解度を確認しなければいけません。
理解度を把握するために分かっているところと分かっていないところの整理をします。
そして分かっていないところ、自信がないところをどう補うかという形で理解を深めていきます。
この「知識を整理する」ということが重要なポイント。
知識を整理すること=自分のスキルの棚卸することになります。
この棚卸をすることで足りてない知識を発見するだけでなく、知識や経験の裏付けを確認できます。
つまり、この裏付けを認識することでエンジニアとしての自信につながるのです。

このようなメリットもあるので講義のサポートをするチューター役には前年度の新卒にお願いするようにしています。
毎日がむしゃらに仕事をしていて自分の知識やスキルの棚卸をする機会なんて殆どないですよね?

実際に講師やチューターを担当したエンジニアからは「自身の勉強になった」だけでなく「知識の整理が出来て良かった」という声を聞いています。
人に教えることで自身のスキルを見直し、自信をつけることも出来るのです。

おわりに

研修を通して新卒の皆様にはエンジニアとしての軸を見つけて頂き、社員の皆様にはエンジニアとしての成長の手助けが出来ればうれしいなと考えております。

そんなミクシィの技術研修を受けてみたいと思ったアナタ、ぜひコチラからエントリーしてみてくださいね。

こんにちは。坂本です。

今年も、就職活動の時期ですね。
弊社ではそんな中、mixi Scrap Challengeという学生の方々向けのセキュリティイベントを開催させていただいております。

現在、こちらで第2回目の参加者を募集中です。
今回の記事では2011/12/4に行われた第1回の様子を紹介させていただこうと思います。

mixi Scrap Challenge?

普段WEBアプリケーションを開発している学生の皆さんにセキュリティに関する意識をより高めてもらおうというイベントです。
守るためには攻める側がどういう方法で攻撃してくるか?どんなことを考えてるのか?を知ることも大切です。
そこでmixi Scrap Challengeでは運営スタッフがmixi風の特設サイトを用意して、参加者に脆弱性を探して攻撃もらいます。
その後スタッフがサイトがどう対策すれば脆弱性を防げるかを解説するという内容になっています。

セキュリティに関する講義

まず最初に、弊社技術部長からセキュリティに関する講義を行いました。
一般的なセキュリティの概論、実際に起こったCSSXSSを始めとする数例の実例解説、弊社におけるセキュリティ向上のための取り組みなど
幅広く解説させていただきました。

講義を聞く参加者の皆さん

攻撃してみよう

実際にサイトを守るためには、具体的な攻撃手法についても知る必要があります。
イベントでは攻撃者の気持ちを知ってもらうため、mixiクローンな特設サイトを参加者の皆さんに攻撃してもらいました。
(※本物のmixiではなく、あくまでも今回の攻略実習用に構築された非公開のmixiクローンSNSサイトです)
チーム対抗形式にして、1チーム2-3人で用意された問題通りの攻撃を達成すればpointを獲得というルールで進行しました。

問題や、スコアランキングを確認するためのDashboardページ。

問題の解き方

例えばXSSの問題では参加者はmixiクローンなサイト内の脆弱性を突いた攻撃用URLを作り、それを「鴨 ねぎ男」という運営側のアカウントにメッセージで送信します。
( ねぎ男は外部サイトは警戒してリンクを踏みませんが、サイト内のドメインのリンクなら必ず見てくれます。)
ねぎ男が、URLを開いて問題通りの攻撃が実行されれば問題クリアとなりポイントが獲得できます。

丸一日攻撃用URLを送られ続け、そして踏み続けた、鴨ねぎ男

イベントの様子

一定時間ごとに各問題のヒントが追加で表示されて、それに伴い得られるpointも下がっていくルールにしました。
ヒントが出る前の難しい問題を解いて高ポイントを狙うチーム、作業分担して並列作業で進めるチームと、チームごとに様々な戦略をとってるようでした。

真剣に取り組む参加者の皆さん

イベントは、前半と後半にわかれていたのですが後半で一気に追い上げるチームがあったり、終盤で逆転があったりとドラマの多い展開でした。

届き続ける怪しげなメッセージ

鴨ねぎ男に届くメッセージも、もはや送信名からして怪しげなメッセージばっかりになっていました。
※なお、前述したとおりイベントに使ったサイトは本物のmixiではなく、あくまでも今回の攻略実習用に構築された、非公開のmixiクローンSNSサイトです。

最終的に優勝したチームは全10問中9問クリアという、運営側で想定していた以上のスコアでした。
残り1問も、あと数秒あれば解けていた、とのことでした。
なお、優勝したのは唯一メンバーが2人だけのチームでした。(他は3人で1チーム)

脆弱性の塞ぎ方の解説

時間いっぱい攻撃してもらった後は、各脆弱性について、どうすれば防げたか?についての解説を行いました。
セキュアなサイトを作るには、「ユーザ(クライアント)からの入力を信用しない」というのが、基本的な原則となります。
今回のイベントではXSS関係の問題が多く、どのように入力をエスケープするべきだったかという説明が中心になりました。

運営裏話(と失敗談)

今回のイベントで攻撃してもらったサイトは実際のmixiのソースコードをforkして、そこに脆弱性を用意しました。
サービスを起動するためには数十のDBが必要なのですが、イベント用のDBサーバに最低限必要なものを展開していきました。
DBの他にもメール連動部分や、ガラケー認証部分、課金関係の処理などをMockしたりSkipしたりする必要があり独立したmixiを1つ作るのは当初の想定以上に時間がかかりました。

と、準備に時間をかけたつもりでいたものの……
実は第一回では参加者の皆さんにサイトにアクセスしていただいた瞬間、イベント用のWebサーバの負荷が高くなりアクセス不能になりました。
このため当初はWebサーバを1台しか用意してなかったのですが、予備の4台にイベント用のコードをデプロイして急遽、負荷を分散させました。
「攻撃する前に壊れた……!」などの声をいただきながら、なんとか30分ほどで無事アクセスを分散させて安定稼働させることができました。

第1回の参加者のみなさん、こちらの不手際で進行がスムーズにいかず、申し訳ありませんでした。
第2回では、このようなことが起こらないようにあらかじめ対策した上でイベントを進行したいと思います。

Togetter

より、当日の雰囲気がわかるようなTogetterでのまとめも上がっておりますので、興味を持たれた方は読んで頂ければと思います。
株式会社ミクシィ 学生向けエンジニアイベント “Scrap challenge 2011″の参加者tweetまとめ

まとめ

弊社では、このようなイベントは初めてで、不慣れな点もあり不手際もありましたがなんとか無事、終えることができました。
イベント終了後、懇親会で参加者の方々からは、「これから、もっとセキュリティについて勉強したい」、「自分で作ってるサイトの安全性を見なおしてみたい」などの感想をいただけました。
参加者の皆さんのアプリケーション作りに、イベントの内容が少しでも役立てば嬉しいです。

第一回は想定よりとても多くの方々から応募していただき、大盛況で終えることが出来ました。
現在、第2回目の参加者を募集しておりますので、この記事を読んで興味をもっていただけた学生の方は
下記のサイトから是非、参加申し込みをしていただければと思います!

http://mixi.co.jp/event/challenge-2012/

全国20人の ECMA セオリストのみなさま、おつかれさまです。大形尚弘です。

ついに Dmitry 先生の ES3 シリーズも最終章となりました。この後に ES5 シリーズが5章続きますが、それらは基本的に今シリーズの補足として書かれたものですので、ここまでお読みいただいたみなさまは、ほぼ ECMAScript の理論的側面を理解したと言えます。

もしそうでない部分があったとしても、実際に ECMAScript の仕様書をご覧いただければ、これまでとは全く理解度が違っていて、あっという間に足りない知識を補足できると思います。端的に、「仕様が読める」ようになっているはずです。

ES5 であれば、本来 PDF である仕様書を、有志の方が es5.github.com にて「注釈付きの」 HTML 形式で公開し、頻繁に更新されています。注釈の一つはもちろん我らが Dmitry 先生の ES シリーズです。ここまでお読みいただいたみなさまには、仕様書で初めて目にする言葉や概念はほんの少しです。是非、おすすめします。

それでは ES3 シリーズ最終章『評価戦略』をどうぞ。またいずれ ES5 シリーズの翻訳でお会いできればと思います。

詳細 ECMA-262-3 シリーズ

第8章 評価戦略

目次

  1. はじめに
  2. 概説
    1. 値渡し
    2. 参照渡し
    3. 共有渡し
      1. 値渡しの特別なケースとしての共有渡し
      2. 共有渡しとポインタ
  3. ECMAScript での実装
    1. 各用語について
  4. 結論
  5. 参考文献

はじめに

この短い章では、 ECMAScript における、関数に引き渡す引数の戦略について検討します。

一般的に、コンピュータサイエンスにおけるこの部分は、評価戦略と呼ばれます。つまり、プログラミング言語においてあるを評価し、演算するための規則の集合です。関数に引き渡す引数の戦略とは、その特定のケースです。

この章の動機は、あるフォーラムにおける同様のトピックでした。そこでの議論の結果私たちは、 ECMAScript における引数の引き渡し戦略について、ほぼ完全な表現を記述するに至りました。フォーラムや議論の中で用いることのできる、相応の定義も与えることになったのです。

多くのプログラマは、 JavaScript (あるいは常用する言語においても同様)におけるオブジェクトは参照によって関数に与えられ、一方プリミティブ型の値については値によって与えられると強く考えています。さらに、こうした「事実」はさまざまな記事、フォーラムでの議論、さらには JavaScript に関する書籍においても見ることができます。しかしながら、この用語の使い方がどれほど正確で、この説明が本当に正しいものなのか(さらに重要なことは、この理解がどれだけ正しいものなのか)、この章で議論していきたいと思います。

概説

簡単に申し上げると、一般的に評価戦略には二つの種類があります。正格、つまり実引数が関数への適用の前に計算されるものと、非正格、原則的に実引数の計算は必要になったときに都度実行されるもの(いわゆる「遅延」計算です)があります。

しかしここでは、私たちは ECMAScript の観点から、その理解に重要な、関数への引数の引き渡しに関する基本的な戦略について検討します。

始めに、 ECMAScript では、そのほかの多くの言語と同じように(例えば C 、 Java 、 Python 、 Ruby 等)、正格な引数引き渡し戦略が用いられています。

また、引数が評価される順序も重要です。 ECMAScript では左から右です。他の言語や実装では、この逆の評価順序(右から左)も使われる場合があります。

正格な引き渡し戦略はまた、その中でいくつかの戦略に細かく分けられています。そのうち最も重要ないくつかについては、この後で議論していきます。

以下で述べる全ての戦略が ECMAScript で採用されているわけではありませんので、例えば論理的な戦略を具体的に説明するために、 Pascal 様のシンタックスの抽象的な擬似コードを用いることにします。

値渡し

この種の戦略は、多くのプログラマに良くなじみのあるものです。ここでは、実引数の値は呼び出し元によって渡される対象の値の複製です。関数内部での仮引数への変更は、渡された対象の外側での状態には影響しません。一般的に、外部の対象が複製される際には新しいメモリ領域の割り当てが行われ(この議論では具体的な実装はさほど重要ではありません。スタックでも、動的メモリでもかまいません)、まさにこのメモリ上の新しい領域の値が、関数の内部で使われます。

bar = 10
 
procedure foo(barArg):
  barArg = 20;
end
 
foo(bar)
 
// foo 内部の変更は、
// 外側の bar には影響しない
print(bar) // 10

しかしながらこの戦略は、関数への実引数がプリミティブ値で無く、複雑な構造、オブジェクトである場合に、大きなパフォーマンスの問題を抱えてしまいます。これはまさに、 C/C++ で構造体が関数に値渡しされるとき、完全に複製されるときに起こっていることです。

以下の各評価戦略の説明で用いる、一般的なコード例をここに載せておきます。ある抽象的な手続きが、二つの引数を受け取ります。一つはオブジェクトの値、もう一つはブーリアンフラグで、このフラグによってオブジェクトの値を完全に(新しい値を代入して)変更するか、単にオブジェクトのプロパティを変更するのかを決定します。

bar = {
  x: 10,
  y: 20
}
 
procedure foo(barArg, isFullChange):
 
  if isFullChange:
    barArg = {z: 1, q: 2}
    exit
  end
 
  barArg.x = 100
  barArg.y = 200
 
end
 
foo(bar)
 
// 値渡しの戦略では、
// 外側のオブジェクトは変更されない
print(bar) // {x: 10, y: 20}
 
// 全体の変更(新しい値の代入)
// においても同様
foo(bar, true)
 
// 何の変更もされていない
print(bar) // {x: 10, y: 20} 、 {z: 1, q: 2} ではない

参照渡し

次に、参照渡しの評価戦略は(これもまたよく知られたものです)、値の複製ではなく、対象への暗黙の参照を受け取ります。つまり、外側から渡される対象の、直接のメモリアドレスです。関数の内側での仮引数への変更は(新しい値の代入であれ、プロパティの変更であれ)、全て外側の対象にも影響します。なぜなら、まさにこの対象のアドレスが仮引数に結びついているからです。つまり、この場合の実引数とは、外側から渡される対象のエイリアスのようなものなのです。

擬似コードです。

// 手続き foo の定義は前の例を参照
 
// 同じオブジェクトを用意して
bar = {
  x: 10,
  y: 20
}
 
// 参照渡しの場合の
// 手続き foo の結果は
// 次の通り
 
foo(bar)
 
// オブジェクトのプロパティ値は変更されている
print(bar) // {x: 100, y: 200}
 
// 新しい値の代入も
// オブジェクトに影響する
foo(bar, true)
 
// bar は新しいオブジェクトを参照している
print(bar) // {z: 1, q: 2}

この戦略では、複雑なオブジェクトを効率的に渡すことができます。例えば、非常に大量のプロパティを持った構造体を渡す場合などに有効です。

共有渡し

最初の二つの戦略が多くのプログラマのよく知るところだったとしても、この戦略(あるいはより正確には、ここで議論する用語)は、それほど広く認知されていません。しかし、すぐに分かるとおり、まさにこれが、 ECMAScript での引数渡し戦略において重要な役割を果たしています。

この戦略の別の呼び方としては、「オブジェクト渡し」や「オブジェクト共有渡し」などがあります。

「共有渡し」の戦略は、 Barbara Liskov によって、 CLU プログラミング言語のために1974年に提案され、名付けられました。

この戦略の中心となるポイントは、関数は対象への参照の複製を受け取るという点です。この参照の複製が仮引数に結びつき、その値となるのです。

この場合、参照という考え方が現れてはいますが、参照渡しとして扱われるべきではありません(多くの人が誤解しているポイントです)。なぜなら実引数の値は直接のエイリアス(外側の対象のアドレスそのもの)ではなくアドレスの複製だからです。

参照渡しとの大きな違いは、関数内部での仮引数への新しい値の代入は、外側の対象には影響しない(参照渡しの場合は影響します)ということです。しかしながら仮引数はアドレスの複製を保持しており、外側にあるものと同じ対象にアクセスすることが可能ですので(つまり外側から渡された対象が、値渡しのように完全に複製されているわけではない)、関数ローカルの仮引数のプロパティへの変更は、外側のオブジェクトに影響します

// 手続き foo の定義は前の例を参照
 
// もう一度、同じオブジェクトを用意し
bar = {
  x: 10,
  y: 20
}
 
// 共有渡しの場合、
// 次の通り
// オブジェクトに影響する
 
foo(bar)
 
// オブジェクトのプロパティ値は変更されている
print(bar) // {x: 100, y: 200}
 
// しかしオブジェクトの完全な変更は
// 反映されない
foo(bar, true)
 
// 依然として一つ前の関数呼び出しの前と同じ状態
print(bar) // {x: 100, y: 200}

この戦略は、言語の大部分において、プリミティブ値ではなくオブジェクトを対象に演算を行うということを前提にしています。

値渡しの特別なケースとしての共有渡し

共有渡しの戦略は多くの言語で採用されています。例えば Java 、 ECMAScript 、 Python 、 Ruby 、 Visual Basic 等です。

Python コミュニティではまさにこの用語、共有渡しが使われています。他の言語に関してはまた別の用語が用いられたりしますが、命名上他の戦略と混同される原因ともなっています。

多くの場合、例えば Java や ECMAScript 、 Visual Basic においては、この戦略は参照の複製という値を意味する、値渡しの特別なケースとも呼ばれます。

この場合、関数の内部において、仮引数の名前に新しい(アドレス)を束縛しても、外側の対象には影響を及ぼしません。

こうした用語の交錯は、実に(この問題を深く検証してみなければ)誤った取り扱い( JavaScript において関数にどのようにオブジェクトが渡されるのかというフォーラム中の議論)につながります。

しかし理論的には、これはまさに特定の値、つまりアドレスの複製を扱う値渡しの特別なケースであり、用語の規則を破っているわけでは無いのです。

Ruby では、この戦略が参照渡しと名付けられています。しかしここでも、(値渡しのように)大きな構造体の複製が渡されるわけではなく、対象へのオリジナルの参照を取り扱うわけでもないのです。結果として、こうした用語の錯綜が混乱につながってしまいます。

この理論は、参照渡しの特殊なケースを説明するものではなく、値渡しのある特定のケースを説明するものです

しかしながら、こうした技術( Java 、 ECMAScript 、 Python 、 Ruby 等)で、それぞれに独自の用語が用いられていることは認識しておく必要があるでしょう。ただし実際に、それらは全て、理論的には共有渡しと名付けられるものです。

共有渡しとポインタ

C/C++ に関して言うと、この戦略は論理的にポインタ渡しに似ていますが、一点大きな違いがありますポインタ渡しの場合、ポインタをデリファレンスし、対象を完全に変更することができるのです。しかし一般的に、値(アドレス)をポインタに代入することは、新しいメモリブロックにポインタを束縛するということです(ポインタがその代入以前に参照していたメモリブロックはそのまま変更されず残ります)。そしてポインタを経由して参照する対象のプロパティの変更は、外側の対象にも影響するのです。

したがって、ポインタを用いて表現するなら、共有渡しとはアドレスの値渡しであり、まさにこのアドレスとはポインタがそれに当たります。この場合、共有渡しは代入においてポインタ(しかしデリファレンスできない)のようにふるまい、プロパティ変更においては参照(デリファレンス操作を必要としない)のようにふるまうある種の「シンタックスシュガー」であると言えます。時折これは「安全なポインタ」とも呼ばれます。

しかし、 C/C++ はまた、対象のプロパティを、明確なポインタのデリファレンス無しに参照できる、特別な「シンタックスシュガー」を用意しています。

(*obj).x ではなく obj->x

より C++ に近づいて考えてみると、この考え方は「スマートポインタ」の実装の一つに見ることができます。例えば boost::shared_ptr は、この用途のために代入演算子とコピーコンストラクタをオーバーロードし、対象への参照カウントを使い、 GC によって対象を削除します。このデータ型は、まるでこの戦略と同様の名前、shared_ptr を持ちます。

ECMAScript での実装

さて、これまでの通り、私たちは ECMAScript で用いられている、ある対象を引数として関数に引き渡す評価戦略について知ることができました。共有渡しです。引数のプロパティの変更は外側に影響し、新しい値の代入は、外側の対象には影響しません。

しかし前述したように、 ECMAScript デベロッパの中では、この戦略に対するローカルな用語も使われています。それは「値が参照の複製である場合の値渡し」と呼ばれるものでした。

JavaScript の発明者である Brendan Eich もまた、参照の複製(アドレスの複製)が渡されると述べています。あるフォーラムの議論の中には、 ECMAScript の開発者がこれを値渡しと呼んでいる場面もありました

より正確に言えば、このふるまいは単純な代入を検討することで理解できます。二つの異なるオブジェクトがあり、しかしそれらは同じ値、つまり同じアドレスの複製をそれぞれが持つという場合です。

ECMAScript のコードで見てみましょう。

var foo = {x: 10, y: 20};
var bar = foo;
 
alert(bar === foo); // true
 
bar.x = 100;
bar.y = 200;
 
alert([foo.x, foo.y]); // [100, 200]

つまり、二つの識別子(名前束縛)が、メモリ上の同じ対象に束縛されているということです。対象を共有しているのです。

foo value: addr(0xFF) => {x: 100, y: 200} (address 0xFF) <= bar value: addr(0xFF)

そして、代入は単に識別子を新しい対象(新しいアドレス)に束縛し、(それ以前に)束縛されていた対象(古いアドレス)には影響しません。これがもし参照の場合では(メモリアドレスの参照先そのもののを書き換えますので)、影響してしまいます。

bar = {z: 1, q: 2};
 
alert([foo.x, foo.y]); // [100, 200] - 何も変わらない
alert([bar.z, bar.q]); // [1, 2] - しかし bar は新しい対象を参照する

こうして foobar は異なる値、異なるアドレスを持つことになります。

foo value: addr(0xFF) => {x: 100, y: 200} (address 0xFF)
bar value: addr(0xFA) => {z: 1, q: 2} (address 0xFA)

繰り返しになりますが、こうした一連の手続きは全て、オブジェクト型の場合の変数値はアドレスでありオブジェクト構造そのものではないという事実によるものです。ある変数を他の変数に代入することは、その値参照を複製することであり、両方の変数はメモリ上の同じ場所を参照します。次に新しい値(新しいアドレス)の代入が行われると、これは古いアドレスから名前を解放し、新しいアドレスに束縛します。まさにこれが、重要な、参照渡し戦略との違いです。

さらに踏み込んで考えると、 ECMA-262 標準で与えられる抽象レベルのみを検討すれば、全てのアルゴリズムに現れるのは「値」という考え方だけです。そして「値」(そしてその種類、プリミティブ値であろうとオブジェクトであろうと)を渡す部分の実装は舞台裏に隠されています。この観点から、 ECMAScript について抽象的に議論するなら、正確かつ厳密な意味で、「値」のみが存在し、したがって値渡しのみであるとも言えるのです。

しかし誤解を避けるために(なぜ外部の対象のプロパティが関数内部で変更されるのか?)、実装レベルに降りて検討し、共有渡しあるいは「デリファレンスや対象を完全に変更することのできない、しかし対象のプロパティを変更することのできる、ポインタ渡し」といったように議論する必要があったわけです。

各用語について

それでは、 ECMAScript におけるこの問題に関して、正確な用語の定義を与えましょう。

引き渡される値がアドレスの複製であり、値渡しの特別なケースであると明確に表現できるので、「値渡し」であり得ます。この見地からは例外オブジェクトを除く ECMAScript 上の全てが値渡しであると言えますし、 ECMAScript 仕様の抽象レベルにおいては実際にこの定義で満たされます(訳注:それでは例外オブジェクトが「何渡し」にあたるのかは、訳者は理解できておりません。どなたかおわかりの方、お教えいただけると嬉しいです)。

あるいは、この場合の戦略について特別に、「共有渡し」であるとも言えます。これは正確に伝統的な値渡し参照渡しの区別を明確にします。この場合は、引き渡される値の種類を二つに分類し、プリミティブ値は値渡し、オブジェクトは共有渡しであると表現できます。

「オブジェクトは参照渡しによって関数に参渡される」という表現は、 ECMAScript においては正式には誤りなのです

結論

この章が、評価戦略の詳細、そして ECMAScript に関係する特徴を、包括的に理解する手助けになれば幸いです。いつものように、コメントでみなさまの質問にお答えします(訳注:これでシリーズ一旦完結となります。次の ES5 シリーズに進む前に、個人ブログに全て転載をいたしますので、質問等はそちらにお寄せいただければ分かる範囲でお答えしたいと思います。転載が遅くなって申し訳ありません)。

参考文献

英語版翻訳: Dmitry A. Soshnikov[英語版].
英語版公開日時: 2010-04-10

オリジナルロシア語版: Dmitry A. Soshnikov [ロシア語版]
協力: Zeroglif
オリジナルロシア語版公開日時: 2009-08-11

本シリーズはすべて英語版からの訳出です。

こんにちは。大形尚弘です。

さて、ついに OOP の話も ECMAScript の実装に移ります。乗りに乗った Dmitry 先生が、前節を越えるボリュームを持って、日本全国30人(はてなブックマークの平均による導出)の ECMAScript ファンの皆さんに襲いかかります。

正直に言って、私はもう10年以上 JScript 、 JavaScript 、 ActionScript に関わってきましたが、プロトタイプの話は、内部 [[Prototype]] プロパティが「どういうわけか」コンストラクタ関数の prototype プロパティと同じ呼び名を与えられていることを発端として、常に混乱を伴ってきました。一度理解したつもりでも、すぐに忘れてしまい、わけが分からなくなってしまうのです。

それはどういうことかというと、一度もしっかり理解していなかったからです。

この節を読めば、それが理解できます。おそらく、もう二度と忘れないと思います。

しかし、ここをご覧のみなさまにもその理解が訪れるかどうかは、ひとえに、この長大な節を読み通すことができるかということにかかっています。

どうかごゆっくり、落ち着いた、集中できる、邪魔の入らない環境で、じっくりご覧下さいますようお願い申し上げます。

詳細 ECMA-262-3 シリーズ

第7章2節 OOP: ECMAScript での実装

目次

  1. はじめに
  2. ECMAScript の OOP 実装
    1. データ型
      1. プリミティブ値型
      2. オブジェクト型
        1. 動的な特質
        2. ビルトイン、ネイティブ、ホストオブジェクト
        3. Boolean 、 String 、 Number オブジェクト
        4. リテラル記法
          1. 正規表現リテラルと RegExp オブジェクト
        5. 連想配列?
        6. 型変換
        7. プロパティ属性
        8. 内部プロパティと内部メソッド
    2. コンストラクタ
      1. オブジェクト生成のアルゴリズム
    3. プロトタイプ
      1. constructor プロパティ
      2. 明示的な prototype と暗黙の [[Prototype]] プロパティ
      3. 非標準の __proto__ プロパティ
      4. オブジェクトはコンストラクタから独立している
      5. insanceof 演算子の特徴
      6. メソッドと共有プロパティのためのストレージとしてのプロトタイプ
    4. プロパティの読み書き
      1. [[Get]] メソッド
      2. [[Put]] メソッド
      3. プロパティアクセサ
    5. 継承
      1. プロトタイプチェーン
  3. 結論
  4. 参考文献

はじめに

この節は、 ECMAScript におけるオブジェクト指向プログラミングに関する記事の第二部です。第一部ではオブジェクト指向の概論を、 ECMAScript に対応させながら検討しました。今節では、前節で定義した用語を積極的に使っていきますので、こちらをお読みになる前に、必要であれば第一部をお読みになることをお勧めします。第一部はこちら、『第7章1節 OOP: 概説』になります。

ECMAScript の OOP 実装

主要な概論を検討する長い道を通り抜け、私たちはやっと ECMAScript そのものへとたどり着きました。今や私たちは、 ECMAScript の OOP アプローチについて理解しています。再度正確な定義を与えましょう。

ECMAScript は、プロトタイプを元にした委譲による継承をサポートする、オブジェクト指向プログラミング言語です。

これからはまず、データ型の検討から分析を始めます。最初に、 ECMAScript がプリミティブ値オブジェクトを区別しているという事実に気をつける必要があります。つまり、「 JavaScript 上の全ての要素はオブジェクトです」という、いくつかの記事で掲げられるフレーズは誤りである(完全ではない)ことがわかります。プリミティブ値とは、これから詳細を検討していくというものの、データとしての状態に関するものです。

データ型

ECMAScript は動的で、ダックタイピングによる弱い型付けの、自動型変換のある言語ではありますが、それでもやはり、いくつかのデータ型を持ちます。つまり、ある瞬間において、オブジェクトは一つの具体的な型に属するわけです。

標準では9つの型が定義されており、うち6つは ECMAScript プログラムの中で直接参照することができます。

  • Undefined
  • Null
  • Boolean
  • String
  • Number
  • Object

この他の3つの型は、実装内部のレベルで参照できるもので( ECMAScript 上のオブジェクトでこのような型を持つものはありません)、いつくかの演算の振る舞いを説明するために仕様によって用いられたり、中間計算値の値を保存するなどといった用途に使われます。これらは以下の3つです。

  • Reference
  • List
  • Completion

これらを簡単に見てみると、 Referene 型は deletetypeof といった演算子、 this 値などの説明のために用いられ、 base オブジェクトプロパティ名によって構成されます。 List 型は実引数のリスト( new 式や関数呼び出しの際の)のふるまいを表現します。同じく Completion 型は breakcontinuereturnthrow 文のふるまいを説明します。

訳注:これら3つの型は、 ECMA-263-5 では「仕様型( Specification Type )」という名前を与えられて区別されています。それぞれ Reference 仕様型、 List 仕様型、 Completion 仕様型となります。

プリミティブ値型

ECMAScript プログラムによって使われる6つの型に戻りましょう。最初の5つ、 UndefinedNullBooleanStringNumber は、プリミティブ値の型です。

プリミティブ値の例です。

var a = undefined;
var b = null;
var c = true;
var d = 'test';
var e = 10;

これらの値は、低いレベルの実装中において直接表現されます。これらはオブジェクトではありません。プロトタイプも、コンストラクタも持ちません。

typeof 演算子は、適切に理解しないとあまり直観的では無いかも知れません。顕著な例は null 値の扱いです。 typeof 演算子に null 値が与えられると、null 値の型は Null と定められている事実にもかかわらず、結果は "object" となります。

alert(typeof null); // "object"

この理由は、 typeof 演算子は単に、標準に定められた表に照らし合わせて取り出された値を返すものだからです。その表には単にこうあります。null 値には、文字列 "object" が返されるべきである」と。

仕様はこれをはっきりとさせていませんが、 Brendan Eich ( JavaScript 発明者)は、 undefined に対し null が、主にオブジェクトが現れる位置で使われ、つまり本質的にオブジェクトに密接に関わるものであると述べています(オブジェクトへの「空の」参照を意味し、将来的な使用のために留保しています)。しかし、いくつかの草案において、この「現象」が一般的なバグであるとする文書もあります。また、このバグは Brendan Eich も参加するバグトラッキングシステムにも登録されていました。とはいえ、結果として、 typeof null はそのまま残されることが決まりました。つまり、 ECMA-262-3 標準では null の型を Null と定めているにもかかわらず、 “object” が返されます。

オブジェクト型

次に、オブジェクト型は( Object コンストラクタと混同しないように!。ここでは、抽象型である Object について話しています)、 ECMAScript のオブジェクトを表現する唯一の型です。

オブジェクトとは、順序づけの無い、キーと値のペアのコレクションです。

オブジェクトのキーの部分はプロパティと呼ばれます。プロパティはプリミティブ値または他のオブジェクトの入れ物です。プロパティがその値として関数を保管する場合は、メソッドと呼ばれます。

例です。

var x = { // オブジェクト "x" は3つのプロパティ: a, b, c を持つ
  a: 10, // プリミティブ値
  b: {z: 100}, // プロパティ z を持つオブジェクト "b"
  c: function () { // 関数(メソッド)
    alert('method x.c');
  }
};
 
alert(x.a); // 10
alert(x.b); // [object Object]
alert(x.b.z); // 100
x.c(); // 'method x.c'
動的な特質

第7章1節で触れたとおり、 ECMAScript におけるオブジェクトは完全に動的です。これは、プログラム実行中のどの時点においても、オブジェクトのプロパティの追加、変更、削除が可能だということを意味します。

例です。

var foo = {x: 10};
 
// 新しいプロパティを追加
foo.y = 20;
console.log(foo); // {x: 10, y: 20}
 
// プロパティの値を関数に変更
foo.x = function () {
  console.log('foo.x');
};
 
foo.x(); // 'foo.x'
 
// プロパティを削除
delete foo.x;
console.log(foo); // {y: 20}

中には変更できないプロパティ( read-only プロパティ)や、削除できないプロパティ( non-configurable プロパティ)があります。これらのケースについては、プロパティ属性の項にて簡単に検討します。

ES5 では、新しいプロパティで拡張できず、プロパティの変更や削除もできない static オブジェクトを標準化しています。これらは frozen オブジェクトとも呼ばれ、 Object.freeze(o) メソッドを適用することによって得られます。

var foo = {x: 10};
 
// オブジェクトを freeze する
Object.freeze(foo);
console.log(Object.isFrozen(foo)); // true
 
// 変更できない
foo.x = 100;
 
// 拡張できない
foo.y = 200;
 
// 削除できない
delete foo.x;
 
console.log(foo); // {x: 10}

また、 Object.preventExtensions(o) メソッドを使って拡張を防ぎ、 Object.defineProperty(o) メソッドによって特定の属性を操作することができます。

var foo = {x : 10};
 
Object.defineProperty(foo, "y", {
  value: 20,
  writable: false, // read-only(読み取り専用)
  configurable: false // non-configurable(再設定不可)
});
 
// 変更できない
foo.y = 200;
 
// 削除できない
delete foo.y; // false
 
// 拡張を防ぐ
Object.preventExtensions(foo);
console.log(Object.isExtensible(foo)); // false
 
// 新しいプロパティを追加できない
foo.z = 30;
 
console.log(foo); {x: 10, y: 20}

詳しくは、この章(未訳)を参照下さい。

ビルトイン、ネイティブ、ホストオブジェクト

仕様上では、ネイティブオブジェクトと、ビルトインオブジェクト、それにホストオブジェクトが区別されていることも、お伝えしておかないとなりません。

ビルトイン及びネイティブオブジェクトは、 ECMAScript 仕様によって定義され、その実装における差異はあまり重要ではありません。ネイティブオブジェクトは、 ECMAScrpt 実装によって与えられる全てのオブジェクトがそれに当たります(そのうちのいくつかはビルトインであり、その他はプログラム実行中に生成されるもの、例えばユーザの定義するオブジェクトです)。

ビルトインオブジェクトはネイティブオブジェクトの派生型(部分集合)であり、プログラムが開始されるより前に先だって ECMASCript にビルトインされるものです(例えば、 parseIntMath オブジェクトなどです)。

ホストオブジェクトは、全てホスト環境、大抵はブラウザによって与えられ(訳注:最近では node.js 等も)、 windowalert といったものを含みます。

注意が必要なのは、ホストオブジェクトは時に ES 自身によって実装され、完全に仕様のセマンティック(意味的機序)に合致する場合があるということです。この観点から、主に理論上の側面ながら、それらは(非公式にネイティブ・ホストオブジェクトとも呼べるものです。しかしながら仕様では、「ネイティブ・ホスト」という考え方は定義されていません。

訳注:こうした分類はあくまで理論上のもので、例え同じオブジェクトでも、実装や実装のバージョンによって変わり得ます。 XMLHttpRequest オブジェクトは、初期バージョンの IE では当然ホストオブジェクトですが、 IE9 においては JavaScript エンジンによって実装されるネイティブ・ホストオブジェクトと考えることができます。

Boolean 、 String 、 Number オブジェクト

一部のプリミティブについては、仕様は特別なラッパーオブジェクトを定義しています。以下の3つです。

  • Boolean オブジェクト
  • String オブジェクト
  • Number オブジェクト

これらのオブジェクトは、それぞれ対応するビルトインのコンストラクタによって生成され、内部プロパティの一つとしてプリミティブ値を持ちます。こうしたオブジェクト表現はプリミティブ値に変換することができ、またその逆も可能です。

それぞれのプリミティブ型に対応するオブジェクト値の例です。

var c = new Boolean(true);
var d = new String('test');
var e = new Number(10);
 
// プリミティブに変換
// 変換器: ToPrimitive
// "new" キーワード無しで、関数として適用する
c = Boolean(c);
d = String(d);
e = Number(e);
 
// オブジェクトに戻す
// 変換器: ToObject
c = Object(c);
d = Object(d);
e = Object(e);

加えて、特別なビルトインコンストラクタによって生成されるオブジェクトもあります。 Function (関数オブジェクトコンストラクタ)、 Array (配列コンストラクタ)、 RegExp (正規表現コンストラクタ)、 Math (算術モジュール)、 Date (日付のコンストラクタ)などです。これらのオブジェクトは全て Object 型の値であり、それらの区別は後に検討する内部プロパティによって行われます。

リテラル記法

3つのオブジェクト値、 object (オブジェクト)array (配列)regular expression(正規表現)には、それぞれオブジェクト初期化子配列初期化子正規表現リテラルと呼ばれる短縮記法があります。

// new Array(1, 2, 3); または
// array = new Array();
// array[0] = 1;
// array[1] = 2;
// array[2] = 3;
// と同等
var array = [1, 2, 3];
 
// var object = new Object();
// object.a = 1;
// object.b = 2;
// object.c = 3;
// と同等
var object = {a: 1, b: 2, c: 3};
 
// new RegExp("^\\d+$", "g"); と同等
var re = /^\d+$/g;

ObjectArrayRegExp といった名前結合に対し新しいオブジェクトを再代入する場合は注意が必要です。その代入の後にリテラル記法を使用した場合の機序は実装によって異なり得るからです。例えば、現行の Rhino 実装や、古い SpiderMonkey 1.7 では、対応するリテラル記法はそれぞれ新しく代入されたコンストラクタ名の new 文による値のオブジェクトを生成します。他の実装(現行の Spider/TraceMonkey )では、コンストラクタ名が新しいオブジェクトに再束縛されても、リテラル記法の機序は変更されません。

var getClass = Object.prototype.toString;
 
Object = Number;
 
var foo = new Object;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"
 
var bar = {};
 
// Rhino 、 SpiderMonkey 1.7 では ― 0, "[object Number]"
// その他では:依然として "[object Object]", "[object Object]"
alert([bar, getClass.call(bar)]);
 
// Array という名前も同様
Array = Number;
 
foo = new Array;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"
 
bar = [];
 
// Rhino 、 SpiderMonkey 1.7 では ― 0, "[object Number]"
// その他では:依然として "", "[object Object]"
alert([bar, getClass.call(bar)]);
 
// しかし RegExp に関しては、
// テストした全ての実装にて、
// リテラルの機序は変更されなかった
 
RegExp = Number;
 
foo = new RegExp;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"
 
bar = /(?!)/g;
alert([bar, getClass.call(bar)]); // /(?!)/g, "[object RegExp]"

正規表現リテラルと RegExp オブジェクト

しかし、ここでご注意いただきたいのは、 ES3 において最後の二つ、正規表現が意味的に同じであるというケースにおいても、実は異なる点があるということです。ある一つの正規表現リテラルが、パース時に生成されるたった一つのインスタンスのみを生成しこれが再利用される一方、 RegExp コンストラクタは常に新しいオブジェクトを生成します。これは例えば正規表現オブジェクトの lastIndex プロパティを使用する際に問題となり得ます。正規表現テストが上手く動かない場合があるのです。

for (var k = 0; k < 4; k++) {
  var re = /ecma/g;
  alert(re.lastIndex); // 0, 4, 0, 4
  alert(re.test("ecmascript")); // true, false, true, false
}
 
// に対し
 
for (var k = 0; k < 4; k++) {
  var re = new RegExp("ecma", "g");
  alert(re.lastIndex); // 0, 0, 0, 0
  alert(re.test("ecmascript")); // true, true, true, true
}

ES5 ではこの問題は解決され、正規表現リテラルが常に新しいオブジェクトを生成します(訳注:訳者の手元の IE 環境では、バージョン 6 で既にこの問題は解決しているようです。ただし、ループ中で正規表現リテラルを使用する際は、念のためご自身でご確認ください)

連想配列?

しばしば、いくつかの記事や議論の中で、 JavaScript のオブジェクト(そして大抵は宣言的な形式、オブジェクト初期化子 {} で生成されたもの)は、ハッシュテーブル、または単にハッシュ( Ruby や Perl からの用語)、あるいは連想配列( PHP からの用語)、辞書( Python からの用語)などと呼ばれます。

この用語の使用法は、具体的な実装に対する習慣によるものです。実際の処これらは十分に同等と言えるものであり、「キーバリュー」ペアのストレージとして見れば、論理的に全く「連想配列」「ハッシュテーブル」に対応しています。さらに言えば、ハッシュテーブルという抽象的なデータ型は実装のレベルでも利用できますし、また実際一般的に使われてもいます。

しかしながら、こうした用語は概念的な考え方を表現するものですが、一方 ECMAScript において実際には、これは技術的に正しいものであるとは言えません。前述したとおり、 ECMAScript はたった一つの オブジェクト型とその「派生型」を持ち、「キーバリュー」ペアのストレージという観点からは、これらの間に全く違いは無いのです。したがって、そこに個別の特別な用語(ハッシュやその他)はありません。なぜならどんなオブジェクトも、その内部プロパティに関わらず、これらのペアを保管できるからです。

var a = {x: 10};
a['y'] = 20;
a.z = 30;
 
var b = new Number(1);
b.x = 10;
b.y = 20;
b['z'] = 30;
 
var c = new Function('');
c.x = 10;
c.y = 20;
c['z'] = 30;
 
// などなど ― どんなオブジェクト「派生型」であれ

さらに、 ECMAScript のオブジェクトはその委譲の性質から空になり得ませんので、「ハッシュ」という用語も不適当になり得ます。

Object.prototype.x = 10;
 
var a = {}; // 「空の」「ハッシュ」を生成
 
alert(a["x"]); // 10 、しかし空では無かった
alert(a.toString); // 関数
 
a["y"] = 20; // 「ハッシュ」に新しいペアを追加
alert(a["y"]); // 20
 
Object.prototype.y = 20; // プロトタイプにプロパティを追加
 
delete a["y"]; // 削除
alert(a["y"]); // しかしキーバリューは依然として存在する ― 20

ES5 は、プロトタイプを持たないオブジェクトを生成する方法を標準化しました(未訳)。つまり、そのプロトタイプが null にセットされるのです。これは、 Object.create(null) メソッドを用いて行われます。この観点からは、こうしたオブジェクトは単なるハッシュテーブルであると言えます。

var aHashTable = Object.create(null);
console.log(aHashTable.toString); // undefined

そして、いくつかのプロパティは特別な getter/setter を持ち得ますので、さらに混乱の元になるかもしれません。

var a = new String("foo");
a['length'] = 10;
alert(a['length']); // 3

とは言え、「ハッシュ」がもし「プロトタイプ」を持つことができるとしても(例えば Ruby や Python では、クラスがハッシュオブジェクトから委譲できます)、 ECMAScript では、やはりこの用語は不適当になり得ます。なぜなら、プロパティアクセサの種類ドットまたは括弧記法)の間に意味的な違いは無いからです。

さらに、 ECMAScript では、「プロパティ」の考え方が、「キー」「配列インデックス」「メソッド」「プロパティ」に意味的には区別されていません。これらは全てプロパティであり、プロトタイプチェーンの探索に関する読み書きのアルゴリズムの一般的な法則に従います。

以下の Ruby の例では意味論的な違い、そして結果として用語がそれぞれ異なり得ることを見て取ることができます。

a = {}
a.class # ハッシュ
 
a.length # 0
 
# 新しい「キーバリュー」ペア
a['length'] = 10;
 
# しかしドット記法の意味は依然として異なり、
# 「キー」ではなく、
# 「プロパティ・メソッド」へのアクセスを意味する
 
a.length # 1
 
# そして括弧記法は、
# ハッシュの「キー」へのアクセスを提供する
 
a['length'] # 10
 
# 動的に、 Hash クラスを新しい
# プロパティ・メソッドで拡張し、
# 委譲によって既に生成されたオブジェクトにも
# 利用可能になる
 
class Hash
  def z
    100
  end
end
 
# 新しい「プロパティ」が利用可能
 
a.z # 100
 
# しかしこれは「キー」ではない
 
a['z'] # nil

ECMA-262-3 仕様は、「ハッシュ」(や同様のもの)の考え方を定義していません。しかし、(実装面・技術的ではない)論理的なデータ構造について言うのであれば、オブジェクトをそのように呼ぶことは可能です。

型変換

オブジェクトをプリミティブ値に変換するには、 valueOf メソッドが利用できます。既に触れたように、(ある型の)コンストラクタを関数として、 new 演算子無しで呼び出しても、オブジェクト型からプリミティブ値への変換を行えます。この変換には全くもって、暗黙的な valueOf メソッドの呼び出しが用いられています。

var a = new Number(1);
var primitiveA = Number(a); // 暗黙の "valueOf" 呼び出し
var alsoPrimitiveA = a.valueOf(); // 明示的な呼び出し
 
alert([
  typeof a, // "object"
  typeof primitiveA, // "number"
  typeof alsoPrimitiveA // "number"
]);

このメソッドによって、オブジェクトは(そのままの形で)さまざまな演算に参加することができます。加算の例を見てみましょう。

var a = new Number(1);
var b = new Number(2);
 
alert(a + b); // 3
 
// あるいはさらに
 
var c = {
  x: 10,
  y: 20,
  valueOf: function () {
    return this.x + this.y;
  }
};
 
var d = {
  x: 30,
  y: 40,
  // オブジェクト "c" が持つ
  // .valueOf の機能を拝借
  valueOf: c.valueOf
};
 
alert(c + d); // 100

valueOf メソッドの値は、デフォルトでは(オーバーライドされていなければ)オブジェクトの型によってさまざまです。 this 値を返すものもあれば(例えば Object.prototype.valueOf() )、計算された値( Date.prototype.valueOf() のように)ある日の UNIX 時間を返すものもあります。

var a = {};
alert(a.valueOf() === a); // true, "valueOf" が this 値を返した
 
var d = new Date();
alert(d.valueOf()); // 時間
alert(d.valueOf() === d.getTime()); // true

オブジェクトのプリミティブな表象にはもう一つあります。文字列としての現れ方です。これは toString メソッドが担当します。このメソッドに関しても、いくつかの演算にて自動的に適用されます。

var a = {
  valueOf: function () {
    return 100;
  },
  toString: function () {
    return '__test';
  }
};
 
// この演算では、
// toString メソッドが
// 自動で呼び出される
alert(a); // "__test"
 
// ここでは .valueOf() メソッドが呼ばれる
alert(a + 10); // 110
 
// しかし同じ演算でも
// valueOf メソッドが無ければ、
// toString メソッドで代替される
delete a.valueOf;
alert(a + 10); // "_test10"

Object.prototype に定義された toString メソッドには特別な意味があります。後に検討する、内部プロパティ [[Class]] の値を返すのです。

ToPrimitive 変換と同じく、逆に値をオブジェクト型に変換する ToObject 変換もあります。

明示的に ToObject を呼び出す一つの方法は、ビルトインの Object コンストラクタを関数として(いくつかの型に関しては new 演算子付きも可能)呼び出すことです。

var n = Object(1); // [object Number]
var s = Object('test'); // [object String]
 
// いくつかの型に関しては
// new 演算子付きで Object を呼び出しても
// ToObject 変換が可能
var b = new Object(true); // [object Boolean]
 
// ただし引数無しで呼び出すと
// 単なるオブジェクトを生成する
var o = new Object(); // [object Object]
 
// もし Object 関数への引数が
// 既にオブジェクト値だった場合、
// 単にそれをそのまま戻す
var a = [];
alert(a === new Object(a)); // true
alert(a === Object(a)); // true

ビルトインコンストラクタを呼び出す際の new 演算子の有無に関しては一般的なルールは無く、コンストラクタ次第です。例えば ArrayFunction コンストラクタは、コンストラクタとして( new 付きで)呼び出した際も、単なる関数として( new 無しで)呼び出した際も、同じ結果を出力します。

var a = Array(1, 2, 3); // [object Array]
var b = new Array(1, 2, 3); // [object Array]
var c = [1, 2, 3]; // [object Array]
 
var d = Function(''); // [object Function]
var e = new Function(''); // [object Function]

演算子によっては、適用される際に明示的、あるいは暗黙的な型キャストが行われます。

var a = 1;
var b = 2;
 
// 暗黙的
var c = a + b; // 3 、 number
var d = a + b + '5' // "35" 、 string
 
// 明示的
var e = '10'; // "10" 、 string
var f = +e; // 10 、 number
var g = parseInt(e, 10); // 10 、 number
 
// 等々
プロパティ属性

全てのプロパティはいくつかの属性を持ちます(訳注:下記は ES3 でのものです)。

  • {ReadOnly} ― プロパティへの値の書き込もうとする試みは無視される。しかし、 ReadOnly プロパティはホスト環境の作用によって変化し得るため、 ReadOnly が「定数」を意味するわけではない。
  • {DontEnum} ― プロパティが for.. in ループに列挙されない。
  • {DontDelete} ― プロパティへの delete 演算子の作用は無視される。
  • {Internal} ― プロパティは内部的なものである。名前を持たず、実装レベルでのみ使用される。こうしたプロパティは、 ECMAScript のプログラムからはアクセスできない。

ES5 では、 {ReadOnly}{DontEnum}{DontDelete} はそれぞれ [[Writable]][[Enumerable]][[Configurable]]名称変更されました(未訳)。また、これらの属性は Object.defineProperty などのメソッドを使って手動で修正することが可能です。

var foo = {};
 
Object.defineProperty(foo, "x", {
  value: 10,
  writable: true, // {ReadOnly} = false
  enumerable: false, // {DontEnum} = true
  configurable: true // {DontDelete} = false 
});
 
console.log(foo.x); // 10
 
// 属性の集合は記述子と呼ばれます
var desc = Object.getOwnPropertyDescriptor(foo, "x");
 
console.log(desc.enumerable); // false
console.log(desc.writable); // true
// etc.
内部プロパティと内部メソッド

オブジェクトもまた、実装の一部であり ECMAScript プログラムからは直接にアクセスすることのできない(とはいえ今後見ていくように実装によってはアクセス可能な)、いくつかの内部プロパティを持ちます。これらのプロパティは、慣習として二重の角括弧 [[ ]] で囲われます。

ここではこれらのいくつかに触れていきますが(全てのオブジェクトに必須なもの)、その他のプロパティの詳細は仕様に見ることができます。

全てのオブジェクトは、以下の内部プロパティと内部メソッドを実装しなければなりません。

  • [[Prototype]] ― このオブジェクトのプロトタイプ(詳しくは後に検討します)。
  • [[Class]] ― オブジェクトの種類を文字列で表すもの(例えば ObjectArrayFunction など)。オブジェクトを区別するために使われる。
  • [[Get]] ― プロパティの値を取得するためのメソッド。
  • [[Put]] ― プロパティの値を設定するためのメソッド。
  • [[CanPut]] ― プロパティへの書き込みが可能かチェックする。
  • [[HasProperty]] ― オブジェクトが指定のプロパティを持つかチェックする。
  • [[Delete]] ― オブジェクトからプロパティを削除する。
  • [[DefaultValue]] ― オブジェクトに対応するプリミティブ値を返す(この値の取得には valueOf メソッドが呼ばれる。いくつかのオブジェクトに関しては、 TypeError 例外が投げられる)。

ECMAScript プログラムからの [[Class]] プロパティの取得は、 Object.prototype.toString() を経由して間接的に可能です。このメソッドは以下の文字列 "[object " + [[Class]] + "]" を返します。例えば、

var getClass = Object.prototype.toString;
 
getClass.call({}); // [object Object]
getClass.call([]); // [object Array]
getClass.call(new Number(1)); // [object Number]
// など

この機能はしばしばオブジェクトの種類を確認するために用いられますが、仕様によればホストオブジェクトの内部 [[Class]] プロパティはビルトインオブジェクトの [[Class]] プロパティを含め何でも良いと規定されていることに注意が必要です。つまり理論的には100%保証するチェックはできないということです。例えば、 document.childNodes.item(...) メソッドの IE における [[Class]] プロパティの値は "String" です(他の実装では、 "Function" が返されます)。

// IE では "String" 、 その他では "Function"
alert(getClass.call(document.childNodes.item));

訳注: ES5 仕様において、ホストオブジェクトの [[Class]] プロパティが、 "Arguments""Array""Boolean""Date""Error""Function""JSON""Math""Number""Object""RegExp""String" 以外の値を取ると規定されました。

コンストラクタ

既に触れたように、 ECMAScript におけるオブジェクトは、いわゆるコンストラクタを経由して生成されます。

コンストラクタは、新しく生成されるオブジェクトを生成し、初期化する関数です。

生成(メモリアロケーション)に関しては、コンストラクタ関数の [[Constructor]] 内部メソッドが担当します。新しいオブジェクトのメモリを割り当てるために、この内部メソッドのふるまいが規定され、全てのコンストラクタ関数がこのメソッドを利用します。

そして初期化に関しては、新しく生成されたオブジェクトのコンテキストで関数を呼び出すことで行われます。ここではコンストラクタ関数の [[Call]] 内部メソッドが担当します。

注意としては、ユーザコードからアクセス可能なのはこのうち初期化フェーズのみだということです。しかし初期化フェーズにおいても、最初に生成された this オブジェクトを無視して、異なるオブジェクトを戻すことが可能です。

function A() {
  // 新しく生成されたオブジェクトを更新
  this.x = 10;
  // しかし異なるオブジェクトを戻す
  return [1, 2, 3];
}
 
var a = new A();
console.log(a.x, a); undefined, [1, 2, 3]

『第5章 関数』にて検討したように、関数オブジェクトの生成アルゴリズムを見ると、関数とはプロパティの中でもこの内部 [[Construct]] 及び [[Call]] プロパティを持ち、明示的な prototype プロパティ(将来この関数をコンストラクタとして生成されるオブジェクトのプロトタイプへの参照)を持つネイティブオブジェクトであることが分かります(下記の例での NativeObject とは、 ECMA-262-3 の「ネイティブオブジェクト」の考え方のために私が慣習的に名付けた擬似コード上の名称であって、ビルトインコンストラクタではありません)。

F = new NativeObject();
 
F.[[Class]] = "Function"
 
.... // その他のプロパティ
 
F.[[Call]] = <reference to function> // 関数自身
 
F.[[Construct]] = internalConstructor // 一般的な内部コンストラクタ
 
.... // その他のプロパティ
 
// F コンストラクタで生成されるオブジェクトのプロトタイプ
__objectPrototype = {};
__objectPrototype.constructor = F // {DontEnum}
F.prototype = __objectPrototype

したがって、 [[Class]] プロパティ( "Function" となる)に加えて [[Call]] が、オブジェクトを区別する点で重要になります。つまり、内部 [[Call]] プロパティを持つオブジェクトが、関数と呼ばれるのです。こうしたオブジェクトに対して、 typeof 演算子は "function" という値を返します。ただしこれは主にネイティブオブジェクトの話であって、呼び出し可能なホストオブジェクトの場合は、実装によっては typeof 演算子が( [[Class]] プロパティに負けず劣らず)他の値を返します。例えば、 IE において window.alert(...) は…、

// IE では ― "Object", "object" 、その他では "Function", "function"
alert(Object.prototype.toString.call(window.alert));
alert(typeof window.alert); // "Object"

内部 [[Construct]] メソッドはコンストラクタ関数に new 演算子を付与することでアクティベートされます。申し上げたように、このメソッドはメモリアロケーションとオブジェクトの生成を担当しています。もし実引数が無い場合は、コンストラクタ関数の呼び出し括弧は省略可能です。

function A(x) { // コンストラクタ A
  this.x = x || 10;
}
 
// 実引数が無い場合、
// 呼び出し括弧は省略可能
var a = new A; // or new A();
alert(a.x); // 10
 
// 明示的な x 引数の
// 引き渡し
var b = new A(20);
alert(b.x); // 20

既にご存じの通り、コンストラクタ中の(初期化フェーズにおける) this 値新しく生成されたオブジェクトにセットされます。

それでは次に、オブジェクト生成のアルゴリズムについて検討してみましょう。

オブジェクト生成のアルゴリズム

内部 [[Construct]] メソッドのふるまいは以下のように記述できます。

F.[[Construct]](initialParameters):
 
O = new NativeObject();
 
// [[Class]] プロパティが "Object" にセットされる、つまり単純なオブジェクト
O.[[Class]] = "Object"
 
// この瞬間において F.protottype を参照している
// オブジェクトを取得
var __objectPrototype = F.prototype;
 
 // もし __objectPrototype がオブジェクトならば
O.[[Prototype]] = __objectPrototype
// そうでは無い場合、
O.[[Prototype]] = Object.prototype;
// ここで O.[[Prototype]] とは生成されるオブジェクトのプロトタイプ
 
// F.[[Call]] を呼び出して
// 新しく生成されたオブジェクトを初期化
// this 値としては新しく生成されたオブジェクト ― O
// [[Call]] への実引数は F への initialParameters と同じ
R = F.[[Call]](initialParameters); this === O;
// R は [[Call]] の戻り値
// JS 的に見ればこのようになる
// R = F.apply(O, initialParameters);
 
// R がオブジェクトなら
return R
// そうでなければ
return O

二つの重要な特徴に注目して下さい。

最初に、生成されたオブジェクトのプロトタイプは、その瞬間のコンストラクタ関数の prototype プロパティから取られます(これの意味することは、ある一つのコンストラクタから生成された二つのオブジェクトのプロトタイプは異なる可能性がある、ということです。なぜならコンストラクタ関数の prototype プロパティは、いつでも変更し得るからです)。

次に、上で触れたように、オブジェクト初期化時に [[Call]]オブジェクトを戻した場合、まさにそのオブジェクトが new 式全体の結果として用いられます。

function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10 - 委譲によって、プロトタイプから
 
// 関数の .prototype プロパティを
// 新しいオブジェクトにセットする
// .constructor プロパティを
// 明示的に定義している理由は後に説明する
A.prototype = {
  constructor: A,
  y: 100
};
 
var b = new A();
// オブジェクト "b" が新しいプロトタイプを持つ
alert(b.x); // undefined
alert(b.y); // 100 - 委譲によって、プロトタイプから
 
// しかし、オブジェクト "a" のプロトタイプは
// 依然として古いままである(理由は後述する)
alert(a.x); // 10 - 委譲によって、プロトタイプから
 
function B() {
  this.x = 10;
  return new Array();
}
 
// もし "B" コンストラクタが何も戻さなければ、
// (あるいは this を戻したとしたら)
// this オブジェクトが使われるが、
// この場合では配列である
var b = new B();
alert(b.x); // undefined
alert(Object.prototype.toString.call(b)); // [object Array]

それでは、プロトタイプについてより詳しく見ていきましょう。

プロトタイプ

全てのオブジェクトはプロトタイプを持ちます(システムオブジェクトには例外もあります)。プロトタイプとの通信は、内部的で、暗黙的で、直接にはアクセス不可の [[Prototype]] プロパティを経由して行われます。プロトタイプは、オブジェクトまたは null 値を取ります。

constructor プロパティ

上の例には、二つの重要なポイントがありました。一つ目は関数の prototype プロパティの constructor プロパティに関係しています。

関数オブジェクト生成のアルゴリズムで見たとおり、 constructor プロパティは、関数の生成時に、関数の prototype プロパティ(訳注:この関数によって生成されたオブジェクトにとってのプロトタイプ)にセットされます。このプロパティの値は、関数自身そのものへの循環参照になっています。

function A() {}
var a = new A();
alert(a.constructor); // function A() {} 、委譲によって
alert(a.constructor === A); // true

しばしば、このケースについて誤解があります。 constructor プロパティを、誤って生成されたオブジェクト自身のプロパティと扱ってしまうのです。しかし、見てきたようにこのプロパティはプロトタイプに属し、継承を通じてオブジェクトにアクセスできるのです。

継承された constructor プロパティを使って、インスタンスは間接的にプロトタイプオブジェクトへの参照を得ることができるのです。

function A() {}
A.prototype.x = new Number(10);
 
var a = new A();
alert(a.constructor.prototype); // [object Object]
 
alert(a.x); // 10 、委譲を通じて
// the same as a.[[Prototype]].x
alert(a.constructor.prototype.x); // 10
 
alert(a.constructor.prototype.x === a.x); // true

しかしご注意いただきたいのは、関数の constructor 及び prototype プロパティ共に、オブジェクトが生成された後で再定義されることが可能だということです。この場合、オブジェクトは上記のメカニズムへの参照を失うことになります。

もしここで、関数の prototype プロパティを通じてオリジナルのプロトタイプに新しいプロパティを追加、または既存のプロパティを変更すると、インスタンスからはその新しく追加・変更されたプロパティを見ることになります。

しかし、もし関数の prototype プロパティを完全に変更してしまったら(新しいオブジェクトを代入することで)、オリジナルのコンストラクタ(もちろんオリジナルのプロトタイプも)への参照は失われてしまいます。もちろんこれは、新しく生成したオブジェクトが constructor プロパティを(自身に)持たないためです。

function A() {}
A.prototype = {
  x: 10
};
 
var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // false!

従って、この参照を戻すためには、手動で回復してあげなければなりません。

function A() {}
A.prototype = {
  constructor: A,
  x: 10
};
 
var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // true

手動で回復された constructor プロパティは、失われたオリジナルに比較して、 {{DontEnum}} 属性を持たないことには注意が必要です。そしてこの結果として、 A.prototype を通じて for.. in ループ中に現れてしまいます。

ES5 では、 {{Enumerable}} 属性を通じて、プロパティの列挙可能状態を操作することができるように(未訳)なりました。

var foo = {x: 10};
 
Object.defineProperty(foo, "y", {
  value: 20,
  enumerable: false // {DontEnum} = true だったもの
});
 
console.log(foo.x, foo.y); // 10 、 20
 
for (var k in foo) {
  console.log(k); // "x" のみ
}
 
var xDesc = Object.getOwnPropertyDescriptor(foo, "x");
var yDesc = Object.getOwnPropertyDescriptor(foo, "y");
 
console.log(
  xDesc.enumerable, // true
  yDesc.enumerable  // false
);
明示的な prototype と暗黙の [[Prototype]] プロパティ

時折、誤ってオブジェクトのプロトタイプを関数の prototype プロパティへの明示的な参照であると混同してしまうことがあります。これは完全な誤りとは言えませんが、正確ではありません。実際は、これらは共に、全く同じオブジェクト、つまりオブジェクトの [[Prototype]] プロパティへの参照なのです(訳注:どちらかが実体で、どちらかが参照、という関係では無く、どちらも参照でしかありません)。

a.[[Prototype]] ----> Prototype <---- A.prototype

さらにインスタンスの [[Prototype]] は、その値を(その参照を)、まさしくコンストラクタの prototype プロパティから、オブジェクト生成時に、得るのです。

しかしながら、コンストラクタの prototype プロパティを置き換えても、既に生成されたオブジェクト のプロトタイプには影響しません。変更されるのは、コンストラクタの prototype プロパティのみなのです!。つまり、今後新しく生成されるオブジェクトは、新しいプロトタイプを持ち、( prototype プロパティが変更される前に)既に生成されたオブジェクトは、古いプロトタイプへの参照を持ち続け、この参照は以後変更できないのです

// A.prototype が変更される前
a.[[Prototype]] ----> Prototype <---- A.prototype
 
// 変更された後
A.prototype ----> New prototype // 新しいオブジェクトはこのプロトタイプを持つことになる
a.[[Prototype]] ----> Prototype // 古いプロトタイプへの参照を持ち続ける

例です。

function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10
 
A.prototype = {
  constructor: A,
  x: 20
  y: 30
};
 
// オブジェクト "a" は
// 暗黙の [[Prototype]] 参照を通じ
// 古いプロトタイプに委譲を行う
alert(a.x); // 10
alert(a.y) // undefined
 
var b = new A();
 
// しかし新しいオブジェクトは
// 生成時に新しいプロトタイプへの参照を取得する
alert(b.x); // 20
alert(b.y) // 30

訳注:分かりにくいポイントなので補足しますと、コンストラクタ関数の prototype プロパティそのものの参照先を変更した場合は、上記の通りです。対して、コンストラクタ関数の prototype プロパティであるオブジェクトのプロパティを一部変更した場合は、その変更が既に生成されたオブジェクトに継承を通じて影響します。

function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10
 
// prototype 「の」プロパティを追加
A.prototype.y = 20;
 
// インスタンスにも継承を通じて反映
alert(a.y); // 20
 
// prototype 「そのもの」を変更
A.prototype = {
  constructor: A,
  x: 20,
  y: 30
};
 
// インスタンスから参照されているプロトタイプは
// 古いまま
alert(a.x); // 10
alert(a.y); // 20

従って、 JavaScript に関数記事などでしばしば見受けられる、「プロトタイプそのものの動的な変更は全てのオブジェクトに影響し、それらのオブジェクトは皆新しいプロトタイプを持つことになる」という主張は、誤りです(訳注:補足したとおり「プロトタイプのプロパティへの変更」であれば、全てのオブジェクトに影響します)。新しいプロトタイプは、新しいオブジェクトにのみ影響します。

重要はルールはこうです。オブジェクトのプロトタイプは、オブジェクトの生成時にセットされ、その後は新しいオブジェクトに変更できません。コンストラクタからの明示的な prototype参照を使えば、それが(オブジェクトのプロトタイプとコンストラクタの prototype プロパティが共に)同じオブジェクトを指している限り、オブジェクトのプロトタイプに新しいプロパティを追加したり、既存のプロパティを変更することだけは可能です。

非標準の __proto__ プロパティ

ところが、一部の実装、例えば SpiderMonkey は、(暗黙的であってオブジェクト生成後にはユーザコードからは変更できないはずの)オブジェクトのプロトタイプへの明示的な参照を持っています。非標準の __proto__ プロパティです。

function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10
 
var __newPrototype = {
  constructor: A,
  x: 20,
  y: 30
};
 
// 新しいオブジェクトへの参照
A.prototype = __newPrototype;
 
var b = new A();
alert(b.x); // 20
alert(b.y); // 30
 
// オブジェクト "a" は依然として
// 古いプロトタイプを持つ
alert(a.x); // 10
alert(a.y); // undefined
 
// プロトタイプを明示的に変更
a.__proto__ = __newPrototype;
 
// オブジェクト "a" も
// 新しいオブジェクトを参照するようになる
alert(a.x); // 20
alert(a.y); // 30

ES5 では、 Object.getPrototypeOf(O) メソッドが導入されました。これは直接オブジェクトの [[Prototype]] プロパティ、つまりインスタンスのオリジナルのプロトタイプを返すものです。しかし __proto__ と異なるのは、これは getter であり、プロトタイプをセットすることはできないということです。

var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // true
オブジェクトはコンストラクタから独立している

インスタンスのプロトタイプはコンストラクタ及びコンストラクタの prototype プロパティから独立していますから、その主な用途、オブジェクトの生成が終了した後は、コンストラクタは削除できます。コンストラクタが削除されてもプロトタイプオブジェクトは [[Prototype]] プロパティを通じて参照され、存在し続けます(訳注:コンストラクタの prototype プロパティもまた、プロトタイプオブジェクトへのある一つの参照でしかないということ。この「参照」がオブジェクト(インスタンス)生成時にオブジェクトに引き渡されるということを再度思い出して下さい。より根本的には、 JavaScript においてオブジェクトを格納する変数・プロパティはすべて参照である、ということも意識しておくと理解しやすいと思います)。

function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10
 
// 明示的なコンストラクタへの参照である
// "A" を null にセットする
// (訳注:コンストラクタ関数へのある一つの参照を削除しただけ)
A = null;
 
// しかし依然として、
// .constructor プロパティが変更されていなければ
// 他のオブジェトからの間接的な参照を通じて
// オブジェクトを生成することができる
var b = new a.constructor();
alert(b.x); // 10
 
// 両方の暗黙的な参照を削除する
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
 
// これで "A" コンストラクタのオブジェクトを
// 生成することはできなくなったが、
// 生成された二つのオブジェクトには、
// それぞれのプロトタイプへの参照が存在し続ける
alert(a.x); // 10
alert(b.x); // 10
insanceof 演算子の特徴

instanceof 演算子の働きには、コンストラクタの prototype プロパティを経由した、プロトタイプへの明示的な参照が関わっています。

この演算子は、コンストラクタに対してではなく、まさにプロトタイプチェーンに対して働きます。これを鑑みるに、いくつか誤解があるように思われます。例えば、このような検査があったとします。

if (foo instanceof Foo) {
  ...
}

これは、オブジェクト foo が、コンストラクタ Foo によって生成されたかどうかの検査では無いのです!。

instanceof 演算子が行っているのは、オブジェクトのプロトタイプ( foo.[[Prototype]] )を取り出し、プロトタイプチェーン( foo.[[Prototype]]foo.[[Prototype]].[[Prototype]]foo.[[Prototype]].[[Prototype]] …)中に Foo.prototype存在を検査することだけです。

これをサンプルコードで見てみましょう。

function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10
 
alert(a instanceof A); // true
 
// もし A.prototype を
// null にセットしたら…
A.prototype = null;
 
// … "a" オブジェクトは
// 依然としてそのプロトタイプに
// a.[[Prototype]] を経由して
// アクセスできる
alert(a.x); // 10
 
// しかし、 instanceof 演算子は
// 働かなくなる
// なぜならその検査は
// コンストラクタのプロトタイププロパティに対して
// 行われるからである
alert(a instanceof A); // エラー、 A.prototype はオブジェクトではない

これに対し、あるコンストラクタからオブジェクトを生成しつつ、 instanceof異なるコンストラクタとの検査に true を返すようにすることが可能です。オブジェクトの [[Prototype]] プロパティ及びコンストラクタの prototype プロパティを、同じオブジェクトに向けさえすればいいのです。

function B() {}
var b = new B();
 
alert(b instanceof B); // true
 
function C() {}
 
var __proto = {
  constructor: C
};
 
C.prototype = __proto;
b.__proto__ = __proto;
 
alert(b instanceof C); // true
alert(b instanceof B); // false
メソッドと共有プロパティのためのストレージとしてのプロトタイプ

ECMAScript におけるプロトタイプの最も有用な応用は、オブジェクトのメソッドデフォルト状態共有プロパティのストレージとしての役割です。

実際、オブジェクトはそれぞれ独自の状態を持つことができます。しかし、メソッドは大抵同じです。したがって、メモリ使用量の最適化のために、メソッドは通例プロトタイプ中に定義されます。これは、このコンストラクタから生成された全てのインスタンスが、常に同じメソッドを共有するということを意味します。

function A(x) {
  this.x = x || 100;
}
 
A.prototype = (function () {
 
  // コンテキストを初期化、
  // 補助的なオブジェクトを使う
 
  var _someSharedVar = 500;
 
  function _someHelper() {
    alert('internal helper: ' + _someSharedVar);
  }
 
  function method1() {
    alert('method1: ' + this.x);
  }
 
  function method2() {
    alert('method2: ' + this.x);
    _someHelper();
  }
 
  // これがプロトタイプそのもの
  return {
    constructor: A,
    method1: method1,
    method2: method2
  };
 
})();
 
var a = new A(10);
var b = new A(20);
 
a.method1(); // method1: 10
a.method2(); // method2: 10 、 internal helper: 500
 
b.method1(); // method1: 20
b.method2(); // method2: 20 、 internal helper: 500
 
// どちらのオブジェクトも
// 同じプロトタイプの
// 同じメソッドを使っている
alert(a.method1 === b.method1); // true
alert(a.method2 === b.method2); // true

プロパティの読み書き

前述したように、プロパティの読み書きはそれぞれ内部メソッド [[Get]][[Put]] によって行われます。これらのメソッドは、プロパティアクセサ、つまりドット記法及び括弧記法によってアクティベートされます。

// 書き込み
foo.bar = 10; // [[Put]] が呼ばれる
 
// 読み出し
console.log(foo.bar); // 10 、[[Get]] が呼ばれる
console.log(foo['bar']); // 上に同じ

[[Get]] メソッドはオブジェクトのプロトタイプチェーン中のプロパティも考慮します。したがって、プロトタイプのプロパティはオブジェクト自身のプロパティ同様にアクセスが可能です。

対して [[Put]] メソッドは、オブジェクト自身のプロパティを作成または更新するもので、プロトタイプ中の同名のプロパティを隠すように働きます。

これらのメソッドの働きを、擬似コードで見ていくことにしましょう。

[[Get]] メソッド

[[Get]]

O.[[Get]](P):
 
// 自身のプロパティがあれば
// それを返す
if (O.hasOwnProperty(P)) {
  return O.P;
}
 
// そうでなければ、プロトタイプを分析する
var __proto = O.[[Prototype]];
 
// もしプロトタイプが無ければ
// (例えばチェーンの最後の要素、
// Object.prototype.[[Prototype]]
// は null である)
// undefined を返す
if (__proto === null) {
  return undefined;
}
 
// プロトタイプがあれば、
// プロトタイプに対し再帰的に [[Get]] メソッドを呼び出す
// つまりプロトタイプチェーン中の
// ある要素(プロトタイプ)に対し
// プロパティを見つけようとし、
// 見つからなければ
// [[Prototype]] が null に等しくなるまで
// そのプロトタイプのプロトタイプへと続く
return __proto.[[Get]](P)

補足としては、 [[Get]] メソッドはケースによっては undefined を返しますので、以下のように、変数の存在をチェックすることが可能です。

if (window.someObject) {
  ...
}

ここで、プロパティ someObjectwindow 、あるいはそのプロトタイプ、さらにそのプロトタイプのプロトタイプ…( null になるまで)に見つからなければ、アルゴリズムに従い、 undefined が返されます。

正確な存在の確認には、 in 演算子が使われることも覚えておいてください。こちらもプロトタイプチェーンを考慮します。

if ('someObject' in window) {
  ...
}

in 演算子は、例えば someObject が存在しながら値が false である場合でも、プロパティの存在を確認できます。もう一つ前の例では、 if を通過できません。

[[Put]] メソッド

[[Put]]

O.[[Put]](P, V):
 
// このプロパティに
// 書き込めない場合は
// 終了する
if (!O.[[CanPut]](P)) {
  return;
}
 
// もしオブジェクトが
// それ自身にプロパティを持っていなければ
// それを生成する
// 全ての属性は空に設定される( false になる)(訳注: ES3 )
if (!O.hasOwnProperty(P)) {
  createNewProperty(O, P, attributes: {
    ReadOnly: false,
    DontEnum: false,
    DontDelete: false,
    Internal: false
  });
}
 
// 値をセットする
// 属性は変更されない
O.P = V
 
return;
プロパティアクセサ

前述の通り、内部メソッド [[Get]][[Put]]プロパティアクセサによってアクティベートされます。これは、 ECMAScript においてはドット記法または括弧記法を用いて利用可能です。ドット記法はプロパティ名が妥当な識別子であり予めその名前を知っている場合に用いられ、括弧記法ではプロパティの名前を動的に形成することができます。

var a = {testProperty: 10};
 
alert(a.testProperty); // 10 、ドット記法
alert(a['testProperty']); // 10 、括弧記法
 
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10 、動的なプロパティによる括弧記法

ここに一つ重要な特徴があります。プロパティアクセサは、常に左辺のオブジェクトに関し ToObject 変換を行います。そして、この暗黙の変換のために、大雑把に言って「 JavaScript における全ての要素はオブジェクト」と表現することが可能なのです(とはいえもちろん皆さんはご存じの通り、プリミティブ値というものがありますから、「全て」ではありません)。

プリミティブ値に対してプロパティアクセサを用いると、その値に対応する中間ラッパーオブジェクトを生成することになります。そして演算が終了した後、このラッパーは削除されます。

例です。

var a = 10; // プリミティブ値
 
// しかし、まるでオブジェクトであるように
// メソッドにアクセスできる
alert(a.toString()); // "10"
 
// さらに、 [[Put]] を呼び出して
// プリミティブ値 "a" に
// 新しいプロパティを作る(ことを試みる)
// ことさえできる
a.test = 100; // seems, it even works
 
// しかし [[Get]] は
// このプロパティの値を返さない
// アルゴリズムに従い undefined が返される
alert(a.test); // undefined

ではなぜ、この例では「プリミティブ」値が toString メソッドにアクセスしながら、新しく作られた test プロパティにはアクセスできないのでしょうか?

答えはシンプルです。

まず最初に、ご説明したとおり、プロパティアクセサが適用されると、それはプリミティブではなく中間オブジェクトになります。この場合 new Number(a) が使われ、委譲によってプロトタイプチェーン中に toString メソッドが見つかります。

// a.toString() を評価するアルゴリズム
 
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;

次に、 [[Put]] メソッドも test プロパティの評価時に、またそれ独自のラッパーオブジェクトを生成します。

// a.test = 100: を評価するアルゴリズム
 
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;

ステップ3でラッパーが削除され、同様に新しく生成された test プロパティもまた、オブジェクトそのものが削除されるのですから、もちろん削除されます

そしてそれから再度 [[Get]] が呼び出されると、プロパティアクセサはもう一度新しいラッパーを生成します。もちろん、これには test プロパティのようなものは一切含まれません

// a.test: を評価するアルゴリズム
 
1. wrapper = new Number(a);
2. wrapper.test; // undefined

これが、プリミティブ値のプロパティ・メソッドがプロパティの読み出し時にのみ有効となる仕組みです。従ってもし、プリミティブ値がしばしばプロパティにアクセスするのであれば、時間リソースを節約するために、予め直接オブジェクト表現に変換しておくと良いでしょう。それに対し、値が小さな計算にのみ参加し、プロパティへのアクセスを要求しないようであれば、プリミティブ値のまま取り扱うのが効率的です。

継承

既にご存じの通り、 ECMAScript はプロトタイプに基づく委譲による継承を利用しています。

プロトタイプをつなげたものが、これも既に触れたプロトタイプチェーンを形成します。

実際には、委譲を実装する働きや、プロトタイプチェーンの分析は、記述した [[Get]] メソッドの働きに集約されます。

[[Get]] メソッドのシンプルなアルゴリズムを完全に理解していれば、 JavaScript における継承に関する疑問は自ずと消失するするでしょうし、その答えも明確になるでしょう。

しばしばフォーラム等において JavaScript の継承に関する話題が上ると、私は例として、完全にかつ正確に ECMAScript のオブジェクト構造を記述し、委譲ベースの継承について示してくれるたった一行の ECMAScript のコードを提示します。実に、コンストラクタやオブジェクトを一切生成できなくとも、言語そのものに継承が浸透しているのです。この一行のコードはとてもシンプルです。

alert(1..toString()); // "1"

今や私たちは、 [[Get]] メソッドのアルゴリズムとプロパティアクセサを理解していますから、このコードで何が起こるかを理解できるでしょう。

  1. まず、プリミティブ値 1 から、ラッパーオブジェクト new Number(1) が生成されます。
  2. そして、継承されたメソッド toString がこのラッパーによって呼び出されます。

なぜ継承が利用されるのでしょうか?。それは ECMAScript のオブジェクトは自身のプロパティを持つことができますが、この場合、ラッパーオブジェクトが自身の toString メソッドを持たないためです。従ってプロトタイプから継承することになります。 Number.prototype からです。

シンタックスの微妙なケースについて注意して下さい。上記の例の2つのドットはエラーではありません。最初のドットは数字の小数点であり、二つ目のドットはご存じプロパティアクセサです。

1.toString(); // シンタックスエラー!
 
(1).toString(); // OK
 
1..toString(); // OK
 
1['toString'](); // OK
プロトタイプチェーン

このプロトタイプチェーンというものを、ユーザ定義オブジェクトにおいてどのように生成するのか見てみましょう。とても簡単です。

function A() {
  alert('A.[[Call]] activated');
  this.x = 10;
}
A.prototype.y = 20;
 
var a = new A();
alert([a.x, a.y]); // 10 (自身の)、 20 (継承された)
 
function B() {}
 
// プロトタイプチェーン生成の
// 最も簡単な方法は、子のプロトタイプを
// 親コンストラクタによって新しく生成したオブジェクトに
// セットすることです。
B.prototype = new A();
 
// .constructor プロパティを修正します
// そうしなければ A を指してしまいます
B.prototype.constructor = B;
 
var b = new B();
alert([b.x, b.y]); // 10 、 20 、共に継承
 
// [[Get]] b.x:
// b.x (無) -->
// b.[[Prototype]].x (有) - 10
 
// [[Get]] b.y
// b.y (無) -->
// b.[[Prototype]].y (無) -->
// b.[[Prototype]].[[Prototype]].y (有) - 20
 
// ここでは、 b.[[Prototype]] === B.prototype であり
// b.[[Prototype]].[[Prototype]] === A.prototype である

このアプローチには二つの特徴があります。

一つ目は、 B.prototypex プロパティを持つことです。一見すると、これは正しくないように見えるかもしれません。 x プロパティは A にて自身のものとして定義されていて、これを継承する B コンストラクタのオブジェクトが、自身のものとして持つことを期待してしまいます。

しかし、プロトタイプ継承の場合、これは特に不思議の無い状況です。子オブジェクトが、もし自身のものとしてプロパティを持たなければ、それをプロトタイプに委譲します。この背景にあるのは、 B コンストラクタによって生成されたオブジェクトが x プロパティを持つ必要が無いという考え方です。これに対してクラスベースモデルでは、全てのプロパティは子クラスにコピーされます。

ただしもし、どうしても(クラスベースのアプローチを模倣して) x プロパティを自身のものとして B コンストラクタのオブジェクトに持たせたい場合には、いくつかのテクニックがあります。その人は後ほどご紹介します。

二つ目は、これは特徴と言うよりむしろ欠点なのですが、コンストラクタの(初期化)コードが、子プロトタイプを生成するときにも実行されてしまうと言うことです。これは、上記サンプルコードを実行した際に、 "A.[[Call]] activated"二度表示されることに見て取れます。 A コンストラクタによって生成されたオブジェクトが B.prototype として使われると共に、もちろん a オブジェクトを生成するときも使われるのです!

より問題となる例としては、親コンストラクタで投げられる例外があります。おそらく、コンストラクタによって実際のオブジェクトを生成する際には、こうしたチェックが必要だと思います。しかし明らかに、このコストラクタから生成する親オブジェクトを子のプロトタイプとして使おうとすると、同じケースは受け入れがたいものになります。

function A(param) {
  if (!param) {
    throw '引数が必要です';
  }
  this.param = param;
}
A.prototype.x = 10;
 
var a = new A(20);
alert([a.x, a.param]); // 10 、 20
 
function B() {}
B.prototype = new A(); // エラー

さらに、親コンストラクタ中で重たい計算を行う場合も、このアプローチの欠点であると捉えることができるでしょう。

こうした「特徴」や問題を解決するために、今日のプログラマは下記のようなプロトタイプを結びつける標準的なパターンを用います。このトリックの主な目的は、必要とされるプロトタイプ同士を結びつける、中間ラッパーコンストラクタを生成することにあります。

function A() {
  alert('A.[[Call]] activated');
  this.x = 10;
}
A.prototype.y = 20;
 
var a = new A();
alert([a.x, a.y]); // 10 (自身のもの)、 20 (継承で)
 
function B() {
  // あるいは単に A.apply(this, arguments) とする
  B.superproto.constructor.apply(this, arguments);
}
 
// 継承:空の中間コンストラクタを使って
// プロトタイプ間を結びつける
var F = function () {};
F.prototype = A.prototype; // 参照
B.prototype = new F();
B.superproto = A.prototype; // 親プロトタイプへの明示的な参照のための「シュガー」
 
// コンストラクタプロパティを修正する
// そうしなければ A となってしまう
B.prototype.constructor = B;
 
var b = new B();
alert([b.x, b.y]); // 10 (自身のもの)、 20 (継承で)

b インスタンスに自身のプロパティ x を生成している過程に注目して下さい。親コンストラクタを、新しく生成されたオブジェクトのコンテキストの中で、 B.superproto.constructor 参照を通じて呼び出しています。

さらに、子プロトタイプの生成のために、親コンストラクタの不要な呼び出しが発生する問題も解決しています。このアプローチでは、 "A.[[Call]] activated" のメッセージは本当に必要な時しか表示されません。

プロトタイプチェーンを作るために同じこと(中間コンストラクタの生成、 superproto シュガーの設定、オリジナルの constructor の回復)を都度実行することを避けるために、このテンプレートは便利なユーティリティ関数にまとめることができます。目的は、具体的なコンストラクタの名前に関わらず、プロトタイプを結びつけられるようにすることです。

function inherit(child, parent) {
  var F = function () {};
  F.prototype = parent.prototype
  child.prototype = new F();
  child.prototype.constructor = child;
  child.superproto = parent.prototype;
  return child;
}

これによって継承は次のようになります。

function A() {}
A.prototype.x = 10;
 
function B() {}
inherit(B, A); // プロトタイプを結びつける
 
var b = new B();
alert(b.x); // 10 、 A.prototype 中に見つかる

こうしたラッパーには、シンタックス的に多くのバリエーションがあります。しかし、それらは全て上記のような作用に集約されると思います。

例えば、このラッパーを最適化し、中間コンストラクタを外に出すことで(従って生成されるのはたった一つの関数になることで)再利用できます。

var inherit = (function(){
  function F() {}
  return function (child, parent) {
    F.prototype = parent.prototype;
    child.prototype = new F;
    child.prototype.constructor = child;
    child.superproto = parent.prototype;
    return child;
  };
})();

オブジェクトの本当のプロトタイプは [[Prototype]] プロパティですから、中間ラッパーである F.prototype は簡単に変更でき、再利用しても問題ありません。なぜなら new F を通じて生成される child.prototype は、その生成時に [[Prototype]]parant.prototypeその瞬間の値から受け取るからです。

function A() {}
A.prototype.x = 10;
 
function B() {}
inherit(B, A);
 
B.prototype.y = 20;
 
B.prototype.foo = function () {
  alert("B#foo");
};
 
var b = new B();
alert(b.x); // 10 、 A.prototype に見つかる
 
function C() {}
inherit(C, B);
 
// そして "superproto" シュガーを使えば、
// 同名の親メソッドを呼び出すこともできる
 
C.ptototype.foo = function () {
  C.superproto.foo.call(this);
  alert("C#foo");
};
 
var c = new C();
alert([c.x, c.y]); // 10 、 20
 
c.foo(); // B#foo, C#foo

ES5 は、このユーティリティ関数をより良いプロトタイプ連結のために標準化しました。それが Object.create メソッドです。

ES3 においても、このメソッドの簡易化されたバージョンを次のように実装できるでしょう。

Object.create ||
Object.create = function (parent, properties) {
  function F() {}
  F.prototype = parent;
  var child = new F;
  for (var k in properties) {
    child[k] = properties[k].value;
  }
  return child;
}

使用例

var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10 、 20

詳細はこの章(未訳)を参照ください。

今存在する「 JS におけるクラス的継承」の模倣のバリエーションは、全てこの方式に基づいています。見てきたように、実際これは「クラスベースの継承の模倣」などではなく、単なる「プロトタイプ同士を結びつけることによる、便利なコード再利用」なのです。

結論

この章は、かなり長く、そして詳細にわたるものとなりました。この資料がみなさまの役に立ち、 ECMAScript に関する疑念を解消してくれることを望みます。質問や補足などあれば、いつもの通りコメント欄で議論できます(訳注:このブログにはコメント欄がありません。いつものように、私の個人ブログにいずれ転載しますので、そちらで質問をいただければと思います)。

参考文献

英語版翻訳: Dmitry A. Soshnikov 、 と Garret Smith による補足[英語版].
英語版公開日時: 2010-03-04

オリジナルロシア語版: Dmitry A. Soshnikov [ロシア語版]
オリジナルロシア語版公開日時: 2009-09-12

本シリーズはすべて英語版からの訳出です。

こんにちは、opera 大好き 松岡 剛志 です。今日は部長職ではなくて、セキュリティチームリーダー立場でブログを書いています。

今回は弊社の様々なアプリケーションセキュリティの取り組みのうち、以下の4つのコンテンツについて書きます。この内容はほとんどが弊社のセキュリティイベントである scrap challenge で使われたスライドの焼き直しです。

  • トレーニング
  • セキュリティレビュー
  • コードレビュー
  • セキュリティチェック

トレーニング

現在ミクシィでは新卒エンジニアに対して1カ月程度の缶詰の教育を行っています。そのコンセプトは以下です。

  • 関係各所、チーム、チーム横断でのタスクに関して 迷惑をかけずに自分で判断できる/あるいは正しく判断を仰げる状態までの成長。
  • 現状の技術的問題点や課題を把握し、改善策や改善のためのプランニングができる。
  • 各項目への知識体系の羅針盤を提供して、自学自習によってより高度な領域まで発展することができる。

このため面白い特徴として「ミクシィでしか使えないこと」の教育は最小限にしています。この辺りはいずれtakaiやyuzuemonが書いてくれるでしょう。

この研修の中に以下のセキュリティのメニューがあります。

どれも可能な限り座学だけではなく、手を動かすことを意識しています。

セキュリティレビュー

私達は自分たちの企画が安全なはずないと考えています。

ミクシィでは社内で規定された一定の情報レベル以上を取り扱う時や担当者が鼻を利かせたとき、社内のセキュリティチームにレビューを行うというルールがあります。我々のサービスはお客様の大切な情報をお預かりしているので、かなり気をつけているところです。しかしながら多くの問題を起こしていることを恥ずかしくも思っております。

セキュリティレビューはマイクロソフト様のSTRIDEの視点で見ることが多いです。違う視点としては以下のような視点で見ることが多いです。

  • 処理するところ、保持するところの確認。
  • 通信経路の確認
  • プライバシーは守られているか?
  • 入出力情報は必要最低限か?
  • アビューズについて考えたか?
  • 3rd partyは絡んでいるのか?
  • etc

セキュリティレビューはJIRA(issue tracking systemの一種)で管理されています。ミクシィのその他のレビューもすべてJIRAで管理されています。これにより案件の漏れや期限の遅れを減らし、一つ一つの判断を判例として残しています。

このセキュリティレビューの重要な点として、必ずセキュリティ担当者が2名以上でチェックして全員がokしないと進めません。私達は自分たち自身(セキュリティ担当者)も信用していないのです。

コードレビュー

私達はミクシィのエンジニアの作るコードを信用しません。ゆえにミクシィのコードはすべてレビューを受けないと本番に反映されることが無いようにしています。悪意あるコードが混入するリスクのほかにコードの品質、統一感も重視します。

このレビューには様々なチェックポイントがあるのですが、セキュリティ面ではたとえば以下です。

  • CSRF対策のためのトークンを(付与|検証)しているか?
  • SQL文生成はプレースホルダを利用しているか?
  • 変数はエスケープして画面出力を行っているか?
  • 不要なパラメータは受け取らないか/出力しないか?

これはチェックしてないの?という指摘があると思います。それらはすでにフレームワークなどで対応して、開発者がいちいち気を配らなくてよいようにしています。近い将来3番目の要素はテンプレートエンジンの変更に伴い、コードレビューからは無くなるでしょう。

セキュリティチェック

私達はこの枠組みを信用しません。このためミクシィでは一定以上の条件を超えるものについて、たんぽぽのエンジニアや外部のセキュリティ会社による監査を受けています。

その条件とは以下です。

  • 課金やポイントに関するもの
  • 第三者と情報をやり取りするもの
  • 新規、大規模な変更

ものつくりの都合上、試験できる日程は良く変更になります。付き合ってくださっているセキュリティ会社様に日々感謝です。

その他

今回はあげませんでしたが、ほかにも様々な取り組みをミクシィでは行っています。たとえば運用よりのセキュリティとして、開発と運用の分離や情報レベルの高いものはセキュアネットワークに置くなどです。

まとめ

ミクシィではお客様の安全を守る一つの取り組みとして、このようなことを行っております。いたらぬことも多いですが、一つ一つ問題を解決し、皆様が安心して楽しむことのできるサイトを運営したいと考えています。

ここまで読んでいただきありがとうございました。