Manfaat apa yang disediakan sintaks `class` ES2015 (ES6)?

87

Saya punya banyak pertanyaan tentang kelas ES6.

Apa manfaat menggunakan classsintaks? Saya membaca bahwa publik / privat / statis akan menjadi bagian dari ES7, apakah itu alasannya?

Selain itu, apakah classjenis OOP yang berbeda atau masih merupakan warisan prototipe JavaScript? Bisakah saya memodifikasinya menggunakan .prototype? Atau hanya objek yang sama tetapi dua cara berbeda untuk mendeklarasikannya.

Apakah ada manfaat kecepatan? Mungkin lebih mudah untuk mempertahankan / memahami jika Anda memiliki aplikasi besar seperti aplikasi besar?

ssbb.dll
sumber
Hanya pendapat saya sendiri: sintaks yang lebih baru jauh lebih mudah dipahami dan tidak memerlukan banyak pengalaman sebelumnya (terutama karena kebanyakan orang berasal dari bahasa OOP). Tetapi model objek prototipe perlu diketahui dan Anda tidak akan pernah belajar bahwa menggunakan sintaks baru, juga, akan lebih menantang untuk mengerjakan kode prototipe kecuali Anda sudah mengetahuinya. Jadi saya akan memilih untuk menulis semua kode saya sendiri dengan sintaks lama ketika saya memiliki pilihan itu
Zach Smith

Jawaban:

120

classSintaks baru , untuk saat ini , sebagian besar adalah gula sintaksis. (Tapi, Anda tahu, baik jenis gula.) Tidak ada dalam ES2015-ES2020 yang classdapat melakukan itu Anda tidak dapat melakukan dengan fungsi konstruktor dan Reflect.construct(termasuk subclassing Errordan Array¹). (Ini adalah kemungkinan akan ada beberapa hal dalam ES2021 yang dapat Anda lakukan dengan classbahwa Anda tidak bisa melakukan sebaliknya: bidang swasta , metode swasta , dan bidang statis / metode statis pribadi .)

Selain itu, apakah classjenis OOP yang berbeda atau masih merupakan warisan prototipe JavaScript?

Ini adalah warisan prototipe yang sama yang selalu kita miliki, hanya dengan sintaks yang lebih bersih dan nyaman jika Anda suka menggunakan fungsi konstruktor ( new Foo, dll.). (Khususnya dalam hal mengambil dari Arrayatau Error, yang tidak dapat Anda lakukan di ES5 dan sebelumnya. Sekarang Anda dapat melakukannya dengan Reflect.construct[ spec , MDN ], tetapi tidak dengan gaya ES5 yang lama.)

Bisakah saya memodifikasinya menggunakan .prototype?

Ya, Anda masih bisa memodifikasi prototypeobjek pada konstruktor kelas setelah Anda membuat kelas. Misalnya, ini legal:

class Foo {
    constructor(name) {
        this.name = name;
    }
    
    test1() {
        console.log("test1: name = " + this.name);
    }
}
Foo.prototype.test2 = function() {
    console.log("test2: name = " + this.name);
};

Apakah ada manfaat kecepatan?

Dengan memberikan idiom khusus untuk ini, saya kira mungkin saja mesinnya dapat melakukan pengoptimalan pekerjaan yang lebih baik. Tapi mereka sudah sangat pandai dalam mengoptimalkan, saya tidak mengharapkan perbedaan yang signifikan.

Manfaat apa yang disediakan classsintaks ES2015 (ES6) ?

Singkatnya: Jika Anda tidak menggunakan fungsi konstruktor pada awalnya, lebih suka Object.createatau serupa, classtidak berguna bagi Anda.

Jika Anda menggunakan fungsi konstruktor, ada beberapa keuntungan untuk class:

  • Sintaksnya lebih sederhana dan tidak terlalu rentan terhadap kesalahan.

  • Jauh lebih mudah (dan sekali lagi, lebih sedikit rawan kesalahan) untuk menyiapkan hierarki pewarisan menggunakan sintaks baru daripada dengan yang lama.

  • classmelindungi Anda dari kesalahan umum karena gagal menggunakan newfungsi konstruktor (dengan meminta konstruktor melontarkan pengecualian jika thisbukan objek yang valid untuk konstruktor).

  • Memanggil versi metode prototipe induk jauh lebih sederhana dengan sintaks baru daripada yang lama ( super.method()bukan ParentConstructor.prototype.method.call(this)atau Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)).

Berikut perbandingan sintaks untuk hierarki:

// ***ES2015+**
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    personMethod() {
        // ...
    }
}

class Employee extends Person {
    constructor(first, last, position) {
        super(first, last);
        this.position = position;
    }

    employeeMethod() {
        // ...
    }
}

class Manager extends Employee {
    constructor(first, last, position, department) {
        super(first, last, position);
        this.department = department;
    }

    personMethod() {
        const result = super.personMethod();
        // ...use `result` for something...
        return result;
    }

    managerMethod() {
        // ...
    }
}

Contoh:

vs.

// **ES5**
var Person = function(first, last) {
    if (!(this instanceof Person)) {
        throw new Error("Person is a constructor function, use new with it");
    }
    this.first = first;
    this.last = last;
};

Person.prototype.personMethod = function() {
    // ...
};

var Employee = function(first, last, position) {
    if (!(this instanceof Employee)) {
        throw new Error("Employee is a constructor function, use new with it");
    }
    Person.call(this, first, last);
    this.position = position;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.employeeMethod = function() {
    // ...
};

var Manager = function(first, last, position, department) {
    if (!(this instanceof Manager)) {
        throw new Error("Manager is a constructor function, use new with it");
    }
    Employee.call(this, first, last, position);
    this.department = department;
};
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
Manager.prototype.personMethod = function() {
    var result = Employee.prototype.personMethod.call(this);
    // ...use `result` for something...
    return result;
};
Manager.prototype.managerMethod = function() {
    // ...
};

Contoh Langsung:

Seperti yang Anda lihat, banyak hal berulang dan bertele-tele di sana yang mudah salah dan membosankan untuk diketik ulang (itulah sebabnya saya menulis skrip untuk melakukannya , dulu).


¹ "Tidak ada dalam ES2015-ES2018 yang classdapat melakukan hal yang tidak dapat Anda lakukan dengan fungsi konstruktor dan Reflect.construct(termasuk subclassing ErrordanArray )"

Contoh:

TJ Crowder
sumber
9
Saya tidak tahu apakah ini perbandingan yang adil. Anda dapat mencapai objek serupa tanpa semua cruft. Ini menggunakan cara JS untuk menghubungkan objek melalui rantai prototipe daripada mendekati pewarisan klasik. Saya membuat intisari yang menunjukkan contoh sederhana berdasarkan milik Anda untuk menunjukkan cara melakukan ini.
Jason Cust
8
@JasonCust: Ya, ini perbandingan yang adil, karena saya menunjukkan cara ES5 untuk melakukan apa yang dilakukan ES6 class. JavaScript cukup fleksibel sehingga Anda tidak perlu menggunakan fungsi konstruktor jika Anda tidak mau, itulah yang Anda lakukan, tetapi itu berbeda dari class- intinya classadalah menyederhanakan pewarisan pseudo-klasik dalam JavaScript.
TJ Crowder
3
@JasonCust: Ya, ini cara yang berbeda. Saya tidak akan menyebutnya "lebih asli" (saya tahu Kyle melakukannya, tapi itu Kyle). Fungsi konstruktor sangat penting untuk JavaScript, lihat semua tipe bawaan (kecuali bahwa faksi anti-konstruktor entah bagaimana berhasil Symbolmengacaukannya). Tetapi hal yang hebat, sekali lagi, adalah jika Anda tidak ingin menggunakannya, Anda tidak perlu melakukannya. Saya menemukan dalam pekerjaan saya bahwa fungsi konstruktor masuk akal di banyak tempat dan semantik newmeningkatkan kejelasan; dan Object.createmasuk akal di banyak tempat lain, khususnya. situasi fasad. Mereka saling melengkapi. :-)
TJ Crowder
4
meh. Saya tidak membeli kebutuhan untuk ini. Saya menjauh dari c # untuk menjauh dari OOP dan sekarang OOP merayap di javascript. Saya tidak suka tipe. semuanya harus berupa var / object / function. itu dia. tidak ada kata kunci yang lebih asing. dan warisan adalah penipuan. jika Anda ingin melihat pertarungan yang bagus antara tipe dan nontipe. lihat sabun vs istirahat. dan kita semua tahu siapa yang memenangkannya.
Shai UI
3
@foreyez: classsintaks tidak membuat JavaScript menjadi lebih diketik daripada sebelumnya. Seperti yang saya katakan di jawaban, itu sebagian besar hanya (jauh) sintaks yang lebih sederhana untuk apa yang sudah ada. Benar-benar ada kebutuhan untuk itu, orang-orang terus-menerus salah menggunakan sintaks lama. Ini juga tidak berarti Anda harus menggunakan warisan lebih dari yang Anda lakukan sebelumnya. (Dan tentu saja, jika Anda tidak pernah menyiapkan "kelas" dengan sintaks lama, Anda juga tidak perlu melakukannya dengan sintaks baru.)
TJ Crowder
22

Kelas ES6 adalah gula sintaksis untuk sistem kelas prototipe yang kita gunakan saat ini. Mereka membuat kode Anda lebih ringkas dan mendokumentasikan diri sendiri, yang merupakan alasan yang cukup untuk menggunakannya (menurut saya).

Menggunakan Babel untuk memindahkan kelas ES6 ini:

class Foo {
  constructor(bar) {
    this._bar = bar;
  }

  getBar() {
    return this._bar;
  }
}

akan memberi Anda sesuatu seperti:

var Foo = (function () {
  function Foo(bar) {    
    this._bar = bar;
  }

  Foo.prototype.getBar = function () {
    return this._bar;
  }

  return Foo;
})();

Versi kedua tidak jauh lebih rumit, ini lebih banyak kode yang harus dipertahankan. Ketika Anda terlibat dalam warisan, pola-pola itu menjadi lebih rumit.

Karena kelas mengkompilasi ke pola prototipe yang sama yang telah kita gunakan, Anda dapat melakukan manipulasi prototipe yang sama pada mereka. Itu termasuk menambahkan metode dan sejenisnya pada waktu proses, mengakses metode Foo.prototype.getBar, dll.

Ada beberapa dukungan dasar untuk privasi di ES6 hari ini, meskipun itu didasarkan pada tidak mengekspor objek yang tidak ingin Anda akses. Misalnya, Anda dapat:

const BAR_NAME = 'bar';

export default class Foo {
  static get name() {
    return BAR_NAME;
  }
}

dan BAR_NAMEtidak akan tersedia untuk modul lain sebagai referensi secara langsung.

Banyak perpustakaan telah mencoba mendukung atau menyelesaikan ini, seperti Backbone dengan mereka extends helper yang mengambil hash yang tidak tervalidasi dari fungsi dan properti seperti metode, tetapi tidak ada sistem yang konsisten untuk mengekspos warisan prototipe yang tidak melibatkan penyia-nyiaan dengan prototipe.

Karena kode JS menjadi lebih rumit dan basis kode lebih besar, kami mulai mengembangkan banyak pola untuk menangani hal-hal seperti pewarisan dan modul. IIFE yang digunakan untuk membuat ruang lingkup privat untuk modul memiliki banyak tanda kurung dan kurung; kehilangan salah satu dari mereka dapat menghasilkan skrip valid yang melakukan sesuatu yang sama sekali berbeda (melewatkan titik koma setelah modul dapat meneruskan modul berikutnya sebagai parameter, yang jarang bagus).

tl; dr: itu gula untuk apa yang sudah kita lakukan dan membuat niat Anda jelas dalam kode.

ssube.dll
sumber