夜が深くなってきました......
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
となるため、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 コマンド
- 萌新小吉 --- 厳格モードの詳細