オブジェクトの生成方法
- オブジェクトリテラル
- new式でコンストラクタ呼び出し
//1.オブジェクトリテラルで生成
let obj = {x:3, y:5};
//2.コンストラクタ呼び出し
function MyClass(x, y) {
this.x = x;
this.y = y;
}
const obj2 = new MyClass(5, 10);
//2.組み込みオブジェクトのコンストラクタ呼び出し
const str = new String('abc');
- コンストラクタ自体は普通の関数宣言と同じ
- 関数呼び出しと区別し、new式を使った呼び出しをコンストラクタ呼び出しという。
- コンストラクタは暗黙にreturn thisのような挙動があるため、return文は書かない。
クラスもどきの定義
function MyClass(x, y) {
this.x = x;
this.y = y;
this.show = function() {
console.log(x, y)
}
}
const a = new MyClass(2, 5);
a.show();
//2 5
変数定義するフィールドと、メソッドもつクラスがあるのでこれで良いように見えるが、2つの問題がある。
1. すべてのインスタンスが同じメソッド定義の実体のコピーをもつので効率が良くない => プロトタイプ継承で解決
2. プロパティ値のアクセス制御ができない (a.xでアクセス可能。a.x = 10で書き換え可能) => クロージャで解決
プロトタイプベースでクラスを定義
クラスの書き方の基本例
(クラス名).prototype.(メソッド名) = function() {メソッド関数};
function MyClass(x, y) {
this.x = x;
this.y = y;
}
MyClass.prototype.show = function() {
console.log(this.x, this.y);
}
プロトタイプベースで解決
メソッド定義はインスタンスオブジェクトの直接のプロパティではなくなり、プロトタイプ継承されたメソッドとなる。その結果、メモリ効率と実行効率が改善される。
クロージャでの解決
クロージャを用いると、外部から関数内の変数を参照することができる。
また関数内のローカルスコープなので外部からのアクセスを制御できている。
プロトタイプ継承とはなにか
わかりやすくはこちらを参照
図で理解するJavaScriptのプロトタイプチェーン
まずオブジェクトを生成する
const obj = {name:"taro"};
//const obj = new Object({name: "taro"});
console.log(obj);
//結果
Object{name: "taro"}
name: "taro"
__proto__: Object
コンソールの結果、Object{name: "taro"}
の他に
__proto__: Object
が生成される
これは暗に参照している、オブジェクトクラスのプロトタイププロパティである。
obj.__proto__ === Object.prototype; // true
プロトタイプ継承とは
-
すべての関数(オブジェクト)はprototypeという名のプロパティを持つ。
-
Object.prototype
のように、すべての関数がプロトタイププロパティをもつということ。これはString.prototype
でも、MyClass.prototype
でもそう。
-
-
すべてのオブジェクトはオブジェクトの生成に使ったコンストラクタ(関数オブジェクト)のprototypeオブジェクトへのリンクを持つ(暗黙リンク)
-
obj.__proto__
のように、生成されたオブジェクトobj
はObjectクラスのプロトタイプオブジェクト
を常に参照している。つまり、プロトタイププロパティに定義されているメソッドなども使えるということ。
-
const obj = {name:"taro"};
console.log(obj.toString();)
//"[object Object]"
//objが暗に参照している、Objectのprototypeプロパティがもつメソッド、toStringが使える。
上記のことを踏まえて、クラスの書き方を見直すと
function MyClass(x, y) {
this.x = x;
this.y = y;
}
//1.プロトタイププロパティにメソッドを登録する
MyClass.prototype.show = function() {
console.log(this.x, this.y);
}
const func = new MyClass(1,5);
//funcはコンストラクタMyClassのprototypeオブジェクトにリンクをもつので、showにアクセス
func.show();
//1 5
プロトタイプチェーンの探す順序
- 指定したオブジェクトのプロパティ(objのプロパティ、name)
- なかった場合protoが参照する先で存在を調べる(MyClass.prototype)
- それでもなかった場合protoが参照する・・・(Object.prototype)...(ループ)
- 最終的にnullになるまで行う。nullならundefinedを返す
Functionオブジェクトでは、ベースにObjectが存在する
上記の例のconst func
は、FunctionオブジェクトとObjectオブジェクトの両方を参照する
const func = new MyClass(1,5);
console.log(func);
//
//x:1
//y:5
//__proto__:
// show:ƒ ()
// constructor:ƒ MyClass(x, y)
// __proto__:Object
Functonのプロトタイプオブジェクトが、Objectのプロトタイプオブジェクトを参照している。
Function.prototype.__proto__ === Object.prototype; // true
クロージャについて
クロージャとは、簡単に定義すると「自分を囲むスコープにある変数を参照できる関数」である。
下記の例を出す。
function f(arg) {
var n = 123 + Number(arg);
function g() {
console.log('n is' + n);
console.log('g is called');
}
return g;
}
const g2 = f(3);
//n is 126
//g is called
関数gが外側の変数nを参照することができている。
なぜ、外側の変数を参照できたかを以下に説明する。
- 関数が呼ばれるとき、callオブジェクトが生成される。callオブジェクトが生きている限り、関数は生きているとみれる
- 関数fを呼んだとき、call-f(関数fのcallオブジェクト)が生成される。
- このときgはcall-fオブジェクトのプロパティになる。
- プロパティgが参照している関数オブジェクト(print( ‘ n is ‘ + n );が入っている関数、関数オブジェクトに名前はない)をg2が参照する。
- 名前g2が有効な限り、参照されるこの関数オブジェクトはガベージコレクションの対象にならないので生き続ける。
- この関数オブジェクトはcall-fオブジェクトを参照(スコープチェーン)しているので、call-fオブジェクトも生き続ける。
- 結果ローカル変数nも生き続ける。
クロージャの実践的な応用
クロージャを利用して名前空間を汚染せずに、一回しか利用しない関数を呼び出せる。
即時関数は、クロージャを利用して名前空間の汚染を防いだもの。
var say = 'goodby';
(function(name) {
var say = 'hello';
console.log(say + name);
})("taro");
//hellotaro
外部からアクセスできない関数を作成。
var module = (function() {
var count = 0;
return {
increment: function() {
count++;
},
show: function() {
console.log(count);
}
};
})();
module.show(); // 0
module.increment();
module.show(); // 1
console.log(count); // undefined
多値データを扱うときの2種の方法
多くの引数を取る場合、対応関係が分かりづらくなるので
下の関数のほうがわかりやすくなる
//引数3つを取る関数
function getDistance(x, y, z) { //立方根を求める関数
return Math.sqrt(x * x + y * y + z * z);
}
getDistance(1, 4, 5);
//引数を1つにし値を与えるときにオブジェクトを渡す
function getDistance1(pos) {
return Math.sqrt(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z);
}
getDistance1({x:3, y:4, z:5});