夜が深くなってきました......
1. 前言#
JavaScript におけるコンストラクタについて探求する前に、コンストラクタとは何かを理解する必要があります。その名の通り「構築」するものであり、次に何を構築しているのかを見ていきましょう。
先に進む前に、コンストラクタとは実際には関数であることを明確にする必要がありますが、それには独自の特徴と使い方があります。コンストラクタを区別し、使用する方法を理解するためには、JavaScript のnew
キーワードが欠かせません。
2.new キーワード#
JavaScript におけるコンストラクタを説明する際に、必ず避けられない 2 つの特徴があります:
- 関数内部で this キーワードが使用され、生成されるオブジェクトインスタンスを表します。
- オブジェクトを生成する際には、必ず new キーワードを使用しなければなりません。
1.new コマンドの基本的な使い方#
以下は、最も基本的な方法でnew
コマンドを使用してコンストラクタをインスタンス化する例です。
let MyNew = function (){
this.say_hello = function (){
console.log('hello world')
}
}
let mynew = new MyNew
上記の操作は、new コマンドを使用してコンストラクタMyNew
からインスタンスオブジェクトを生成し、その結果をmynew
に渡すものであり、mynew
はコンストラクタ内の属性を得ることになります。
2. 引数付きコンストラクタ#
実際には、1 つのコンストラクタを使用して大量の同じオブジェクトを構築することはできないため、new
を使用してコンストラクタを呼び出す際に引数を渡すことができます。
let MyNew_1 = function (name){
this.name = name
}
let mynew_1 = new MyNew_1('張三')
console.log(mynew_1.name) // '張三'
上記の操作は、new
キーワードを使用してオブジェクトをインスタンス化する際に対応する引数を渡すものです。
3.new キーワードの規範#
new キーワードを使用してコンストラクタをインスタンス化する際には、最後に自動的に実行されるため、new キーワードの使用に関しては以下のように注意が必要です。
let mynew_2 = new MyNew_1 // 推奨されません
let mynew_3 = new MyNew_1() // 推奨されます
上記の 2 つの方法はどちらも可能ですが、2 番目の方法を使用することがより推奨されます。1 番目の方法よりも認識されやすいためです。
4. 注意#
コンストラクタを使用する際に、new
キーワードを忘れると、それは関数として実行されます。
let MyNew_1 = function (name){
this.name = name
}
let test = MyNew_1()
console.log(test.name) // undefined
上記がundefined
を出力する理由は、MyNew_1
が関数として扱われたため、そのスコープはグローバルになり、内部のthis
はwindow
を指し、window
にはこの変数が宣言されていないため、最終的にundefined
が出力されます。
このような事態を避けるにはどうすればよいでしょうか?
- 厳格モードを使用する
通常の実行モードに加えて、ECMAScript 5 は「厳格モード」を追加しました。このモードは、JavaScript
がより厳しい条件下で実行されることを意味します。
function Fubar(foo, bar){
'use strict';
this._foo = foo;
this._bar = bar;
}
Fubar()
厳格モードを使用すると、コンストラクタを関数として使用すると直接エラーが発生します。結果は以下の通りです:
エラーが発生するのは、厳格モードでは関数内部の this がグローバルオブジェクトを指すことができず、デフォルトでundefined
となるため、new
なしで呼び出すとエラーが発生するからです(JavaScript はundefined
にプロパティを追加することを許可しません)。
- instanceof キーワードを使用する
instanceof
キーワードを使用して判断することができます。
function Fubar_1(foo, bar) {
if (!(this instanceof Fubar_1)) {
return new Fubar(foo, bar);
}
this._foo = foo;
this._bar = bar;
}
console.log(Fubar_1(1, 2)._foo)// 1
console.log((new Fubar_1(1, 2))._foo) // 1
上記のコードのように、this
がFubar_1
のインスタンスオブジェクトであるかどうかを判断することで、コンストラクタがnew
キーワードを使用したかどうかを知ることができます。使用していない場合は、new
を使用してインスタンス化されたオブジェクトを返します。
3.new キーワードの原理#
new
キーワードを使用する際、実際には以下のいくつかのステップが実行されます:
- 空のオブジェクトを作成し、返されるオブジェクトインスタンスとします。
- この空のオブジェクトのプロトタイプをコンストラクタの
prototype
属性に指し示します。 - この空のオブジェクトを関数内部の
this
キーワードに割り当てます。 - コンストラクタ内部のコードの実行を開始します。
コードに変換すると、以下の操作になります:
function _new(/* コンストラクタ */ constructor, /* コンストラクタ引数 */ params) {
// argumentsオブジェクトを配列に変換
var args = [].slice.call(arguments);
// コンストラクタを取得
var constructor = args.shift();
// 空のオブジェクトを作成し、コンストラクタのprototype属性を継承
var context = Object.create(constructor.prototype);
// コンストラクタを実行
var result = constructor.apply(context, args);
// 戻り値がオブジェクトの場合はそれを返し、そうでない場合はcontextオブジェクトを返す
return (typeof result === 'object' && result != null) ? result : context;
}
// インスタンス
var actor = _new(Person, '張三', 28);
後でさらに詳しく見ていきましょう🧐🧐🧐
4. コンストラクタ内の戻り値#
コンストラクタ内にreturn
文がある場合、覚えておくべきことは、戻り値がオブジェクトであれば最終的にそのオブジェクトが返され、オブジェクトでない場合はthis
が返されるということです。
let MyNew_2 = function (){
this.count = 0
return this.count
}
(new MyNew_2()) === 0 // false
let MyNew_3 = function (){
this.count = 0
return {count:this.count}
}
(new MyNew_3()).count === 0 // true
5.new.target#
これは ECMAScript 6 で導入された新しい属性で、主に 2 つの目的があります:
-
new
演算子を使用してコンストラクタを呼び出すと、new.target
属性の値はコンストラクタになり、そうでない場合はundefined
になります。function Person (fullName) { if (typeof new.target !== 'undefined') { this.fullName = fullName; } else { throw new Error('必ず new キーワードを使用して Person を呼び出してください。'); } } let student = new Person('aa'); let student_1 = Person('aa'); // エラー
-
new.target
が特定のコンストラクタによって呼び出されたかどうかを確認できます。function Person (fullName, age) { if (typeof new.target === Person) { this.fullName = fullName; this.age = age; } else { throw new Error('必ず new キーワードを使用して Person を呼び出してください。'); } } function Dog (fullName, age) { Person.call(this, fullName, age) } let dog = new Dog('HeHe', 3) console.log(dog)
ES5 では、コンストラクタが使用されたかどうかを確認するために、最初にinstanceof
を思い浮かべるでしょう。
以下のように:
function Person (name) {
if(this instanceof Person){
this.name = name
} else {
throw new Error('newを使って呼び出されていません')
}
}
var person2 = Person('Hello') //例外をスロー
上記の操作により、コンストラクタがnew
キーワードを使用したかどうかを判断できます。しかし、これは十分ではありません。例えば、以下の例のように:
function Person (name) {
if(this instanceof Person){
this.name = name
} else {
throw new Error('newを使って呼び出されていません')
}
}
var person1 = new Person('Hello')
var person2 = Person.call(person1,'Hello') //正常に実行
上記のコードでは、call
メソッドを使用して this をperson1
のインスタンスに設定しています。関数自体に対して、Person.call
とnew
キーワードを区別することはできません。しかし、new.target
を使用すれば実現できます😍😍😍
6. 参考リンク#
- 王昱潇 --- 関数が呼び出された方法を判断する
- 網道 --- インスタンスオブジェクトと new コマンド
- 萌新小吉 --- 厳格モードの詳細