簡単に言うとオブジェクト指向のJavaScript





良い一日、友達!



JavaScriptでオブジェクトを作成する方法は4つあります。



  • コンストラクター関数
  • クラス(クラス)
  • 他のオブジェクトにリンクしているオブジェクト(OLOO)
  • 工場機能


どの方法を使用する必要がありますか?どれが最高ですか?



これらの質問に答えるために、各アプローチを個別に検討するだけでなく、継承、カプセル化、キーワード「this」、イベントハンドラーの基準に従って、クラスとファクトリ関数を比較します。



オブジェクト指向プログラミング(OOP)とは何かから始めましょう。



OOPとは何ですか?



基本的に、OOPは、単一のオブジェクトを使用してオブジェクトを作成できるようにするコードを作成する方法です。これは、コンストラクターの設計パターンの本質でもあります。共有オブジェクトは通常、ブループリント、ブループリント、またはブループリントと呼ばれ、共有オブジェクトが作成するオブジェクトはインスタンスです。



各インスタンスには、親から継承されたプロパティと独自のプロパティの両方があります。たとえば、Humanプロジェクトがある場合、それに基づいて異なる名前のインスタンスを作成できます。



OOPの2番目の側面は、レベルの異なる複数のプロジェクトがある場合のコードの構造化です。これは、継承またはサブクラス化と呼ばれます。



OOPの3番目の側面はカプセル化です。これは、実装の詳細を外部から隠し、外部から変数や関数にアクセスできないようにする場合です。これが、モジュールとファサードの設計パターンの本質です。



オブジェクトを作成する方法に移りましょう。



オブジェクト作成方法



コンストラクター関数


コンストラクターは、「this」キーワードを使用する関数です。



    function Human(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
    }


これにより、作成中のインスタンスの一意の値を保存してアクセスできます。インスタンスは、「new」キーワードを使用して作成されます。



const chris = new Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

const zell = new Human('Zell', 'Liew')
console.log(zell.firstName) // Zell
console.log(zell.lastName) // Liew


クラス


クラスは、コンストラクター関数の抽象化(「構文シュガー」)です。インスタンスの作成が簡単になります。



    class Human {
        constructor(firstName, lastName) {
            this.firstName = firstName
            this.lastName = lastName
        }
    }


コンストラクターには、上記のコンストラクター関数と同じコードが含まれていることに注意してください。これを初期化するには、これを行う必要があります。初期値を割り当てる必要がない場合は、コンストラクターを省略できます。



一見すると、クラスはコンストラクターよりも複雑に見えます。より多くのコードを作成する必要があります。あなたの馬を保持し、結論にジャンプしないでください。クラスはかっこいいです。理由は少し後でわかります。



インスタンスも「new」キーワードを使用して作成されます。



const chris = new Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


オブジェクトのリンク


オブジェクトを作成するこの方法は、KyleSimpsonによって提案されました。このアプローチでは、プロジェクトを通常のオブジェクトとして定義します。次に、メソッド(通常はinitと呼ばれますが、クラスのコンストラクターとは異なり、これは必須ではありません)を使用して、インスタンスを初期化します。



const Human = {
    init(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
    }
}


Object.createは、インスタンスを作成するために使用されます。インスタンス化後、initが呼び出されます。



const chris = Object.create(Human)
chris.init('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


これをinitに戻すことで、コードを少し改善できます。



const Human = {
  init () {
    // ...
    return this
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


工場機能


ファクトリ関数は、オブジェクトを返す関数です。任意のオブジェクトを返すことができます。クラスまたはオブジェクトバインディングのインスタンスを返すこともできます。



ファクトリ関数の簡単な例を次に示します。



function Human(firstName, lastName) {
    return {
        firstName,
        lastName
    }
}


インスタンスを作成するのに「this」キーワードは必要ありません。関数を呼び出すだけです。



const chris = Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


次に、プロパティとメソッドを追加する方法を見てみましょう。



プロパティとメソッドの定義



メソッドは、オブジェクトのプロパティとして宣言された関数です。



    const someObject = {
        someMethod () { /* ... */ }
    }


OOPでは、プロパティとメソッドを定義する方法が2つあります。



  • 一例として
  • プロトタイプで


コンストラクターでのプロパティとメソッドの定義


インスタンスにプロパティを定義するには、それをコンストラクター関数に追加する必要があります。必ずこれにプロパティを追加してください。



function Human (firstName, lastName) {
  //  
  this.firstName = firstName
  this.lastname = lastName

  //  
  this.sayHello = function () {
    console.log(`Hello, I'm ${firstName}`)
  }
}

const chris = new Human('Chris', 'Coyier')
console.log(chris)






メソッドは通常、プロトタイプで定義されます。これにより、インスタンスごとに関数を作成する必要がなくなります。すべてのインスタンスが単一の機能(共有または分散機能と呼ばれる)を共有できるようにします。



プロトタイプにプロパティを追加するには、prototypeを使用します。



function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastname = lastName
}

//    
Human.prototype.sayHello = function () {
  console.log(`Hello, I'm ${this.firstName}`)
}






複数のメソッドを作成するのは面倒です。



//    
Human.prototype.method1 = function () { /*...*/ }
Human.prototype.method2 = function () { /*...*/ }
Human.prototype.method3 = function () { /*...*/ }


Object.assignを使用すると、作業が楽になります。



Object.assign(Human.prototype, {
  method1 () { /*...*/ },
  method2 () { /*...*/ },
  method3 () { /*...*/ }
})


クラスでのプロパティとメソッドの定義


インスタンスプロパティは、コンストラクターで定義できます。



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
      this.lastname = lastName

      this.sayHello = function () {
        console.log(`Hello, I'm ${firstName}`)
      }
  }
}






プロトタイププロパティは、コンストラクタの後に通常の関数として定義されます。



class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}






クラスで複数のメソッドを作成することは、コンストラクターよりも簡単です。これにはObject.assignは必要ありません。他の機能を追加しているだけです。



class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  method1 () { /*...*/ }
  method2 () { /*...*/ }
  method3 () { /*...*/ }
}


オブジェクトをバインドするときのプロパティとメソッドの定義


インスタンスのプロパティを定義するには、これにプロパティを追加します。



const Human = {
  init (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    this.sayHello = function () {
      console.log(`Hello, I'm ${firstName}`)
    }

    return this
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris)






プロトタイプメソッドは、通常のオブジェクトとして定義されています。



const Human = {
  init () { /*...*/ },
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}






ファクトリ関数(FF)でのプロパティとメソッドの定義


プロパティとメソッドは、返されるオブジェクトに含めることができます。



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}






FFを使用する場合、プロトタイププロパティを定義することはできません。このようなプロパティが必要な場合は、クラス、コンストラクター、またはオブジェクトバインディングのインスタンスを返すことができます(ただし、それは意味がありません)。



//   
function createHuman (...args) {
  return new Human(...args)
}


プロパティとメソッドを定義する場所



プロパティとメソッドをどこで定義する必要がありますか?インスタンスまたはプロトタイプ?



多くの人は、プロトタイプがこれに適していると考えています。



しかし、それは本当に重要ではありません。



インスタンスにプロパティとメソッドを定義することにより、各インスタンスはより多くのメモリを消費します。プロトタイプでメソッドを定義する場合、メモリの消費量は少なくなりますが、重要ではありません。最新のコンピューターの能力を考えると、この違いは重要ではありません。ですから、あなたに最適なことは何でもしてください。



たとえば、クラスまたはオブジェクトバインディングを使用する場合は、コードを記述しやすくするため、プロトタイプを使用することをお勧めします。 FFの場合、プロトタイプは使用できません。インスタンスのプロパティのみを定義できます。



per 。:作者に反対させてください。プロパティとメソッドを定義するときにインスタンスの代わりにプロトタイプを使用する問題は、メモリ使用量の問題だけでなく、何よりも定義されているプロパティまたはメソッドの目的の問題です。プロパティまたはメソッドがインスタンスごとに一意である必要がある場合は、インスタンスで定義する必要があります。プロパティまたはメソッドをすべてのインスタンスで同じ(共通)にする場合は、プロトタイプで定義する必要があります。後者の場合、プロパティまたはメソッドに変更を加える必要がある場合は、個別に調整されるインスタンスのプロパティおよびメソッドとは対照的に、プロトタイプに変更を加えるだけで十分です。



予備的結論



調査した資料に基づいて、いくつかの結論を導き出すことができます。それは私の個人的な意見です。



  • クラスは、複数のメソッドを簡単に定義できるため、コンストラクターよりも優れています。
  • Object.createを使用する必要があるため、オブジェクトのバインドは奇妙に思えます。このアプローチを研究するとき、私はこれを忘れ続けました。私にとって、これはそれ以上の使用を拒否するのに十分な理由でした。
  • クラスとFFが最も使いやすいです。問題は、プロトタイプがFFで使用できないことです。しかし、先に述べたように、それは実際には問題ではありません。


次に、JavaScriptでオブジェクトを作成する2つの最良の方法として、クラスとFFを比較します。



クラスvs.FF-継承



クラスとFFの比較に進む前に、OOPの根底にある3つの概念を理解する必要があります。



  • 継承
  • カプセル化
  • この


継承から始めましょう。



継承とは何ですか?


JavaScriptでは、継承とは、プロパティを親から子に渡すことを意味します。プロジェクトからインスタンスへ。



これは2つの方法で発生します。



  • インスタンスの初期化を使用する
  • プロトタイプチェーンを使用する


2番目のケースでは、親プロジェクトが子プロジェクトで展開されます。これはサブクラス化と呼ばれますが、継承と呼ばれることもあります。



サブクラス化を理解する


サブクラス化とは、子プロジェクトが親を拡張することです。



クラスの例を見てみましょう。



クラスによるサブクラス化


「extends」キーワードは、親クラスを拡張するために使用されます。



class Child extends Parent {
    // ...
}


たとえば、Humanクラスを拡張するDeveloperクラスを作成しましょう。



//  Human
class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}


Developerクラスは、Humanを次のように拡張します。



class Developer extends Human {
  constructor(firstName, lastName) {
    super(firstName, lastName)
  }

    // ...
}


「super」キーワードは、「Human」クラスのコンストラクターを呼び出します。これが必要ない場合は、superを省略できます。



class Developer extends Human {
  // ...
}


開発者がコードを書くことができるとしましょう(誰が考えたでしょう)。対応するメソッドを追加しましょう。



class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}


これは、「Developer」クラスのインスタンスの例です。



const chris = new Developer('Chris', 'Coyier')
console.log(chris)






FFによるサブクラス化


FFを使用してサブクラスを作成するには、次の4つの手順を実行する必要があります。



  • 新しいFFを作成する
  • 親プロジェクトのインスタンスを作成します
  • このインスタンスのコピーを作成します
  • このコピーにプロパティとメソッドを追加します


このプロセスは次のようになります。



function Subclass (...args) {
  const instance = ParentClass(...args)
  return Object.assign({}, instance, {
    //   
  })
}


サブクラス「Developer」を作成しましょう。FF「ヒューマン」はこんな感じ。



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}


開発者を作成します。



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    //   
  })
}


「コード」メソッドを追加します。



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}


Developerのインスタンスを作成します。



const chris = Developer('Chris', 'Coyier')
console.log(chris)






親メソッドを上書きする


サブクラス内の親メソッドを上書きする必要がある場合があります。これは次のように実行できます。



  • 同じ名前のメソッドを作成します
  • 親メソッドを呼び出す(オプション)
  • サブクラスに新しいメソッドを作成します


このプロセスは次のようになります。



class Developer extends Human {
  sayHello () {
    //   
    super.sayHello()

    //   
    console.log(`I'm a developer.`)
  }
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()






FFを使用した同じプロセス。



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)

  return Object.assign({}, human, {
      sayHello () {
        //   
        human.sayHello()

        //   
        console.log(`I'm a developer.`)
      }
  })
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()






継承と構成


継承についての会話は、構成に言及せずに行われることはめったにありません。エリックエリオットのような専門家は、可能な限り作曲を使用すべきだと信じています。



作曲とは?



構成を理解する


基本的に、構成はいくつかのものを1つに組み合わせたものです。オブジェクトを組み合わせる最も一般的で最も簡単な方法は、Object.assignを使用することです。



const one = { one: 'one' }
const two = { two: 'two' }
const combined = Object.assign({}, one, two)


構成は例で説明するのが最も簡単です。DeveloperとDesignerの2つのサブクラスがあるとしましょう。設計者は設計方法を知っており、開発者はコードの記述方法を知っています。どちらも「Human」クラスを継承しています。



class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class Designer extends Human {
  design (thing) {
    console.log(`${this.firstName} designed ${thing}`)
  }
}

class Developer extends Designer {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}


ここで、3番目のサブクラスを作成するとします。このサブクラスは、設計者と開発者が混在している必要があります。コードの設計と記述の両方ができる必要があります。それをDesignerDeveloper(または必要に応じてDeveloperDesigner)と呼びましょう。



どうやって作成しますか?



「Designer」クラスと「Developer」クラスを同時に拡張することはできません。どのプロパティを最初に指定するかを決定できないため、これは不可能です。これは、ダイヤモンド問題(ダイヤモンド継承)と呼ばれます。







あるオブジェクトを別のオブジェクトよりも優先する場合、Rhombusの問題はObject.assignで解決できます。ただし、JavaScriptは複数の継承をサポートしていません。



//  
class DesignerDeveloper extends Developer, Designer {
  // ...
}


ここで作曲が役に立ちます。



このアプローチでは、次のように述べています。DesignerDeveloperをサブクラス化する代わりに、必要に応じてサブクラス化できるスキルを含むオブジェクトを作成します。



このアプローチの実装は、次のことにつながります。



const skills = {
    code (thing) { /* ... */ },
    design (thing) { /* ... */ },
    sayHello () { /* ... */ }
}


指定されたオブジェクトを使用して3つの異なるクラスを作成できるため、Humanクラスはもう必要ありません。



DesignerDeveloperのコードは次のとおりです。



class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      code: skills.code,
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}

const chris = new DesignerDeveloper('Chris', 'Coyier')
console.log(chris)






DesignerとDeveloperでも同じことができます。



class Designer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}

class Developer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      code: skills.code,
      sayHello: skills.sayHello
    })
  }
}


インスタンスにメソッドを作成していることに気づきましたか?これは、可能なオプションの1つにすぎません。プロトタイプにメソッドを配置することもできますが、不要だと思います(このアプローチは、コンストラクターに戻ったようです)。



class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design,
  sayHello: skills.sayHello
})






適切と思われるアプローチを使用してください。結果は同じになります。



FFによる構成


FFを使用した構成とは、返されたオブジェクトに分散メソッドを追加することです。



function DesignerDeveloper (firstName, lastName) {
  return {
    firstName,
    lastName,
    code: skills.code,
    design: skills.design,
    sayHello: skills.sayHello
  }
}






継承と構成


継承と構成を同時に使用できないとは誰も言っていません。



Designer、Developer、およびDesignerDeveloperの例に戻ると、これらも人間であることに注意してください。したがって、Humanクラスを拡張できます。



これは、クラス構文を使用した継承と構成の例です。



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class DesignerDeveloper extends Human {}
Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design
})






そして、これはFFの使用と同じです。



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}

function DesignerDeveloper (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code: skills.code,
    design: skills.design
  })
}






実世界のサブクラス


多くの専門家は、構成はサブクラスよりも柔軟である(したがってより有用である)と主張していますが、サブクラスを軽視すべきではありません。私たちが扱うことの多くは、この戦略に基づいています。



例:「クリック」イベントはMouseEventです。MouseEventはUIEvent(ユーザーインターフェイスイベント)のサブクラスであり、UIEvent(ユーザーインターフェイスイベント)はEvent(イベント)のサブクラスです。







別の例:HTML要素はノードのサブクラスです。したがって、ノードのすべてのプロパティとメソッドを使用できます。







継承に関する予備的結論


継承と構成は、クラスとFFの両方で使用できます。FFでは、構成は「よりクリーン」に見えますが、これはクラスよりもわずかに有利です。



比較を続けましょう。



クラスvs.FF-カプセル化



基本的に、カプセル化とは、あるものを別の物の中に隠し、内側のエッセンスに外側からアクセスできないようにすることです。



JavaScriptでは、非表示のエンティティは、現在のコンテキストでのみ使用できる変数および関数です。この場合、コンテキストはスコープと同じです。



単純なカプセル化


カプセル化の最も単純な形式は、コードのブロックです。



{
  // ,  ,     
}


ブロック内では、ブロック外で宣言された変数にアクセスできます。



const food = 'Hamburger'

{
  console.log(food)
}






しかし、その逆はありません。



{
  const food = 'Hamburger'
}

console.log(food)






「var」キーワードで宣言された変数には、グローバルスコープまたは機能スコープがあることに注意してください。varを使用して変数を宣言しないようにしてください。



機能によるカプセル化


機能スコープはブロックスコープに似ています。関数で宣言された変数は、関数内でのみアクセスできます。これは、varで宣言されたものも含め、すべての変数に適用されます。



function sayFood () {
  const food = 'Hamburger'
}

sayFood()
console.log(food)






関数の内部にいるときは、関数の外部で宣言された変数にアクセスできます。



const food = 'Hamburger'

function sayFood () {
  console.log(food)
}

sayFood()






関数は、後で関数の外部で使用できる値を返すことができます。



function sayFood () {
  return 'Hamburger'
}

console.log(sayFood())






閉鎖


クロージャーは、カプセル化の高度な形式です。これは、別の関数内の関数にすぎません。



//  
function outsideFunction () {
  function insideFunction () { /* ... */ }
}




outsideFunctionで宣言された変数は、insideFunctionで使用できます。



function outsideFunction () {
  const food = 'Hamburger'
  console.log('Called outside')

  return function insideFunction () {
    console.log('Called inside')
    console.log(food)
  }
}

//  outsideFunction,   insideFunction
//  insideFunction   "fn"
const fn = outsideFunction()






カプセル化とOOP


オブジェクトを作成するときは、一部のプロパティをパブリック(パブリック)にし、他のプロパティをプライベート(プライベートまたはプライベート)にする必要があります。



例を見てみましょう。車のプロジェクトがあるとしましょう。新しいインスタンスを作成するときに、値50の「fuel」プロパティをインスタンスに追加します。



class Car {
  constructor () {
    this.fuel = 50
  }
}




ユーザーはこのプロパティを使用して、残りの燃料の量を決定できます。



const car = new Car()
console.log(car.fuel) // 50




ユーザーは自分で燃料の量を設定することもできます。



const car = new Car()
car.fuel = 3000
console.log(car.fuel) // 3000


車のタンクが最大100リットルの燃料を保持するという条件を追加しましょう。車を壊す可能性があるため、ユーザーが自分で燃料の量を設定できるようにしたくありません。



これを行うには2つの方法があります。



  • 慣例による私有財産の使用
  • 実際のプライベートフィールドを使用する


合意による私有財産


JavaScriptでは、プライベート変数とプロパティは通常、下線で示されます。



class Car {
  constructor () {
    //   "fuel"  ,       
    this._fuel = 50
  }
}


通常、プライベートプロパティを管理するメソッドを作成します。



class Car {
  constructor () {
    this._fuel = 50
  }

  getFuel () {
    return this._fuel
  }

  setFuel (value) {
    this._fuel = value
    //   
    if (value > 100) this._fuel = 100
  }
}


ユーザーは、getFuelメソッドとsetFuelメソッドを使用して、それぞれ燃料の量を決定および設定する必要があります。



const car = new Car()
console.log(car.getFuel()) // 50

car.setFuel(3000)
console.log(car.getFuel()) // 100


しかし、「_ fuel」変数は実際にはプライベートではありません。外部からアクセスできます。



const car = new Car()
console.log(car.getFuel()) // 50

car._fuel = 3000
console.log(car.getFuel()) // 3000


変数へのアクセスを制限するには、実際のプライベートフィールドを使用します。



本当にプライベートフィールド


フィールドは、変数、プロパティ、およびメソッドを組み合わせるために使用される用語です。



プライベートクラスフィールド


クラスを使用すると、「#」プレフィックスを使用してプライベート変数を作成できます。



class Car {
  constructor () {
    this.#fuel = 50
  }
}


残念ながら、このプレフィックスはコンストラクターでは使用できません。







プライベート変数は、コンストラクターの外部で定義する必要があります。



class Car {
  //   
  #fuel
  constructor () {
    //  
    this.#fuel = 50
  }
}


この場合、定義時に変数を初期化できます。



class Car {
  #fuel = 50
}


現在、「#fuel」変数はクラス内でのみ使用できます。クラス外でアクセスしようとすると、エラーがスローされます。



const car = new Car()
console.log(car.#fuel)






変数を操作するための適切なメソッドが必要です。



class Car {
  #fuel = 50

  getFuel () {
    return this.#fuel
  }

  setFuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.getFuel()) // 50

car.setFuel(3000)
console.log(car.getFuel()) // 100


私は個人的にこれにゲッターとセッターを使うことを好みます。この構文の方が読みやすいと思います。



class Car {
  #fuel = 50

  get fuel () {
    return this.#fuel
  }

  set fuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100


プライベートFFフィールド


FFはプライベートフィールドを自動的に作成します。変数を宣言する必要があります。ユーザーは外部からこの変数にアクセスできなくなります。これは、変数がブロック(または機能)スコープを持っているという事実によるものです。デフォルトでカプセル化されます。



function Car () {
  const fuel = 50
}

const car = new Car()
console.log(car.fuel) // undefined
console.log(fuel) // Error: "fuel" is not defined


ゲッターとセッターは、プライベート変数「燃料」を制御するためにも使用されます。



function Car () {
  const fuel = 50

  return {
    get fuel () {
      return fuel
    },

    set fuel (value) {
      fuel = value
      if (value > 100) fuel = 100
    }
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100


このような。シンプルかつ簡単に!



カプセル化に関する予備的結論


FFカプセル化は、より単純で理解しやすいものです。これは、JavaScriptの重要な部分であるスコープに基づいています。



クラスのカプセル化には「#」プレフィックスの使用が含まれますが、これはやや面倒な場合があります。



FFに対するクラス-これ



これは、クラスの使用に反対する主な議論です。どうして?これの意味は、これがどこでどのように使用されるかによって異なるためです。この動作は、初心者だけでなく、経験豊富な開発者にとっても混乱を招くことがよくあります。



ただし、これの概念は実際にはそれほど難しくありません。これを使用できるコンテキストは全部で6つあります。これらのコンテキストに問題がなければ、これで問題は発生しないはずです。



名前付きコンテキストは次のとおりです。



  • グローバルコンテキスト
  • 作成されるオブジェクトのコンテキスト
  • オブジェクトのプロパティまたはメソッドのコンテキスト
  • シンプルな機能
  • 矢印機能
  • イベントハンドラコンテキスト


しかし、記事に戻ります。クラスとFFでこれを使用する詳細を見てみましょう。



クラスでこれを使用する


クラスで使用される場合、これは作成されるインスタンス(プロパティ/メソッドコンテキスト)を指します。これが、インスタンスがコンストラクターで初期化される理由です。



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    console.log(this)
  }
}

const chris = new Human('Chris', 'Coyier')






コンストラクター関数でこれを使用する


これを関数内で使用し、newを使用してインスタンスを作成する場合、これはインスタンスを指します。



function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
  console.log(this)
}

const chris = new Human('Chris', 'Coyier')






FFのFKとは対照的に、これはウィンドウを指します(モジュールのコンテキストでは、これは通常、「未定義」の値を持ちます)。



//        "new"
function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
  console.log(this)
}

const chris = Human('Chris', 'Coyier')






したがって、これはFFでは使用しないでください。これは、FFとFCの主な違いの1つです。



FFでこれを使用する


これをFFで使用できるようにするには、プロパティ/メソッドコンテキストを作成する必要があります。



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayThis () {
      console.log(this)
    }
  }
}

const chris = Human('Chris', 'Coyier')
chris.sayThis()






これはFFで使用できますが、必要ありません。インスタンスを指す変数を作成できます。この代わりに、このような変数を使用できます。



function Human (firstName, lastName) {
  const human = {
    firstName,
    lastName,
    sayHello() {
      console.log(`Hi, I'm ${human.firstName}`)
    }
  }

  return human
}

const chris = Human('Chris', 'Coyier')
chris.sayHello()


humanは明示的にインスタンスを指しているため、human.firstNameはthis.firstNameよりも正確です。



実際、human.firstNameを記述する必要すらありません。この変数には字句スコープがあるため、firstNameに制限できます(これは、変数の値が外部環境から取得される場合です)。



function Human (firstName, lastName) {
  const human = {
    firstName,
    lastName,
    sayHello() {
      console.log(`Hi, I'm ${firstName}`)
    }
  }

  return human
}

const chris = Human('Chris', 'Coyier')
chris.sayHello()






より複雑な例を見てみましょう。



複雑な例



条件は次のとおりです。「firstName」プロパティと「lastName」プロパティを持つ「Human」プロジェクトと「sayHello」メソッドがあります。



Humanから継承した「Developer」プロジェクトもあります。開発者はコードの書き方を知っているので、「コード」メソッドが必要です。さらに、開発者キャストにいることを宣言する必要があるため、sayHelloメソッドを上書きする必要があります。



クラスとFFを使用して、指定されたロジックを実装しましょう。



クラス


プロジェクト「Human」を作成します。



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastname = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}


「code」メソッドを使用して「Developer」プロジェクトを作成します。



class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}


「sayHello」メソッドを上書きします。



class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }

  sayHello () {
    super.sayHello()
    console.log(`I'm a developer`)
  }
}


FF(これを使用)


プロジェクト「Human」を作成します。



function Human () {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}


「code」メソッドを使用して「Developer」プロジェクトを作成します。



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}


「sayHello」メソッドを上書きします。



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    },

    sayHello () {
      human.sayHello()
      console.log('I\'m a developer')
    }
  })
}


Ff(これなし)


firstNameは字句的に直接スコープされるため、これを省略できます。



function Human (firstName, lastName) {
  return {
    // ...
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

function Developer (firstName, lastName) {
  // ...
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${firstName} coded ${thing}`)
    },

    sayHello () { /* ... */ }
  })
}


これに関する予備的結論


簡単に言うと、クラスではこれを使用する必要がありますが、FFでは必要ありません。この場合、次の理由でFFを使用することを好みます。



  • このコンテキストは変更される可能性があります
  • FFを使用して記述されたコードは、より短く、よりクリーンです(変数の自動カプセル化も原因です)


クラスとFF-イベントハンドラー



OOPに関する多くの記事は、フロントエンド開発者として常にイベントハンドラーを扱っているという事実を見落としています。それらはユーザーとの相互作用を提供します。



イベントハンドラーはこのコンテキストを変更するため、クラスでイベントハンドラーを操作すると問題が発生する可能性があります。同時に、FFではこのような問題は発生しません。



ただし、このコンテキストを変更する方法がわかっていれば、このコンテキストを変更することは重要ではありません。簡単な例を見てみましょう。



カウンターを作成する


カウンターを作成するには、プライベート変数を含め、得られた知識を使用します。



私たちのカウンターには2つのものが含まれます:



  • カウンター自体
  • その値を増やすためのボタン






マークアップは次のようになります。



<div class="counter">
  <p>Count: <span>0</span></p>
  <button>Increase Count</button>
</div>


クラスを使用してカウンターを作成する


作業を簡単にするために、カウンターマークアップを見つけてCounterクラスに渡すようにユーザーに依頼します。



class Counter {
  constructor (counter) {
    // ...
  }
}

// 
const counter = new Counter(document.querySelector('.counter'))


クラスで2つの要素を取得する必要があります。



  • カウンター値を含む<span>-カウンターが増加したときにこの値を更新する必要があります
  • <button>-この要素によって呼び出されるイベントのハンドラーを追加する必要があります


class Counter {
  constructor (counter) {
    this.countElement = counter.querySelector('span')
    this.buttonElement = counter.querySelector('button')
  }
}


次に、countElementのテキストコンテンツで「count」変数を初期化します。指定する変数はプライベートである必要があります。



class Counter {
  #count
  constructor (counter) {
    // ...

    this.#count = parseInt(countElement.textContent)
  }
}


ボタンを押すと、カウンターの値が1増加します。これは、「increaseCount」メソッドを使用して実装します。



class Counter {
  #count
  constructor (counter) { /* ... */ }

  increaseCount () {
    this.#count = this.#count + 1
  }
}


次に、DOMを更新する必要があります。増加カウント内で呼び出される「updateCount」メソッドを使用してこれを実装しましょう。



class Counter {
  #count
  constructor (counter) { /* ... */ }

  increaseCount () {
    this.#count = this.#count + 1
    this.updateCount()
  }

  updateCount () {
    this.countElement.textContent = this.#count
  }
}


イベントハンドラーを追加する必要があります。



イベントハンドラーの追加


this.buttonElementにハンドラーを追加しましょう。残念ながら、increaseCountをコールバック関数として使用することはできません。これにより、エラーが発生します。



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }

  // 
}






これはbuttonElement(イベントハンドラコンテキスト)を指しているため、例外がスローされます。この値をコンソールに出力することで、これを確認できます。







この値は、インスタンスを指すように変更する必要があります。これは2つの方法で行うことができます。



  • バインドを使用する
  • 矢印機能を使用する


ほとんどは最初の方法を使用します(ただし、2番目の方法の方が簡単です)。



バインドを使用したイベントハンドラーの追加


bindは新しい関数を返します。最初の引数として、これが指すオブジェクト(これがバインドされるオブジェクト)が渡されます。



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount.bind(this))
  }

  // ...
}


動作しますが、見栄えがよくありません。さらに、バインドは、初心者が扱いにくい高度な機能です。



矢印機能


とりわけ、矢印関数には独自のこれがありません。彼らはそれを語彙(外部)環境から借ります。したがって、カウンタコードは次のように書き直すことができます。



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', () => {
      this.increaseCount()
    })
  }

  // 
}


さらに簡単な方法があります。矢印関数としてincreaseCountを作成できます。この場合、これはインスタンスを指します。



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }

  increaseCount = () => {
    this.#count = this.#count + 1
    this.updateCounter()
  }

  // ...
}


コード


完全なサンプルコードは次のとおりです。







FFを使用してカウンターを作成する


最初は似ています-カウンターマークアップを見つけて渡すようにユーザーに依頼します。



function Counter (counter) {
  // ...
}

const counter = Counter(document.querySelector('.counter'))


必要な要素を取得します。これはデフォルトでプライベートになります。



function Counter (counter) {
  const countElement = counter.querySelector('span')
  const buttonElement = counter.querySelector('button')
}


「count」変数を初期化してみましょう。



function Counter (counter) {
  const countElement = counter.querySelector('span')
  const buttonElement = counter.querySelector('button')

  let count = parseInt(countElement.textContext)
}


カウンタ値は、「increaseCount」メソッドを使用して増加します。通常の関数を使用できますが、私は別のアプローチを好みます。



function Counter (counter) {
  // ...
  const counter = {
    increaseCount () {
      count = count + 1
    }
  }
}


DOMは、increaseCount内で呼び出される「updateCount」メソッドを使用して更新されます。



function Counter (counter) {
  // ...
  const counter = {
    increaseCount () {
      count = count + 1
      counter.updateCount()
    },

    updateCount () {
      increaseCount()
    }
  }
}


this.updateCountの代わりにcounter.updateCountを使用していることに注意してください。



イベントハンドラーの追加


counter.increaseCountをコールバックとして使用して、buttonElementにイベントハンドラーを追加できます。



これは使用していないので機能します。したがって、ハンドラーがこれのコンテキストを変更することは重要ではありません。



function Counter (counterElement) {
  // 

  // 
  const counter = { /* ... */ }

  //  
  buttonElement.addEventListener('click', counter.increaseCount)
}


これの最初の機能


これはFFで使用できますが、メソッドのコンテキストでのみ使用できます。



次の例では、counter.increaseCountを呼び出すとcounter.updateCountが呼び出されます。これは、カウンターを指しているためです。



function Counter (counterElement) {
  // 

  // 
  const counter = {
    increaseCount() {
      count = count + 1
      this.updateCount()
    }
  }

  //  
  buttonElement.addEventListener('click', counter.increaseCount)
}


ただし、この値が変更されているため、イベントハンドラーは機能しません。この問題はバインドで解決できますが、矢印関数では解決できません。



これの2番目の機能


FF構文を使用する場合、メソッドは関数のコンテキストで作成されるため、矢印関数の形式でメソッドを作成することはできません。これはウィンドウを指します:



function Counter (counterElement) {
  // ...
  const counter = {
    //   
    //  ,  this   window
    increaseCount: () => {
      count = count + 1
      this.updateCount()
    }
  }
  // ...
}


したがって、FFを使用する場合は、これを使用しないことを強くお勧めします。



コード








イベントハンドラーの評決


イベントハンドラーはthisの値を変更するため、これは慎重に使用してください。クラスを使用するときは、矢印関数の形式でイベントハンドラコールバックを作成することをお勧めします。そうすれば、サービスをバインドするために頼る必要はありません。



FFを使用する場合は、これをまったく使用しないことをお勧めします。



結論



そのため、この記事では、JavaScriptでオブジェクトを作成する4つの方法について説明しました。



  • コンストラクター関数
  • クラス
  • オブジェクトのリンク
  • 工場機能


まず、クラスとFFがオブジェクトを作成するための最適な方法であるという結論に達しました。



次に、サブクラスはクラスを使用して作成する方が簡単であることがわかりました。ただし、合成の場合はFFを使用することをお勧めします。



第3に、カプセル化に関しては、クラスよりもFFの方が有利であると要約しました。クラスは特別な「#」プレフィックスを使用する必要があり、FFは変数を自動的にプライベートにするためです。



第4に、FFを使用すると、これをインスタンス参照として使用せずに実行できます。クラスでは、これをイベントハンドラーによって変更された元のコンテキストに戻すために、いくつかのトリックに頼る必要があります。



これですべてです。この記事を楽しんでいただけたでしょうか。清聴ありがとうございました。



All Articles