jser class #1
TRANSCRIPT
JSer ClassJavaScriptの基礎と軽量フレームワーク
目的
• 一般的観点• Webアプリケーション開発のなかで存在感を増し続けるJavaScriptに
ついて、「なんとなくわかる」でない知識を身に付ける。
• JavaScriptのメリット、デメリット、代替技術について知ることで、保守/開発の生産性や品質を向上させる。
• 特殊的観点• 数カ月後に身近な存在となる某クライアントサイドMVCライクなアプ
リケーションの保守/開発のための基礎知識を得る。
開催概要
• 開催日時• 3/2(水)〜 毎週水曜 19:30〜21:30 全3回予定
• 会場• コラボレーションスペース(N・W)
• コンテンツ• 第1回 JavaScriptの言語仕様
• 第2回 DOMとXmlHttpRequest、軽量フレームワーク
• 第3回 クライアントサイドMVC
参考情報
• サイト• JavaScriptガイド@MDN
• https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide
• 書籍• JavaScript 第6版(サイ本)
• http://www.amazon.co.jp/dp/4873115736
• Effective JavaScript• http://www.amazon.co.jp/dp/4798131113
JSer Class #1JavaScriptの言語仕様
あらためてJavaScriptとは何か?
歴史
• その端緒は前世紀末のWWW民間利用解放後のブラウザ競争。
• そのなかで生まれたJavaScriptとJScript、そしてECMAScript。
• 実装と仕様• JavaScript(実装名) → Netscape社が開発(1995〜)
• JScript(実装名) → Microsoft社が開発(1996〜)
• ECMAScript(仕様名) → Ecma International(旧称・欧州電子計算機工業会)で策定(1997〜)
• はじめに実装ありき。あり方はSQLと似ている。
現状〜今後
• 当初• あくまでブラウザ上で何かしらの小規模な処理をこなすための言語
• 数年前(v3〜5)• Webサイトの高機能化にともないコード量が増大• ブラウザ間差異の吸収と言語拡張を目的としたライブラリが登場
• 現状(v5.1)• V8エンジンを独立ランタイムとして転用したNode.jsの登場• クライアントサイドMVCというデザイン・パターンの登場• TypeScript、CoffeeScript、ClojureScript、Scala.jsといった代替技術の登場
• 今後(v6〜)• ???
今、JavaScriptを学ぶ意味
なぜあらためて学ぶ必要があるか? 例えば─
• 活用シーンの多いから。
• ともすえばJavaやC#よりもリーディング力が必要だから。
• TypeScriptなど代替技術(上位技術)の挙動理解に重要だから。
• 「曖昧に済ませられる」言語で「きっちり動く」「メンテしやすい」アプリをつくるにはテクニックが余計に必要だから。
テキストの構成
• わりと行き当たりばったりに:かいつまんだJavaScript言語仕様の提示
注意すべきポイント、べき論の提示
サンプルコード、例題コードの提示
• 例題コードについて:示されるのは極限的状況。
それを解くことを通じて「臭う」コードを見分けられるようになってほしい。
JavaScriptには言語仕様上、JavaにおけるようなIDEは存在し得ない。
「自力でコードを読み解く」能力が重要になる。
学び方
• Web上の情報に目を通す• MDNなど。あくまで入門。体系的理解を期待しないこと。
• 書籍を読む• 前掲書をおすすめ。
• Webブラウザで試してみる• ブラウザが内蔵する開発者ツール、Firebugのような拡張機能、
JSFiddleのようなWebサイトを利用する。
JavaScriptにおけるOOP
OOPとは?
• Object Oriented Programming。
• プログラムの処理(関数)とそれに関係性の強いデータとを「オブジェクト」という単位にまとめることで、プログラムの生産性を高めようという思想=実践。
• カプセル化は求められていない点に注意。カプセル化はプログラムの疎結合化を推進するための別の思想=実践。FPでも有効な概念。cf.Haskell、Erlangにおけるエクスポート。
オブジェクト
データ 処理(関数)
補足)FPとは?
• Functional Programming。
• OOPの反対に、関数とデータを完全に切離し、関数の高機能化をはかることで、プログラムの安全性や保守性の向上、並列分散処理系実装の容易化を推し進めようとする思想=実践。
• やや「実験場」的で難解。高階関数、クロージャ、ケース式などOOPにも応用できる技術が次々と輸出されている。
• 例)Haskell、Erlang、Lisp(およびその方言)
OOPの種類
• クラス・ベースのOOP• JavaやC#、Python、Rubyなどはこちらに該当• オブジェクトの「かたち」をクラスであらかじめ宣言• クラスのかたちでオブジェクト(インスタンス)のかたちも決まる
• プロトタイプ・ベースのOOP• JavaScriptはこちらに該当(ていうかそれ以外にあるのか?)• 「かたち」はすべて実行時に動的に決定される• プロトタイプ(原型)として指定されたオブジェクトがメンバー
(フィールドやメソッド)の継承元となり、それらと自分自身が備えたメンバーでそのオブジェクトの「かたち」が決まる。
プロトタイプ・チェーン
• あるオブジェクトにメンバ(フィールド/メソッド)が存在するか確認する際に利用される概念。
• メンバ検索の順序:• あるオブジェクトAそのものがメンバを持つかチェック
• オブジェクトAのコンストラクタのprototypeフィールドが指すオブジェクトBがメンバを持つかチェック
• オブジェクトBのコンストラクタのprototypeフィールドが…(中略)
• Object.prototypeが指すオブジェクトXがメンバを持つかチェック
foo bar baz
Foo.prototype Bar.prototype
かたち(型)の扱い方
• 型とは何か?• プリミティブもオブジェクトも区別せず、味も素っ気もない言い方を
すれば「メモリ上に確保されるサイズとそのフォーマット」。
• 静的型付け• データには型がある(当たり前)。
• 変数・引数・フィールドにも型がある(これが特徴)。
• 動的型付け• データには型がある(〃)。
• 変数・引数・フィールドには型がない(これが特徴)。
クラスvsプロトタイプ静的型付けvs動的型付け
言語 クラスvsプロトタイプ 静的vs動的 ジェネリクス
JavaScript プロトタイプ
動的 ×
Python/Ruby
クラスJava
静的
△(イレイジャ)
C# ○(非イレイジャ)
なぜ動的&プロトタイプになった?
• そもそもは「ちょっとした処理」しか想定していなかった。
• Webサイトはいろいろなリソースの寄せ集め。動的協働のためには、バージョン問題を惹起するクラスは厄介。
cf.ECMAScript v6におけるクラスとモジュール導入。
cf.Erlangにおける動的型付けの意義。cf. TypeScriptにおける「構
造的部分型」の導入
動的型付けで得られるもの
• ダックタイピングの自由(あるいは責務)• どんな型のデータでも受け取れる・代入できる、ということは取りも
直さず、どんな型のデータでもきちんと処理できなくてはならない。この規則に例外はない。
• コンパイラやIDEの助けは得られない。
• 多重定義メソッドからの解放(もしくは追放)• 受け取るデータの型ごとに多重定義メソッドを宣言しなくてよい。と
いうか、やりたくてもできない。
• コンパイラやランタイムが適切なメソッドを選択してくれていたのと同じことを、開発者が自分のコードで行わなくてはならない。
プロトタイプベースで得られるもの
• クラスやインターフェース宣言の省略• どうせ変数が動的型付けなら、データの側もあらかじめかたちが決
まっている必要はない(やや投げやり)。
• メモリの節約• 継承したいオブジェクトがいるなら、直接プロトタイプとして参照し
てしまえばいい。そうすれば個別インスタンスごとにextendsしたクラスのフィールド分までメモリを消費したりしないで済む。
• 代償としてJava/C#にも存在する「継承による強結合」の病弊がもう一段深刻なかたちで発現する点に注意。
ちょっとまとめ
• JavaScriptはそのビルディング・ブロックからして他のメジャーな言語とはちがう。
• これはそれ自体「悪」というわけではなく、そもそもの目的や設計思想によるもの。
• しかし利用方法の変化は以下の諸問題を顕在化させた:• 静的型付けとクラスが提供する事前検証の一切が利用できない
• オーバーロード、ジェネリクスなどの可能性も根本から絶たれている
• IDEはせいぜい予約語の補完くらいしか開発者をサポートできない
• コード量が多くなるにつれてコーディングは煩雑で危険になる
• 複数ソースのスクリプトの混在でセキュリティ・リスクが増大する
オブジェクト・グラフ
基本型(プリミティブ)
• boolean いまさら説明不要。
• number 整数と浮動小数点数の区別はない。
• string 文字列はJava同様イミュータブル。
• null 基本型なのにtypeof nullは”object”を返す。
• undefined 未初期化・未設定を示す。なぜか非予約語。
特殊値null
• nullという型の唯一の値/インスタンスがnull。
• Javaなどの「オブジェクト参照がない」という意味ではない。
• 定義上変数や配列の要素のスロット、関数の戻り値はあるのだが、設定すべき値がないときに、nullを明示的に利用する。
特殊値undefined
• 代入まえの変数、指定されなかった引数、存在しない添字アクセスに対する戻り値、return文のない関数の戻り値などで登場。
• 強制的に値を生み出したいときはvoid演算子を使う。
• 非・予約語なので変数宣言により上書きが可能…。
// Java
String hello;
System.out.print(hello);
// JavaScript
var hello;
console.log(hello);
暗黙型変換
• Javaも含む他の多くの言語と同様に暗黙型変換が行われるケースがある。しかしその規則はわかりにくい。
• わかりにくい規則を覚えることを考えるまえに、そのようなコーディングを原則禁止とすることを考えるべきである。
• ==演算子は暗黙型変換を生起する。代わりに===を使う。
• ifの条件式、&&や||の被演算子にboolean以外を指定すると暗黙型変換(ぽい何か)が起こる。以下6値はfalse(ぽい):• 0, -0, ””, NaN, null, undefined
暗黙型変換
// JavaScript
var a = 0;
var b = ”0”;
var c = + ”0”;
var d = + ”a”;
console.log(a == b); // (1)
console.log(a === b); // (2)
console.log(a === c); // (3)
console.log(a || false); // (4)
console.log(a === NaN); // (5)
この変換方法は非常にしばしば利用されている。しかしdの例が示すように危険性も大きく、「文字列が数字の並びだけでできている」という前提なしに
利用してはならない。
暗黙型変換ですらない何か
// JavaScript
var a = 0;
var b = ”0”;
console.log(a || b); // (1)
console.log(b || a); // (2)
console.log(a && b); // (3)
console.log(b && a); // (4)
オブジェクト型(例)
• Object オブジェクト(としか言いようがない何か)
• Array 配列(ないしマップ)
• Arguments 「配列っぽい」オブジェクト
• Function 関数オブジェクト
• RegExp 正規表現オブジェクト
• String 基本型stringに対応するオブジェクト
• Number 基本型numberに対応するオブジェクト
• Boolean 基本型booleanに対応するオブジェクト
JavaScriptのObjectの特徴
• JavaでいえばMap<String,Object>みたいなもの(*)。
• キーと値で表現されるプロパティのセット。
• JavaScriptでは関数もオブジェクトの一種に過ぎないから、フィールドとメソッドの区別は存在しない。
• Mapと異なるのはプロトタイプ・チェーンを備えていること。
* 類似性の観点でいうと、Typesafe社のTypesafe Configオブジェクトがもっと適当(参考記事:http://m12i.hatenablog.com/entry/2015/08/17/004038)
オブジェクトの生成(1)リテラルで記述する// JavaScript
var foo = { bar: 123, baz: ”456” };
console.log(foo.bar); // (1)
console.log(foo.baz); // (2)
console.log(foo.foo); // (3)
オブジェクトの生成(2)コンストラクタで記述する// JavaScript
var Foo = function() {
this.bar = 123;
this.baz = "456";
};
Foo.prototype = { foo: "789" };
var foo = new Foo();
console.log(foo.bar); // (1)
console.log(foo.baz); // (2)
console.log(foo.foo); // (3)
プロパティにアクセスする
A) ドット表記(obj.prop)
B) 添字表記(obj["prop"])
// JavaScript
var obj = { foo: "123", "b a r": 456, "789": "baz" };
console.log(obj.foo); // (A)
console.log(obj["foo"]); // (B)
console.log(obj["b a r"]); // (C)
console.log(obj["bar"]); // (D)
console.log(obj[789]); // (E)
プロトタイプ・チェーン(再掲)
prop0: 123
// JavaScript
console.log(foo.prop2);
console.log(foo.prop3);
foo// JavaScript
foo.prop2 = 987;
console.log(foo.prop2);
prop2: 987
プロパティ参照時はチェーンを遡る
プロパティ更新時はチェーンを遡らない
// JavaScript
var Foo = function() {
this.bar = 123;
this.baz = "456";
};
Foo.prototype = { foo: "789" };
プロトタイプを通じた参照共有
配列の生成と要素アクセス
// JavaScript
var arr = [1, 2, 3];
console.log(arr[1]); // (A)
console.log(arr["1"]); // (B)
arr["2"] = arr["2"].toString();
console.log(arr[2]); // (C)
arr.length = 2;
console.log(arr[2]); // (D)
for ... in 文
• Javaの拡張for文に似ているが、別物。
• inの左辺の変数にはinの右辺のオブジェクトのプロパティ名が設定される。
• プロパティの検索はプロトタイプ・チェーンを遡って行われる(toStringなどの組込みプロパティをのぞく)。
• プロパティ名は例え右辺が配列であろうとstring型。
in演算子とhasOwnPropertyメソッド
• in演算子• あるプロパティ名(左辺)があるオブジェクト(右辺)に存在するか
どうかを判定する。
• 存在判定に際してプロトタイプ・チェーンを遡る。
• hasOwnProperty(String)• あるプロパティ名(第1引数)があるオブジェクト(レシーバ)に存在
するかどうか判定する。
• 存在判定に際してプロトタイプ・チェーンを遡らない。
String/Number/Booleanコンストラクタとしての利用は禁止• ラッパーを使ってオブジェ
クトを生成していはいけない。
• メモリの無駄というだけでなく、同値比較(===)が機能しない。
// Java
String a0 = ”a”;
String a1 = ”a”;
String a2 = new String(”a”);
// JavaScript
var a0 = ”a”;
var a1 = ”a”;
var a2 = new String(”a”);
ユーティリティとしての利用やオートボクシングの利用はOK• Java同様ユーティリティ・ク
ラスとしての機能は便利。
• 基本型に対してメソッド呼び出しを行うと自動的にラッパーが生成され、すぐに破棄される。
// JavaScript
Number.isNaN(100);
(100).toExponential();
”hello”.replace(”l”, ”r”);
実態はどうあれ仕様上はstringはあくまでも基本型であり、メソッドは持たない。
モンキー・パッチは禁止
• 以下の前提のもとに組込みオブジェクトを「自分好み」に拡張してしまうこと:• JavaScriptでは一般に定数もしくは再代入不可の変数をサポートして
いない。
• そしてJavaScriptではオブジェクトの鋳型としてのクラスは存在しない(〜v5.1)。
• よって組込みオブジェクトを含むほとんどあらゆるものは変更可能である。
蛇足)Rubyにおけるクラス
• RubyはクラスベースのOOP言語。
• しかしクラスは実行時に動的に拡張することができる。
• それどころか継承ツリーに割り込みをかけることすらできる。
• そしてJavaScript同様標準ライブラリを変更できる。
• この言語の開発者がクラス概念を採用した理由が分からない。
ちょっとまとめ
• JavaScriptのプリミティブ:• 種類が少なく覚えるのは簡単。
• ただしundefinedやNaNという特殊値の扱いには注意。
• 暗黙型変換はきちんと覚えようとすると大変。
• JavaScriptのオブジェクト:• 構造的には非常にシンプル。
• ただし曖昧さは無限大。
関数を定義する
オブジェクトとしての関数
• JavaScriptでは関数はオブジェクトである。cf. C#のデリゲート、Java8のクロージャ、Rubyのブロック。
• したがって関数を格納した配列、関数を引数にとる関数、関数を戻り値とする関数をつくることができる(高階関数)。
• 関数はそれが定義されたスコープに存在した変数への参照を保持(束縛)する(後述)。
• 定義方法がいろいろある。
関数を定義する(1)Functionコンストラクタ関数• 引数名も関数本体も文字列として渡す。本体はeval()で評価さ
れる。このため関数定義のエンクロージング・スコープを参照できない。つまりクロージャとなり得ない。
• ふつう使わない。使うべきでもない。
関数を定義する(2)function文• その名の通り関数を定義するための文(statement)。
• あるスコープでfunction文で定義された関数は、スコープの他のすべての手続きが行われる前に評価される(「巻き上げ」と呼ばれる現象)。つまりコード上より後方で宣言された関数を、より前方にある手続きで使用できる。なおあるスコープでfunction文で定義された関数は、スコープの外からは参照できない。
関数を定義する(3)匿名function式• 手続きを表す文ではなく、値を返す式(expression)で定義。
• 式を書ける場所ならどこにでも書ける。
• オブジェクトとしての関数を強調する構文。
関数を定義する(4)名前付きfunction式• 3つ目と異なるのは定義された関数に名前が付いており、自分
自身を再帰呼出するときなどに使用できること。
• 仮にエンクロージングスコープで同じ名前が使用されていてもこれをシャドウ化する。
• ECMAScript v3仕様/実装では、関数ボディのスコープでプロトタイプ・チェーンが発生するため危険。使うべきでない。
存在意義の疑われる仮引数(1)
• JavaScriptでは関数の仮引数を宣言できる(当たり前)。
• しかし引数の数はすべて実行時に決まる。
• 仮引数がなくても引数は受け取られる。
• いずれにせよ引数には関数ローカルスコープに自動定義されるargumentsを介してアクセスできる。
• 引数が設定されなかった仮引数にはundefinedが設定される。
存在意義の疑われる仮引数(2)
// JavaScript
var foo = function(arg0, arg1) {
console.log(arg1);};
var bar = function() {
console.log(arguments.length);
console.log(arguments[0]);};
foo(123); // (A)
bar(456); // (B)
bar(456, undefined); // (C)
関数とスコープ
スコープとは?
• 変数/定数が参照できる範囲を制限するもの。スコープ外の変数や定数を参照することはできない。
• 視点を変えると、ある変数/定数をコード上に記載した時、その変数/定数はスコープという限定された範囲から検索される。
• 同じ名前空間で同じ名前の変数を宣言・代入すれば、上書きが発生してしまう(Java・C#ではコンパイルエラーになる)。
• スコープはスタック+α。
• スコープがどのような入れ子構造をとるか、どのように生成されるか、どのような寿命を持つか・・・は、言語により異なる。
JavaScriptのスコープ
• グローバル・スコープが存在する。
• 関数がスコープを形成する。
• ブロック・スコープは存在しない。
• パッケージは存在しない。
• スコープの変数≒オブジェクトのプロパティのケースがある。
• スコープが閉じても変数が延命するケースがある。
グローバルスコープが存在する
• <script>タグのすぐ内側や*.jsファイルのすぐ内側。
• スクリプト・ローカルの考え方はないので名前衝突の危険大。
• Java・C#でいえばデフォルトpackageに近い。≠internal
// foo.html
<script type="text/javascript">
var foo = 123;
console.log(foo);
</script>
グローバル・スコープ
関数がスコープを形成する
• 関数がスコープを形成する。
• そしてこれが明示的にスコープ形成を行う唯一の手段。
• Java・C#におけるthisなしのインスタンス/静的変数参照などは×。
// JavaScript
var foo = 123;
var bar = function() {
var foo = 456;
};
グローバル・スコープ
ローカル・スコープ
スコープ・チェーン
• JavaScriptのスコープ・チェーンは重層構造を持つ。
グローバルスコープ
グローバルで宣言された関数のローカルスコープ
ローカルで宣言された関数のローカルスコープ
ローカルのローカルスコープで変数が検索され、存在しなければより外側のスコープから検索され
る。
変数宣言には必ずvarを使う
// JavaScript
var foo = 123;
var bar = function() {
console.log(foo); // (A)
foo = 456;
console.log(foo); // (B)
var foo = 456;
console.log(foo); // (C)
};
bar();
console.log(foo); // (D)
ブロック・スコープは存在しない
• JavaScriptでは「まるでブロック・スコープがあるかのうような変数宣言」ができる。
• しかしブロック・スコープは存在しない。cf.Java。
// JavaScript
var i = "je";
for (var i in [1,2,3]) {
console.log(i); // (A)
}
console.log(i); // (B)
パッケージは存在しない
• Javaでいうところのpackage、C#でいうところのnamespace、Rubyでいうところのmoduleといった概念が存在しない。
• 代わりにオブジェクト・プロパティ・チェーンが利用されることがある。
// JavaScript
var pkg0 = {};
pkg0.pkg1 = {};
pkg0.pkg1.foo = 123;
pkg0.pkg1.bar = function() { ...; };
スコープの変数≒オブジェクトのプロパティというケースがある• グローバル・スコープで変数宣言することは、windowオブ
ジェクトにプロパティを追加することにほかならない。
• ほかにも「名前付きfnction式」(後述)のような特殊な動きをするケースがある。
// JavaScript
var foo = 123;
console.log(foo); // (A)
console.log(window.foo); // (B)
スコープ・チェーン(再掲+α)
• JavaScriptのスコープ・チェーンは重層構造を持つ。
グローバルスコープ
グローバルで宣言された関数のローカルスコープ
ローカルで宣言された関数のローカルスコープ
名前付きfunction式の関数のプロパティ
名前付きfunction式で宣言された関数では、なぜか同関数のプロパティがま
ず検索されるローカルのローカルス
コープで変数が検索され、存在しなければより外側のスコープから検索され
る。
スコープが閉じても変数が延命するケースがある• 関数が終わればローカル・スコープは閉じる。
• しかし関数Aで定義されたローカル・スコープ(a)内で定義された関数Bがあったとき、関数Bで定義されたローカル・スコープ(b)から(a)の変数αを参照していると、(a)が閉じたあともその変数だけは引続き延命して、関数B実行のたびに参照・更新ができる状態になる。
• これを「関数Bが変数αを束縛する」とか「関数Bはクロージャである」とか表現する。
スコープが閉じても変数が延命するケースがある// JavaScript
var A = function() {
var a = "foo";
var B = function(arg0) {
console.log(a);
a = arg0;
console.log(a);
};
return B;
};
// JavaScript
var B = A();
B("bar"); // (1)
B("baz"); // (2)
var B2 = A();
B2("boo"); // (3)
匿名関数でスコープをつくる
• 匿名関数式で関数を定義して即座にこれを呼び出す。するとグローバル・スコープの汚染/被汚染を気にせずに済む自分だけのローカル・スコープを手に入れられる。
// JavaScript
var foo = 123;
(function(){
// anonymous namespace
var foo = 456;
...
})();
ちょっとまとめ
• JavaScriptのスコープは、Javaなどと比べて簡素。
• ようするにグローバルとローカルしか存在しない。
• JavaScriptにはpackageやnamaspaceの概念がない(〜v5.1)ので「名前空間汚染」は常時・深刻な問題となる。
• 「クロージャ」はJavaの内部クラス同様(それ以上)に便利な概念。ただしちょっと難解。
• JavaScriptの関数はスコープ・チェーンのかなめとなる。
• スコープ・チェーンにはときどきプロトタイプ・チェーンが関与する。
コンストラクタとthis
コンストラクタとしての関数
• 関数にnewをつけて呼びだすとコンストラクタとして機能する。
• このとき関数本文内のthisは新しく生成されたオブジェクトを指す。
// JavaScript
var Foo = function(arg0) { this.bar = arg0; };
var foo = new Foo(123);
console.log(foo.bar); // (A)
Foo(456);
console.log(bar); // (B)
メソッドとしての関数
• オブジェクトのプロパティに設定された関数が、オブジェクトをレシーバとして実行されるとメソッドとして振る舞う。
• このときthisはレシーバ・オブジェクトを指す。
// JavaScript
Foo.prototype.baz = function() {
console.log(this.bar);
};
var foo = new Foo(123);
foo.baz(); // (A)
// JavaScript
var baz2 = foo.baz;
var bar = 456;
baz2(); // (B)
曖昧なthis(1)
関数の状況 関数の機能(役割)とthisが指すオブジェクト
グローバルスコープで定義されたコンストラクタ
グローバルスコープで定義された関数で、new演算子ありで実行……関数はコンストラクタ関数として機能。関数はコンストラクタ関数として機能し、thisはObject.prototypeからプロパティを継承する新しいオブジェクトを指す。明示的にreturn式が実行されない限りこのthisが呼出元に返される。
グローバルスコープで定義された関数
グローバルスコープで定義された関数で、new演算子なしに実行……thisはwindowを指す。
プロパティ参照されたコンストラクタ
オブジェクトのプロパティに設定された関数で、new演算子ありで実行……関数はコンストラクタ関数として機能。1つ目と同じ。
プロパティ参照された関数(メソッド)
オブジェクトのプロパティに設定された関数で、new演算子なしに実行(メソッドとして実行)……thisはオブジェクトを指す。
曖昧なthis(2)
関数の状況 関数の機能(役割)とthisが指すオブジェクト
ローカルで定義されたコンストラクタ
ローカルスコープで定義された関数で、new演算子ありで実行……関数はコンストラクタ関数として機能。1つ目と同じ。
ローカルで定義された関数
ローカルスコープで定義された関数で、new演算子なしに実行……thisはwindowを指す。エンクロージングスコープを形成する関数がメソッドであり、そのthisがメソッドの所属するオブジェクトを指していようとも、そのスコープで定義された関数のthisはwindowを指す。
* この2スライドで取り上げた関数のパターンは右の参考記事でも解説している:http://m12i.hatenablog.com/entry/2013/08/12/000858
*
補足)板挟み状態の再帰関数論
• arguments.calleeは「今呼び出されている関数自体」を指す。
• 過去において再帰関数の定義にはこのプロパティが用いられた。
• しかし現在は代わりに名前付きfunction式を用いるべきとされる(*)。これは「末尾再帰最適化」の妨げとなるためだとか。
• ところが名前付きfunction式で宣言された関数のスコープ・チェーンの最前面には関数オブジェクト自身のプロパティ(プロトタイプから継承したものも含む)が来る(v3)。
• よってこれはこれで危険。再帰関数の実装において、匿名関数の利用はあきらめるべき・・・か。
* なぜarguments.calleeの利用が非推奨となったかの説明はMDBの右のページに解説がある:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments/callee
call/applyによるレシーバ差し替え
• 前述のとおり関数内のthisの意味はその宣言方法と呼び出し方法の組み合わせで決まる。
• ところでJavaScriptには意図的にレシーバ(this)の差し替えを行う方法が用意されている。
// JavaScript
Foo.prototype.baz = function() {
console.log(this.bar);
};
var foo = new Foo(123);
var obj = {bar: 456};
// JavaScript
foo.baz.apply(obj, []);
関数オブジェクトへの参照
thisに参照させたい値
関数の引数(配列形式)
補足)安全なthis
• 実用的ではないがとにかく安全性を求めなくてはならないケースでは、call/applyを使ってthisを無効化する手法をとることができる。
// JavaScript
Foo.prototype.baz = function(v) {
this.bar = v;
};
var foo = new Foo(123);
foo.baz.apply(undefined,["456"]);
ちょっとまとめ
• JavaScriptの関数は、関数、メソッド、コンストラクタを兼ねる。
• thisの扱いはとても微妙で、使いどころに注意する必要がある。
まとめ
まとめ
• 繰り返しになるが「自力でコードを読み解く」能力は重要。
• JavaScriptの言語要素はとても少ない。しかしそれらを組み合わせたときの挙動は予測しづらい面がある。
• そのためにプロトタイプ・チェーンとスコープ・チェーン、そしてthisの振る舞いをきちんと理解しておかなくてはならない。
次回は
• 以下の項目について取り上げる予定:• DOM(Document Object Model) API
• XmlHttpRequestオブジェクト
• 軽量フレームワーク(prototype.js、jQuery)