今日も読むー。
## Ch. 8 Functions
### 8.1 Defining Functions
関数宣言
function distance(x1, y1, x2, y2) { let dx = x2 - x1; let dy = y2 - y1; return Math.sqrt(dx*dx + dy*dy); }
- 関数につけた名前が変数名になる
- 関数宣言ステートメントはスクリプトや関数またはブロックの一番上に巻き上げられる(hoisted)。なので、定義してある場所より前でも呼び出すことができる。
- ES6 より前は JavaScript ファイルまたは関数内のトップレベルでの定義しか許可されていなかったが、ES6 の strict mode ではブロック内での宣言が可能となった。そのブロック内からのみ利用可能となる。
関数式
const square = function(x) { return x*x; };
- 関数名はオプショナル。関数宣言と違って、名前をつけても変数は登録されない。
- 名前をつけると関数内のローカル変数になるので、再帰呼び出しで利用できる。
- 関数式は hoist されないので定義前には利用できない。
アロー関数
- ES6
// 全部書くとこう const basic = (x, y) => { return x + y; }; // return ステートメントだけの場合は return と {} を省略できる const simple = (x, y) => x + y; // 引数が1つだけの場合は () を省略できる const oneParam = x => x*x; // 引数がない場合は () が必要 const noParam = () => 10; // オブジェクトリテラルを返したい場合は曖昧になるので普通に書くか const f = x => { return { value: x }; }; // () で囲む const g = x => ({ value: x });
- アローの前に改行を入れたらそこで終わってるって解釈されるからダメ
- アロー関数は
this
を持たないので関数が定義されたコンテキストのthis
を参照する
this
の扱いが一番大切そうだな。
### 8.2 Invoking Functions
- 関数は「関数」または「メソッド」として呼び出される
return
がなければundefined
が返される- 関数として呼び出された場合
this
は、non-strict mode なら global object、strict mode ならundefined
になる。- アロー関数は別。定義された場所の
this
を使う。 - 基本的には関数では
this
は使わない。
- メソッドは
- オブジェクトのプロパティとして保持されている関数
- そのオブジェクトを
this
で参照できる - メソッド内で定義した関数の中で
this
を使ってもメソッドのオブジェクトをは参照できないので注意。その場合は関数呼び出しとなるため。
"use strict"; let obj = { m: function() { console.log("mの中のthis=" + (this === obj)); f(); function f() { console.log("fの中のthis=" + this); } } } obj.m(); mの中のthis=true fの中のthis=undefined
f()
の中で this
を使いたい場合は
- アロー関数を使う
- 一度ローカル変数に入れてそれを参照する
this
をbind
する
そういうときはアロー関数が一番読みやすそうね。
### 8.3 Function Arguments and Parameters
デフォルト値
- デフォルト値の指定には以前は
a = a || []
のように||
オペレーターを使ってた - ES6 以降だとパラメーターにデフォルト値を指定できるのでそっちを使えばいい
- デフォルト式には定数やリテラル以外にも変数や関数呼び出しも使える。その前のパラメーターも使える。へー。
Rest Parameters
- ES6
- Rest Parameters を使うと、数の決まっていないパラメーターを配列で受け取ることができる
function sample(first, ...rest) { }
- 3つのピリオド
- 最後のパラメーターのみ
- 空の配列にはなりうるが、
undefined
になることはない
arguments
- ES6 より前は
arguments
オブジェクトを使っていたarguments
オブジェクトは array-like オブジェクト- 引数名ではなくインデックスで参照
古いコードを読むときに出てくるかも、くらいだな。
Spread Operator
- 関数呼び出し時に spread operator
...
を使用することも可能
let numbers = [5, 2, 10]; Math.min(...numbers);
Destructuring assignment
- パラメーターに destructuring assignment を使うことができる
// 配列 function vectorAdd([x1, y1], [x2, y2]) { return [x1 + x2, y1 + y2]; } vectorAdd([1,2], [3,4]); // オブジェクト function vectorMultiply({x, y}, scalar) { return { x: x*scalar, y: y*scalar }; } vectorMultiply({x: 1, y: 2}, 2); // 違う変数名で受け取る function vectorAdd2({x: x1, y: y1}, {x: x2, y: y2}) { return { x: x1 + x2, y: y1 + y2 }; } vectorAdd2({x: 1, y: 2}, {x: 3, y: 4}); // デフォルト値を設定することもできる function vectorMultiply2({x, y, z = 0}, scalar) { return { x: x*scalar, y: y*scalar, z: z*scalar }; } vectorMultiply2({x: 1, y: 2}, 2); // 配列の残りを rest parameter で受け取ることもできる // 本当の rest parameter とは別物 function f([x, y, ...coords], ...rest) { return [x+y, ...rest, ...coords]; } f([1,2,3,4],5,6); // => [ 3, 5, 6, 3, 4 ] // (ES2018) オブジェクトに対しても rest parameter が使える function vectorMultiply3({x, y, z=0, ...props}, scalar) { return { x: x*scalar, y: y*scalar, z: z*scalar, ...props }; } vectorMultiply3({x: 1, y: 2, w: -1}, 2); // => { x: 2, y: 4, z: 0, w: -1 } // オブジェクトと配列の混ざったやつもOK function drawCircle({x, y, radius, color: [r, g, b]}) { }
- object の destructured parameters を使うと、名前付き引数に似たものを実現できる
### 8.4 Functions as Values
- function はオブジェクトの一種なのでプロパティを持つことができる
f.counter = 0; function f() { return f.counter++; } f(); // => 0 f(); // => 1
んー。面白いけど、Closureでやれば良さそう。
### 8.5 Functions as Namespaces
これ、jQueryでよく見たやつかな。
- 関数内で定義されたローカル変数は外からは参照できないので汚さなくて済む
function chunkNamespace() { // ここに色々処理 } chunkNamespace(); // 呼び出しを忘れずに
でもこれだと chunkNamespace
という関数自体は登録されてしまうので、それもなくすとこうなる:
(function() { // 最初の丸括弧でこれが関数宣言じゃなくて関数式だとみなされる // ここに色々処理 }()); // 呼び出し
### 8.6 Closure
- JavaScript はレキシカルスコープなので、関数が定義された場所のローカル変数がバインドされる。そういうのを Closure と呼ぶ。
あぁ、さっきのやつの Closure 版でてきた:
let f = (function() { let counter = 0; return function() { return counter++; }; }()); f(); // => 0 f(); // => 1
this
を使いたいときはローカル変数にしてそれを参照することで、使うことができる。そういうときはだいたいself
とかthat
って名前を使うのかな?
const self = this;
### 8.7 Function Properties, Methods, and Constructor
call()
apply()
関数をあるオブジェクトに対するメソッドのように呼び出すことができる。第一引数に渡したオブジェクトが this
になる。
f.call(obj); f.apply(obj);
- アロー関数の
this
はレキシカルスコープを使うからcall()
やapply()
では上書きされない。 call()
は関数に渡したい引数をそのまま第二引数以降に指定するapply()
は関数に渡したい引数を第二引数に配列として指定する
f.call(obj, 1, 2); f.apply(obj, [1, 2]);
- ES5 までは spread operator がなかったから、任意の数の引数をとる関数に対しては
apply()
で配列を渡してた。 - ES6 以降だと spread operator があるから
call()
だけでも良さそうかな。
bind()
- 関数にオブジェクトをバインドして新しい関数を返す
this
はそのオブジェクトになる- バインド済みの関数をオブジェクトのプロパティに設定しても、既にバインドしてるオブジェクトに対して呼び出される
- アロー関数の
this
は上書きされない bind()
のよくある使い方は、アロー関数じゃない関数をアロー関数のようにしてしまうこと
でも bind()
メソッドには単にバインドする以上のことができる
bind()
の2番目以降の引数に値を渡すと、その引数もバインドされる(currying)
> let sum = (x,y) => x + y; undefined > let succ = sum.bind(null, 1); undefined > succ(2); 3
### 8.8 Functional Programming
読んで「面白いなー」と思ったけど、今はあんまり覚えなくていいかなぁと思ったので頭の片隅に入れとくくらいにしとく。
## Ch. 9 Classes
ES6 で class
キーワードが導入されたりして書きやすくなったけど、まずは古いスタイルからたどっていく。
### 9.1 プロトタイプ
- JavaScript でいう「クラス」は、同じプロトタイプオブジェクトからプロパティを継承しているオブジェクトのこと
Object.create()
でプロトタイプオブジェクトを指定すれば JavaScript のクラスができる
こんな感じ
function range(from, to) { let r = Object.create(range.methods); r.from = from; r.to = to; return r; } // 関数オブジェクトのプロパティとして定義してる range.methods = { includes(x) { return this.from <= x && x <= this.to; }, } let r = range(1,3); console.log(r.includes(2)); // => true
range()
がファクトリー関数になってるrange.methods
で定義してるメソッドの中でthis
を使って呼び出し元のオブジェクトにアクセスしてる
### 9.2 コンストラクター
- これで単純な方法でクラスを作ることができたので、次はコンストラクターを使うようにしてみる。
- コンストラクターで大切なのはコンストラクター関数の
prototype
プロパティが自動的にプロトタイプとして使用されるということ。
// Range オブジェクトのコンストラクター関数 // コンストラクター関数は最初を大文字で始めるのが慣習 function Range(from, to) { // new で呼び出されると自動的に作成されたオブジェクトが this に入ってて // それが戻り値として返される this.from = from; this.to = to; } // コンストラクター関数の prototype プロパティがプロトタイプとして使用される Range.prototype = { includes(x) { return this.from <= x && x <= this.to; }, } let r = new Range(1,3); console.log(r.includes(2)); // => true
instanceof
- 2つのオブジェクトのプロトタイプオブジェクトが同じだったら、それは同じクラスのインスタンスだということ
- コンストラクター関数が違っていてもプロトタイプが同じなら同じクラスだと言える
instanceof
はコンストラクター関数のprototype
プロパティをオブジェクトが継承している場合にtrue
を返す
r instanceof Range
- たとえ
Range
コンストラクター関数ではなくて、別のStrange
コンストラクター関数でインスタンス化されていたとしても、そのRange
とStrange
のprototype
プロパティが同じならtrue
が返される - コンストラクター関数を使わない最初の例のような書き方をしてる場合は
instanceof
が使えないが、その場合はisPrototypeOf
メソッドが利用できる
range.methods.isPrototypeOf(r);
constructor
プロパティ
- さっきは
Range.prototype
を定義したけど、JavaScriptの関数は最初からprototype
プロパティを持ってる - で、その事前に定義された
prototype
プロパティにはconstructor
というプロパティがあって、それが元の関数を指している - なので、普通の関数を
new
を使って呼び出したらconstructor
というプロパティが存在して、それがコンストラクターとして使用した関数を指すことになる
> let F = function(){}; undefined > let obj = new F(); undefined > obj.constructor === F; true
おー
- さっき定義した
Range
関数はprototype
プロパティを上書きしてるから、ちゃんとconstructor
プロパティを定義しておく方がいいい
Range.prototype = { constructor: Range, includes(x) { return this.from <= x && x <= this.to; }, }
- もうひとつ、古いコードで見られる方法は、事前に定義された
prototype
にメソッドを追加する方法
Range.prototype.includes = function(x) { return this.from <= x && x <= this.to; } Range.prototype.otherMethod = function() { // ... }
### 9.3 class
キーワード
ついにきた。ES6 で導入された class
キーワード。
class Range { constructor(from, to) { this.from = from; this.to = to; } includes(x) { return this.from <= x && x <= this.to; } } let r = new Range(1,3); console.log(r.includes(2)); // => true
これでさっきのやつと全く同じになる。
- コンストラクター関数は
constructor
というキーワードで定義するけどRange
という変数が使用されて、Range
というコンストラクター関数になる。 - また
constructor
プロパティにRange
関数が入ってる - メソッドの定義が複数ある場合でもカンマは不要
大切なこと2つ
class
定義の中は暗黙的に strict mode になるclass
定義は関数定義と違って hoist されない
Static Methods
static
キーワードをつけて static メソッドを定義できる- static メソッドは、プロトタイプオブジェクトのプロパティではなく、コンストラクターのプロパティとして定義される
Getter, Setter
- オブジェクトのときと同じようにして Getter と Setter を定義できる
フィールド
- ES6 ではメソッドの定義しか許可されていない
- フィールドを定義したい場合は、コンストラクター関数かメソッドの中で生成しないといけない
- static フィールドを定義したい場合は、クラスを定義した後に外側で定義しないといけない
class Range { constructor(from, to) { // フィールドを定義 this.from = from; this.to = to; } includes(x) { return this.from <= x && x <= this.to; } } // static フィールドを定義 Range.MIN = -100;
新たに標準化が進んでいる書き方
現在、フィールドに対する標準化が進んでるところ。こんな風にかけるようになる。
class Range { from = 0; to = 0; includes(x) { return this.from <= x && x <= this.to; } }
- フィールドの初期化をコンストラクターの外に書くことができる
- フィールドの初期値を書かなくても大丈夫。でも、その場合は
undefined
になるからちゃんと初期値を設定したほうがいい。
プライベートフィールドも定義できるようになる。 #
がプレフィックス。クラス内では使えるけど、外からは参照できない。
class Buffer { #size = 0; get size() { return this.#size; } }
static フィールドはフィールド宣言の前に static
キーワードをつければ良い。
眠くなってきたから今日はこれくらいにしとこ。次は 9.4 から。