オブジェクト指向の用語については「オブジェクト指向プログラミング」を参照のこと。
ECMAScript2015(ES6)で導入された、キーワード class （クラス）は、オブジェクトを作成するためのテンプレートです{{Cite web
|url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
|title=Classes - JavaScript // MDN
|date=2021/11/10
|accessdate=2021/11/20
。
クラスは、データと、それ自身を処理するコードとともにカプセル化します。
クラスを導入した後もECMAScript/JavaScriptはプロトタイプベースのオブジェクト指向スクリプティング言語ですが、ES5までのクラス似のセマンティクスとは異なる構文やセマンティクスを持っています。
[https://paiza.io/projects/YVKrXPeS26opDanprCAxQw?language=javascript コード例]:
class Hello {
  constructor(name = "world") {
    this.name = name;
  }
  toString() {
    return `Hello ${this.name}`
  }
  
  print() {
    console.log(String(this))
  }
}
const hello = new Hello()
hello.print()
const hello2 = new Hello("my friend")
hello2.print()
console.log(
`typeof Hello === ${typeof Hello}
Object.getOwnPropertyNames(hello) === ${Object.getOwnPropertyNames(hello)}
Object.getOwnPropertyNames(hello.__proto__) === ${Object.getOwnPropertyNames(hello.__proto__)}`)
表示結果:
Hello world
Hello my friend
typeof Hello === function
Object.getOwnPropertyNames(hello) === name 
Object.getOwnPropertyNames(hello.__proto__) === constructor,toString,print
class Hello {
クラス定義の開始部分です。
クラス名Helloで、今回は特にスーパークラスを継承せずに定義しています。
とはいえ、暗黙に Object を継承しているので Object がスーパークラスだと言えます。
また、クラス定義中は strict モードになるので、宣言なしに変数を使うなどのラフなコードは書けません。
  constructor(name = "world") {
    this.name = name;
  }
new 演算子でクラスのインスタンスを作るとき呼出されます。
thisは、new演算子で作りかけのインスタンスです。
constructorというメソッド名は固定なので名称変更は不能、またクラス内に2つ以上は定義できません。
  toString() {
    return `Hello ${this.name}`
  }
Object.prototype.toStringは、文字列化するメソッドです。
  print() {
    console.log(String(this))
  }
printメソッドは継承元のObjectのprototypeににはないのでクラスHelｌoに固有なメソッドです。
String(this)は明示的な文字列化メソッドの呼び出しで、（上で定義した）this.toString()が暗黙に呼びだされます。
const hello = new Hello()
hello.print()
クラスのインスタンス化は、new演算子にクラス名と引数リストを伴って行います。
は必要です。
Hello world
パラメータが省略されたので、ディフォルトの "world" を使用。
const hello2 = new Hello("my friend")
hello2.print()
Hello my friend
今度は、"my friend" を伴って表示。
console.log(
`typeof Hello === ${typeof Hello}
Object.getOwnPropertyNames(hello) === ${Object.getOwnPropertyNames(hello)}
Object.getOwnPropertyNames(hello.__proto__) === ${Object.getOwnPropertyNames(hello.__proto__)}`)
typeof Hello === function
Object.getOwnPropertyNames(hello) === name 
Object.getOwnPropertyNames(hello.__proto__) === constructor,toString,print
クラス定義された識別子の typeof は function になりますこのあたりが、プロトタイプベース継承の糖衣構文たる所以です。
|url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
|title=Object.getOwnPropertyNames() - JavaScript // MDN
|date=2021/11/21
|accessdate=2021/11/22
。
__proto__ は Object.prototype のアクセサープロパティ (ゲッター関数およびセッター関数) です。
クラス間の関係で混乱しやすいのが、包含と継承です。
包含:クラスAがクラスBを1つまたは複数プロパティに持っています。⇒ クラスAはクラスBを包含しています。
継承:クラスAのプロパティPを参照したとき、A.Pが定義されていなければクラスBのプロパティPが参照されます。⇒ クラスAはクラスBを継承しています。
[https://paiza.io/projects/T3soCyAl2q2QmbsVXHykpA?language=javascript 包含と継承のサンプルコード]:
class Point {
  x
  y
  constructor(x = 0, y = 0) {
    console.info("Point::constructor")
    this.x = x
    this.y = y
  }
  move(dx = 0, dy = 0) {
    this.x += dx
    this.y += dy
    console.info('Point::move')
    return this
  }
}
class Shape {
  location
  constructor(x = 0, y = 0) {
    this.location = new Point(x, y)
    console.info("Shape::constructor")
  }
  move(x, y) {
    this.location.move(x, y)
    console.info('Shape::move')
    return this
  }
}
class Rectangle extends Shape {
  width
  height
  constructor(x = 0, y = 0, width = 0, height = 0) {
    super(x, y)
    this.width = width
    this.height = height
    console.info("Rectangle::constructor")
  }
}
console.info("Create a Rectangle!")
let rct = new Rectangle(12, 32, 100, 50)
console.info("rct = ", rct)
console.info('rct instanceof Rectangle => ', rct instanceof Rectangle)
console.info('rct instanceof Shape => ', rct instanceof Shape)
console.info('rct instanceof Point => ', rct instanceof Point)
rct.move(11, 21)
console.info("rct = ", rct)
let rct2 = new Rectangle(1, 2, 10, 150)
console.info("rct = ", rct)
console.info("rct2 = ", rct2)
実行結果:
Create a Rectangle!
Point::constructor
Shape::constructor
Rectangle::constructor
rct =  Rectangle { location: Point { x: 12, y: 32 }, width: 100, height: 50 }
rct instanceof Rectangle =>  true
rct instanceof Shape =>  true
rct instanceof Point =>  false
Point::move
Shape::move
rct =  Rectangle { location: Point { x: 23, y: 53 }, width: 100, height: 50 }
Point::constructor
Shape::constructor
Rectangle::constructor
rct =  Rectangle { location: Point { x: 23, y: 53 }, width: 100, height: 50 } 
rct2 =  Rectangle { location: Point { x: 1, y: 2 }, width: 10, height: 150 }
包含関係:
class Shape {
    location
    constructor(x = 0, y = 0) {
        this.location = new Point(x, y)
ShapeはPointを包含しています。
継承関係:
class Rectangle extends Shape {
    width
    height
    constructor(x = 0, y = 0, width = 0, height = 0) {
        super(x, y)
RectangleはShapeを継承しています。
はスーパークラス（この場合はShape）のコンストラクターの呼び出し。
ES/JSは、単一継承しかサポートしませんが包含やMix-inを使うことで、多重継承を使う動機となる「機能の合成」は実現できます。
ECMAScript/JavaScriptには複素数型がありません。
実装の触りだけですね実際に動くコードを観てみます。
[https://paiza.io/projects/Rlo71iVwVjMgXZUzSWGxlw?language=javascript ES6版]:
class Complex {
  constructor(real = 0, imag = 0) {
    return Object.assign(this,{ real, imag })
  }
  toString() { return `${this.real}+${this.imag}i` }
  cadd(n) { return new Complex(this.real + n.real, this.imag + n.imag) }
}
Complex.prototype.csub = function(n) { return new Complex(this.real - n.real, this.imag - n.imag) }
let a = new Complex(1, 1)
let b = new Complex(2, 3)
console.info("a = " + a)
console.info("b = " + b)
console.info("a + b = " + a.cadd(b))
console.info("a - b = " + a.csub(b))
console.info("a instanceof Complex =>", a instanceof Complex)
[https://paiza.io/projects/K2o2gPvM_tBbIFd3XM-lAA?language=javascript ES5版]:
function Complex(real = 0, imag = 0) {
    return Object.assign(this, { real, imag })
}
Object.assign(Complex.prototype, {
    toString(){ return `${this.real}+${this.imag}i` },
    cadd(n){ return new Complex(this.real + n.real, this.imag + n.imag) },
})
Complex.prototype.csub = function(n) { return new Complex(this.real - n.real, this.imag - n.imag) }
let a = new Complex(1, 1)
let b = new Complex(2, 3)
console.info("a = " + a)
console.info("b = " + b)
console.info("a + b = " + a.cadd(b))
console.info("a - b = " + a.csub(b))
console.info("a instanceof Complex =>", a instanceof Complex)
実行結果（ES6/ES5双方同じ）:
a = 1+1i
b = 2+3i
a + b = 3+4i
a - b = -1+-2i
a instanceof Complex => true
ES5では、constructorに相当する関数がclassに対応するクロージャを提供します。
オブジェクトにcadd()メソッドを定義しています（ES/JSでは演算子オーバーロードできないので、名前付き関数にしました）。
オブジェクトへのメソッドの追加:
Complex.prototype.csub = function(n) { return new Complex(this.real - n.real, this.imag - n.imag) }
ES5でもES6でも、オブジェクトの prototype にプロパティを追加することで、オブジェクトに新しいメソッドを追加することができます。
 は制限なくアクセスできますが、アクセサプロパティを定義することでアクセスを制限できます（ここではコードを簡素にすることを優先しました）
class構文を使った継承とfunctionを使った継承の間の小さな差ですが、classは関数と違って巻上げ (Hoisting) が起こりません。
関数宣言に対する関数式と同じ様に、クラス宣言に対してはクラス式があります。
構文:
class [クラス名] [extends スーパークラス名] {
 // クラス定義
}
[https://paiza.io/projects/I5JxhvfPC3si8UGuRvDk7g?language=javascript コード例]:
var cls1 = class {/*クラス定義*/}
function f() {
    return class {/*クラス定義*/}
}
var cls2 = f()
console.log(`cls1.name = "${cls1.name}"
cls2.name = "${cls2.name}"`)
実行結果:
cls1.name = "cls1" 
cls2.name = ""
クラス式ではクラス本体のクラス名は省略可能で、省略された場合にクラス式がスカラ変数の初期値あるいはスカラ変数に代入されていた場合 Class.nameはスカラ変数の変数名になります。
関数の戻り値でクラス式を返した場合、Class.nameは "" となります。
演算子を使います。
構文:
new クラス式の値([引数1[,引数2[..., 引数n]]])
カプセル化をしたい場合にそのクラスのインスタンスのプロパティにダイレクトにアクセスしたのでは本末転倒です（クラスの内部構造が変わったら、プロパティを参照するコードを全て変更するはめになります）。
そこでプロパティアクセス（obj.propやobj[propString]によるアクセス）をオーバーライドするアクセサプロパティが役に立ちます。
アクセサプロパティは、実際には存在しないプロパティが存在しているかのように見せる仕掛けです[https://tc39.es/ecma262/#sec-method-definitions ECMA-262::15.4 Method Definitions]。
ES6 の class を使ったコードと相当するES5のコードのES6の例を題材にアクセサプロパティを定義してみます。
[https://paiza.io/projects/BUjHE0rSBTH_IUDobE_s9g?language=javascript アクセサプロパティの使用例]:
class Complex {
  constructor(real = 0, imag = 0) {
      this.ary = new Float64Array([real, imag])
  }
  get real() { return this.ary[0] }
  set real(n) { return this.ary[0] = n }
  get imag() { return this.ary[1] }
  set imag(n) { return this.ary[1] = n }
  toString() { return `${this.real}+${this.imag}i` }
  cadd(n) { return new Complex(this.real + n.real, this.imag + n.imag) }
  csub(n) { return new Complex(this.real - n.real, this.imag - n.imag) }
}
let a = new Complex(1, 1)
let b = new Complex(2, 3)
console.info(`a = ${a}
b = ${b}
a.cadd(b) = ${a.cadd(b)}
a.csub(b) = ${a.csub(b)}`)
実行結果:
a = 1+1i
b = 2+3i
a.cadd(b) = 3+4i
a.csub(b) = -1+-2i
class Complex {
  constructor(real = 0, imag = 0) {
      this.ary = new Float64Array([real, imag])
  get real() { return this.ary[0] }
  set real(n) { return this.ary[0] = n }
  get imag() { return this.ary[1] }
  set imag(n) { return this.ary[1] = n }
real, imagを擬似プロパティとして、それぞれのセッターとゲッターを定義。
これで、n.real や n.imag で値を参照でき、n.real = 110 のように左辺値化もできます（n.real(), n.imag()や n.real(110) ではないことに注意してください）。
クラスにはstaticプロパティを定義できます。
クラスの正体はFunctionオブジェクトなので、関数のプロパティを定義していることになります。
[https://paiza.io/projects/5vnFxTJ7Bsclhbdm7ADlJQ?language=javascript staticプロパティの使用例]:
class Test {
    static min(){
      return 100;
    }
    static get MAX(){
      return 10000;
    }
    static n = 123
}
console.log(`Test.min() = ${Test.min()}
Test.MAX = ${Test.MAX}
Test.n = ${Test.n}`)
実行結果:
Test.min() = 100
Test.MAX = 10000 
Test.n = 123
クラスでは、フィールドを明示的に宣言することで、クラス定義がより自己文書化され、フィールドが常に存在するようになります{{Cite web
|url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#field_declarations
|title=Classes - JavaScript // MDN § Field declarations
|date=2021/11/10
|accessdate=2021/11/24
。
フィールドはデフォルト値を持つことも持たないことも宣言できます。
[ フィールドの使用例]:
class ClassWithPublicField {
  x
  constructor(x = 42) {
    this.x = x
  }
  value(c){ return this.x }
  static getValue(c){ return c.x }
}
var x = new ClassWithPublicField()
var y = new ClassWithPublicField(195)
console.log(x.value())
console.log(y.value())
console.log(ClassWithPublicField.getValue(x))
f = ClassWithPublicField.getValue
console.log(f(y))
実行結果:
42
195
42
195
の様に参照します。
トリッキーですが、staticメソッドからもフィールドを参照でき、staticメソッドの値を使った呼出しでもまたフィールドを参照できます。
クラスのインスタンスには、クラスの外からはプロパティアクセスのできないプライヴェートフィールドを設けることができます。
これはカプセル化に役立つ機能です。
[https://paiza.io/projects/9Qxuwy_MW8fArHGY3eYa3g?language=javascript プライヴェートフィールドの使用例]:
class ClassWithPrivateField {
  #x
  constructor(x = 42) {
    this.#x = x
  }
  value(c){ return this.#x }
  static getValue(c){ return c.#x }
}
var x = new ClassWithPrivateField()
var y = new ClassWithPrivateField(195)
console.log(x.value())
console.log(y.value())
console.log(ClassWithPrivateField.getValue(x))
f = ClassWithPrivateField.getValue
console.log(f(y))
実行結果:
42
195
42
195
の様に参照します。
トリッキーですが、staticメソッドからもプライヴェートフィールドを参照でき、staticメソッドの値を使った呼出しでもまたプライヴェートフィールドを参照できます。
ユーザー定義クラスのインスタンスを配列化する為には、コンストラクターのパラメータをコレクションにしたものを、イテレーションし Array::map() でコンストラクターに渡すと簡素に表現できます。
class Drink {
  #name
  #price
  constructor(name, price) {
    this.#name = name
    this.#price = price
  }
  toString() {
    return `${this.#name}: ${this.#price}`
  }
}
const drinks = Object.entries({
    milk:  180, 
    juice: 150
}).map(pair => new Drink(...pair))
drinks.forEach(drink => console.log(String(drink)))
実行結果:
milk: 180 
juice: 150
JavaScriptでは、ユーザー定義クラスのインスタンスもオブジェクトなので、Classのフィールドも（このコードのようにプライベートフィールドにしない限り）プロパティとしてアクセスできてしまいますが、これではカプセル化を破壊してしまいますし、より厄介なのはプロパティの綴りを間違えても警告もエラーも出ず、発見困難なバグの原因となってしまいます。
このため、Object.prototype.toString の様な共通化されたメソッド（インターフェースとも考えられます）を使い、内部構造を隠すことが肝要です。