banner
Aglorice

Aglorice

Life is a coding,I will debug it.
github
twitter
telegram
bilibili

探究コンストラクタと new コマンド

1684925475841

夜が深くなってきました......

1. 前言#

​ JavaScript におけるコンストラクタについて探求する前に、コンストラクタとは何かを理解する必要があります。その名の通り「構築」するものであり、次に何を構築しているのかを見ていきましょう。

​ 先に進む前に、コンストラクタとは実際には関数であることを明確にする必要がありますが、それには独自の特徴と使い方があります。コンストラクタを区別し、使用する方法を理解するためには、JavaScript のnewキーワードが欠かせません。

2.new キーワード#

JavaScript におけるコンストラクタを説明する際に、必ず避けられない 2 つの特徴があります:

  1. 関数内部で this キーワードが使用され、生成されるオブジェクトインスタンスを表します。
  2. オブジェクトを生成する際には、必ず 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が関数として扱われたため、そのスコープはグローバルになり、内部のthiswindowを指し、windowにはこの変数が宣言されていないため、最終的にundefinedが出力されます。

このような事態を避けるにはどうすればよいでしょうか?

  1. 厳格モードを使用する

通常の実行モードに加えて、ECMAScript 5 は「厳格モード」を追加しました。このモードは、JavaScriptがより厳しい条件下で実行されることを意味します。

function Fubar(foo, bar){
    'use strict';
    this._foo = foo;
    this._bar = bar;
}
Fubar()

厳格モードを使用すると、コンストラクタを関数として使用すると直接エラーが発生します。結果は以下の通りです:

image-20230208230605804

エラーが発生するのは、厳格モードでは関数内部の this がグローバルオブジェクトを指すことができず、デフォルトでundefinedとなるため、newなしで呼び出すとエラーが発生するからです(JavaScript はundefinedにプロパティを追加することを許可しません)。

  1. 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

上記のコードのように、thisFubar_1のインスタンスオブジェクトであるかどうかを判断することで、コンストラクタがnewキーワードを使用したかどうかを知ることができます。使用していない場合は、newを使用してインスタンス化されたオブジェクトを返します。

3.new キーワードの原理#

newキーワードを使用する際、実際には以下のいくつかのステップが実行されます:

  1. 空のオブジェクトを作成し、返されるオブジェクトインスタンスとします。
  2. この空のオブジェクトのプロトタイプをコンストラクタのprototype属性に指し示します。
  3. この空のオブジェクトを関数内部のthisキーワードに割り当てます。
  4. コンストラクタ内部のコードの実行を開始します。

コードに変換すると、以下の操作になります:

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 つの目的があります:

  1. 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'); // エラー
    
  2. 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.callnewキーワードを区別することはできません。しかし、new.targetを使用すれば実現できます😍😍😍

6. 参考リンク#

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。