# JavaScript 再入門 #4 ###### tags: `JavaScript` - 参考: [【JS】初級者から中級者になるためのJavaScriptメカニズム](https://www.udemy.com/course/javascript-essence/) ## コンストラクター関数 - 新しくオブジェクトを生成するための、雛形となる関数のこと - `new` でオブジェクトを生成することを **インスタンス化** と呼び、 生成されたオブジェクトを **インスタンス** と呼ぶ - コンストラクター関数は一般的にコンストラクターであることを明示的にするために最初の文字を大文字にする ```javascript // コンストラクター関数 function A() { this.prop = 1; } // インスタンスの生成 const obj = new A(); ``` ## prototype - オブジェクトに存在するプロパティーであり、コンストラクターが持つ `prototype` はそのコンストラクターから生成されたインスタンスにも存在する。このとき、コンストラクターもインスタンスも、参照している `prototype` は同一のものである ```javascript function Person(name, age) { this.name = name; this.age = age; } Person.prototype.hello = function() { console.log("hello " + this.name); } const bob = new Person("BOB", 20); const tom = new Person("TOM", 21); const sam = new Person("SAM", 22); bob.hello(); tom.hello(); sam.hello(); ``` ## `new` 演算子 - コンストラクター演算子からインスタンスを生成するための演算子 - コンストラクター関数の戻り値(`return` されるもの)がオブジェクトの場合 - オブジェクトを返す - コンストラクター関数の戻り値がオブジェクト以外の場合、または定義されていない場合 - コンストラクターの `prototype` のプロパティがオブジェクトの `__proto__` にコピーされる それから、コンストラクター内で使用している `this` を呼び出し元のオブジェクトに返す ```javascript // コンストラクター関数 function A() { this.prop = 1; } // インスタンスの生成 const obj = new A(); ``` ## `instanceof` 演算子 - どのコンストラクターから生成されたオブジェクトかを確認する演算子 ```javascript function F(a, b) { this.a = a; this.b = b; } F.prototype.c = function() {} const instance = new F(1, 2); console.log(instance instanceof F) //-> true ``` ```javascript function F(a, b) { this.a = a; this.b = b; // return のオブジェクト部分は内部的には以下のような処理が行われている // const result= new Object(); // result.a = 1; return {a: 1}; // そしてこのオブジェクトを return すると… } F.prototype.c = function() { } const instance = new F(1, 2); console.log(instance instanceof F) //-> false ``` - `instanceof` を使うことにより以下のような動作が実現できる - 渡された引数のコンストラクターの種類により条件分岐に使う…など ```javascript function fn(arg) { if(arg instanceof Array) { // 引数で渡ってきたのが配列である場合 arg.push("value"); } else { // それ以外の場合(オブジェクトが渡ってきた場合) arg["key"] = value; } console.log(arg); } fn([]); //-> ["value"] ``` ## 関数コンストラクター - `new Function()` で関数を生成できる - あくまで関数を生成するためのもので、単純なオブジェクト生成ではない ```javascript // 関数コンストラクターから関数を生成 const fn1 = new Function("a", "b", "return a + b"); const result = fn1(1, 2); console.log(result); //-> 3 // 通常の関数の定義 function fn2(a, b) { return a + b; } console.log(fn2 instanceof Function); //-> true ``` ```javascript let d = 0; function fn() { let d = 1; const fn1 = new Function( "a", "b", "return a + b * d" ); return fn1; } const f = fn(); // f に代入するとスクリプトスコープを参照するように const result = f(1, 2); console.log(result); //-> 1 ``` ```javascript // new Function() はあくまでも関数を作成するためのコンストラクターである // なので、出力は以下のようになる const obj = new function() { this.a = 0; } const fn3 = new Function("this.a = 0"); console.log(obj, fn3); //-> {a: 0}, f anonymous() {this.a = 0} ``` ```javascript // 関数コンストラクターから生成された関数から new で生成すると以下のようになる const obj = new function() { this.a = 0; } const fn3 = new Function("this.a = 0"); const obj3 = new fn3(); console.log(obj, obj3); //-> {a: 0}, {a: 0} ``` ## プロトタイプチェーン - プロトタイプの多重形成のこと - `__proto__` がいくつも連鎖している状態 - 最上層のオブジェクトから該当のプロパティを探していき、見つからない場合は次の下層にある `__proto__` から探していく。すべて探して該当しなければ `undefined` となる ```javascript function Person(name, age) { this.name = name; this.age = age; } Person.prototype.hello = function() { console.log("Person: hello " + this.name); } Object.prototype.hello = function() { console.log("Object: hello " + this.name); } const bob = new Person("Bob", 18); bob.hello(); //-> Person: hello Bob ``` ```javascript function Person(name, age) { this.name = name; this.age = age; } // Person.prototype.hello = function() { // console.log("Person: hello " + this.name); // } Object.prototype.hello = function() { console.log("Object: hello " + this.name); } const bob = new Person("Bob", 18); bob.hello(); //-> Object: hello Bob ``` ## `hasOwnProperty` と `in` - `hasOwnProperty` は自身のオブジェクト内にプロパティを持つかどうか調べられるもの - 継承したもの(プロトタイプチェーン)は持っていない扱いとなる ```javascript function Person(name, age) { this.name = name; this.age = age; } Object.prototype.hello = function() { console.log("Object: hello " + this.name); } const bob = new Person("Bob", 18); const result1 = bob.hasOwnProperty("name"); const result2 = bob.hasOwnProperty("hello"); console.log(result1, result2); //-> true, false ``` - `in` は `hasOwnProperty` と同様にオブジェクトが持つプロパティを調べられるが、すべての継承元(プロトタイプチェーン)まで辿る性質を持つ ```javascript function Person(name, age) { this.name = name; this.age = age; } Object.prototype.hello = function() { console.log("Object: hello " + this.name); } const bob = new Person("Bob", 18); const result1 = "name" in bob; const result2 = "hello" in bob; console.log(result1, result2); //-> true, true ``` ## プロトタイプ継承 - 別のコンストラクター関数のプロトタイプを受け継いで、機能を流用することができるもの - コンストラクター間で機能を受け継ぐことができるので頻繁に使う機能を効率的に記述することができる - 単に **継承** という言葉もあるが、これは別のコンストラクター関数を受け継ぐことを言う - プロトタイプ継承も継承のひとつである - プロトタイプ継承はいくつか手法があるが今回はそのうちひとつを見ていく ```javascript function Person(name, age) { this.name = name; this.age = age; } Person.prototype.hello = function() { console.log("hello " + this.name); } function Japanese(name, age) { } // Object.create の第 1 引数に渡した Person.prototype を // prototype に持つ中身が空のオブジェクトを作成し、 // その後、その prototype を目的のオブジェクトの prototype に格納している Japanese.prototype = Object.create(Person.prototype); ``` ```javascript function Person(name, age) { this.name = name; this.age = age; } Person.prototype.hello = function() { console.log("hello " + this.name); } function Japanese(name, age) { Person.call(this, name, age); } Japanese.prototype = Object.create(Person.prototype); const taro = new Japanese("Taro", 23); taro.hello(); //-> hello Taro Japanese.prototype.hello = function() { console.log("yahho " + this.name); } taro.hello(); //-> yahho Taro ``` ## クラス - コンストラクター関数をクラス形式で書けるようにしたもののこと - JavaScript では ES 6 で機能が実装された - JavaScript のクラスやアロー関数などのように、既にある機能をより簡単な記述で表現できるようにしたもののことを **シンタックスシュガー(糖衣構文)** と呼ぶ ```javascript class Person { constructor(name, age) { this.name = name; this.age = age; } hello() { console.log("hello " + this.name); } } const bob = new Person("Bob", 23); bob.hello(); //-> hello Bob ``` ## クラス継承 - 他のクラスのプロパティーとメソッドを継承すること ```javascript // Person クラスを定義 class Person { constructor(name, age) { this.name = name; this.age = age; } hello() { console.log("hello " + this.name); } } // Japanese クラスを Person クラスから継承しつつ定義 class Japanese extends Person { constructor(name, age, gender) { super(name, age); // super 関数で元のコンストラクターから呼び出せる this.gender = gender; } hello() { console.log("yahho " + this.name); } bye() { console.log("bye " + this.name); } } const taro = new Japanese("Taro", 23); taro.hello() //-> yahho Taro taro.bye() //-> bye Taro ``` ## `super` - 継承元の関数を呼び出すためのものである - 関数コンテキスト内で使用する関数だが使用条件は限られ、基本的にクラス内で使用するものと考えてよい - オブジェクトリテラル内でのみ使用できる - 関数リテラル内で使おうとするとエラーになる ```javascript const bob = { name: "Bob", hello() { super.hello(); } } bob.bye = function() { super.hello(); //-> 構文エラーになる } ``` - 継承元のプロパティを `super` で呼び出す際には、継承先のプロパティ宣言よりも先に記述する必要がある - 継承元のメソッドを `super` で呼び出す際には記述の順序は関係ない ## ビルトインオブジェクト - コードの実行前に JS エンジンにより自動的に生成されるオブジェクトのこと - `String`, `Object`, `Number`, `Function`, `Math`, `Boolean`, `Date`, `Symbol` ...etc ```javascript console.log(window.Array); //-> f Array() {native code} // 別のプログラミング言語で生成されるものなので // Chrome の Dev tool を使って詳細に // ビルトインオブジェクトの中身を確認することはできない ``` ```javascript const arr = new Array(1,2,3,4); console.log(arr); //-> [1,2,3,4] console.log(arr[0]); //-> 1 ``` ## ラッパーオブジェクト - プリミティブ値を内包するオブジェクトのこと ```javascript const a = new String("hello"); console.log(a.toUpperCase()); //-> HELLO const b = new Number(100); console.log(b.toExponential()); //-> 1e+2 ※10の2乗 const c = "hello"; // String でオブジェクトを生成するのと同じ挙動 console.log(c.toUpperCase()); //-> HELLO console.log(c); //-> hello ``` ## `Symbol` - プロパティーの重複を避けるために必ず一意の値を返す関数 - ES 6 にて実装された機能で、プリミティブ型の値のひとつ。値は必ず関数から与えられることになる ```javascript const s = Symbol(); console.log(s); //-> Symbol() const hello1 = Symbol("hello"); const hello2 = Symbol("hello"); console.log(hello1, hello2); //-> Symbol(hello), Symbol(hello) console.log(hello1 === hello2); //-> false ``` ## プロパティーとディスクリプター - オブジェクトの中にプロパティーを設定するとき、`value` 以外にも `configurable`, `enumerable`, `writeble` という設定値がある。これらは **ディスクリプター** と呼ばれ、プロパティーの挙動についての設定をすることができる | 設定値 | 内容 | | -------- | -------- | | value | 値 | | configurable | 設定変更 | | enumerable | 列挙 | | writable | 値の変更 | ```javascript const obj = {prop: 0}; const descriptor = Object.getOwnPropertyDescriptor(obj, "prop"); console.log(descriptor); //-> {value: 0, writable: true, enumerable: true, configurable: true} // defineProperty で設定値を渡すとデフォルトの設定値はすべて false になる const obj2 = {}; Object.defineProperty(obj2, "prop", { value: 0 }); const descriptor2 = Object.getOwnPropertyDescriptor(obj2, "prop"); console.log(descriptor2); //-> {value: 0, writable: false, enumerable: false, configurable: false} obj2.prop = 1; //-> 値の変更を試みる console.log(descriptor2); //-> {value: 0, writable: false, enumerable: false, configurable: false} ``` ## `setter` / `getter` と `static` - ディスクリプタ~は前述のもの以外にも `set` と `get` がある。任意の設定値であるためデフォルトでは `undefined` となっている - `set` は値の書き換え時に行う関数処理を書き込める - `get` は値の呼び出し時に行う関数処理を書き込める - `static` はクラス内で静的なメソッドを定義するときに使うもの - **静的メソッド(スタティックメソッド)** とも呼ばれ、クラス内で定義されたメソッドをインスタンス化せずとも直接呼び出すことができる ```javascript function Person1 (name, age) { this._name = name; this._age = age; } Object.defineProperty(Person1.prototype, "name", { get: function() { return this._name; }, set: function(val) { this._name = val; } }); const p1 = new Person1("Bob", 23); console.log(p1.name); //-> Bob p1.name = "Tom"; console.log(p1.name); //-> Tom ``` ```javascript // ES 6 からはクラス内で以下のようにも書ける class Person1 { constructor(name, age) { this._name = name; this._age = age; } get name() { return this._name; } set name(val) { this._name = val; } static hello() { console.log("hello"); } } // クラス内の静的メソッドを呼び出す Person1.hello(); //-> hello // 直接クラスのメソッドを呼ぶため this は使えない(クラス自体を呼び出してしまうので) ``` ## チェーンメソッド - クラス内に定義されたメソッドをインスタンス側で連続して呼び出す際の手法 ```javascript class Person { constructor(name, age) { this.name = name; this.age = age; } hello(person) { console.log(`${this.name} says hello ${person.name}.`); return this; // インスタンスを返却 } introduce() { console.log(`「Hi, I,m ${this.name}, ${this.age}.」`); return this;// インスタンスを返却 } shakeHands(person) { console.log(`${this.name} shake hands with ${person.name}.`); return this; // インスタンスを返却 } bye(person) { console.log(`「Bye, ${person.name}.」`); return this; // インスタンスを返却 } } const bob = new Person("Bob", 23); const tim = new Person("Tim", 20); // 各メソッドにて this(中身はbob) を返せば // 次のメソッドに返却されたインスタンスを引き渡して // 連続してメソッドを実行することができる bob.hello(tim) .introduce() .shakeHands(tim) .bye(tim) ```