Apakah JavaScript memiliki tipe antarmuka (seperti 'antarmuka' Java)?


Jawaban:


650

Tidak ada anggapan "kelas ini harus memiliki fungsi-fungsi ini" (yaitu, tidak ada antarmuka per se), karena:

  1. Warisan JavaScript didasarkan pada objek, bukan kelas. Itu bukan masalah besar sampai Anda menyadari:
  2. JavaScript adalah bahasa yang diketik secara sangat dinamis - Anda dapat membuat objek dengan metode yang tepat, yang akan membuatnya sesuai dengan antarmuka, dan kemudian membatalkan semua hal yang membuatnya sesuai . Akan sangat mudah untuk menumbangkan sistem tipe - bahkan tanpa sengaja! - bahwa tidak layak untuk mencoba dan membuat sistem tipe di tempat pertama.

Sebaliknya, JavaScript menggunakan apa yang disebut bebek mengetik . (Jika berjalan seperti bebek, dan dukun seperti bebek, sejauh JS peduli, itu bebek.) Jika objek Anda memiliki duri (), metode berjalan (), dan terbang (), kode dapat menggunakannya di mana pun ia harapkan sebuah objek yang dapat berjalan, dukun, dan terbang, tanpa memerlukan implementasi beberapa antarmuka "Duckable". Antarmuka persis set fungsi yang menggunakan kode (dan nilai pengembalian dari fungsi-fungsi), dan dengan mengetik bebek, Anda mendapatkannya secara gratis.

Sekarang, bukan berarti kode Anda tidak akan gagal setengah jalan, jika Anda mencoba menelepon some_dog.quack(); Anda akan mendapatkan TypeError. Terus terang, jika Anda memberitahu anjing untuk dukun, Anda memiliki masalah yang sedikit lebih besar; Mengetik bebek bekerja paling baik ketika Anda menjaga semua bebek Anda berturut-turut, sehingga untuk berbicara, dan tidak membiarkan anjing dan bebek berbaur bersama kecuali Anda memperlakukan mereka sebagai hewan generik. Dengan kata lain, meskipun antarmuka itu cair, masih ada di sana; sering kali kesalahan menularkan anjing ke kode yang mengharapkannya untuk dukun dan terbang di tempat pertama.

Tetapi jika Anda yakin telah melakukan hal yang benar, Anda dapat mengatasi masalah anjing dukun dengan menguji keberadaan metode tertentu sebelum mencoba menggunakannya. Sesuatu seperti

if (typeof(someObject.quack) == "function")
{
    // This thing can quack
}

Jadi, Anda dapat memeriksa semua metode yang dapat Anda gunakan sebelum menggunakannya. Sintaksnya agak jelek. Ada cara yang sedikit lebih cantik:

Object.prototype.can = function(methodName)
{
     return ((typeof this[methodName]) == "function");
};

if (someObject.can("quack"))
{
    someObject.quack();
}

Ini adalah JavaScript standar, jadi itu harus bekerja pada penerjemah JS apa pun yang layak digunakan. Ini memiliki manfaat tambahan membaca seperti bahasa Inggris.

Untuk peramban modern (artinya, hampir semua peramban selain IE 6-8), bahkan ada cara untuk menjaga agar properti tidak muncul for...in:

Object.defineProperty(Object.prototype, 'can', {
    enumerable: false,
    value: function(method) {
        return (typeof this[method] === 'function');
    }
}

Masalahnya adalah bahwa objek IE7 tidak memiliki .definePropertysama sekali, dan di IE8, itu diduga hanya bekerja pada objek host (yaitu, elemen DOM dan semacamnya). Jika kompatibilitas merupakan masalah, Anda tidak dapat menggunakan .defineProperty. (Saya bahkan tidak akan menyebutkan IE6, karena agak tidak relevan lagi di luar China.)

Masalah lain adalah bahwa beberapa gaya pengkodean suka berasumsi bahwa setiap orang menulis kode yang buruk, dan melarang memodifikasi Object.prototypejika seseorang ingin menggunakan secara membabi buta for...in. Jika Anda peduli tentang itu, atau menggunakan kode ( rusak IMO ), coba versi yang sedikit berbeda:

function can(obj, methodName)
{
     return ((typeof obj[methodName]) == "function");
}

if (can(someObject, "quack"))
{
    someObject.quack();
}

7
Tidak seburuk yang seharusnya terjadi. for...inadalah - dan selalu - penuh dengan bahaya seperti itu, dan siapa pun yang melakukannya tanpa setidaknya mempertimbangkan bahwa seseorang ditambahkan ke Object.prototype(teknik yang tidak biasa, dengan pengakuan artikel itu sendiri) akan melihat kode mereka rusak di tangan orang lain.
cHao

1
@entonio: Saya akan mempertimbangkan kelenturan tipe bawaan sebagai fitur daripada masalah. Ini adalah bagian besar dari apa yang membuat shims / polyfill layak. Tanpanya, kami akan membungkus semua tipe bawaan dengan subtipe yang mungkin tidak kompatibel atau menunggu dukungan browser universal (yang mungkin tidak akan pernah datang, ketika browser tidak mendukung hal-hal yang menyebabkan orang tidak menggunakannya menyebabkan browser tidak ' t mendukungnya). Karena tipe bawaan dapat dimodifikasi, kita malah bisa menambahkan banyak fungsi yang belum ada.
cao

1
Dalam versi terakhir Javascript (1.8.5), Anda dapat menentukan properti objek yang tidak dapat dihitung. Dengan cara ini Anda dapat menghindari for...inmasalah. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Tomas Prado

1
@ Tomás: Sayangnya, sampai setiap browser menjalankan sesuatu yang kompatibel dengan ES5, kita masih harus khawatir tentang hal-hal seperti ini. Dan bahkan kemudian, " for...inmasalah" masih akan ada sampai taraf tertentu, karena akan selalu ada kode ceroboh ... baik, itu, dan Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});sedikit lebih banyak pekerjaan daripada sekadar obj.a = 3;. Saya benar-benar dapat memahami orang yang tidak berusaha melakukannya lebih sering. : P
cHao

1
Hehe ... suka pada "Terus terang, jika Anda memberitahu anjing untuk dukun, Anda memiliki masalah yang sedikit lebih besar. Analogi yang bagus untuk menunjukkan bahwa bahasa tidak boleh mencoba untuk menghindari kebodohan. Itu selalu merupakan pertempuran yang hilang. -Scott
Skooppa. com

73

Ambil salinan ' pola desain JavaScript ' oleh Dustin Diaz . Ada beberapa bab yang didedikasikan untuk mengimplementasikan antarmuka JavaScript melalui Duck Typing. Ini bacaan yang bagus juga. Tapi tidak, tidak ada implementasi bahasa asli antarmuka, Anda harus Duck Type .

// example duck typing method
var hasMethods = function(obj /*, method list as strings */){
    var i = 1, methodName;
    while((methodName = arguments[i++])){
        if(typeof obj[methodName] != 'function') {
            return false;
        }
    }
    return true;
}

// in your code
if(hasMethods(obj, 'quak', 'flapWings','waggle')) {
    //  IT'S A DUCK, do your duck thang
}

Metode yang dijelaskan dalam buku "pola desain pro javascript" mungkin merupakan pendekatan terbaik dari banyak hal yang telah saya baca di sini dan dari apa yang telah saya coba. Anda dapat menggunakan warisan di atasnya, yang membuatnya lebih baik untuk mengikuti konsep OOP. Beberapa mungkin mengklaim bahwa Anda tidak memerlukan konsep OOP di JS, tetapi saya mohon berbeda.
animageofmine

21

JavaScript (ECMAScript edition 3) implementsmenyimpan kata yang disimpan untuk digunakan di masa mendatang . Saya pikir ini dimaksudkan persis untuk tujuan ini, namun, dengan tergesa-gesa untuk mendapatkan spesifikasi keluar dari pintu mereka tidak punya waktu untuk menentukan apa yang harus dilakukan dengan itu, jadi, pada saat ini, browser tidak melakukan apa pun selain biarkan duduk di sana dan sesekali mengeluh jika Anda mencoba menggunakannya untuk sesuatu.

Adalah mungkin dan memang cukup mudah untuk membuat Object.implement(Interface)metode Anda sendiri dengan logika yang muncul setiap kali set properti / fungsi tertentu tidak diimplementasikan dalam objek tertentu.

Saya menulis artikel tentang orientasi objek di mana menggunakan notasi saya sendiri sebagai berikut :

// Create a 'Dog' class that inherits from 'Animal'
// and implements the 'Mammal' interface
var Dog = Object.extend(Animal, {
    constructor: function(name) {
        Dog.superClass.call(this, name);
    },
    bark: function() {
        alert('woof');
    }
}).implement(Mammal);

Ada banyak cara untuk menguliti kucing khusus ini, tetapi ini adalah logika yang saya gunakan untuk implementasi Antarmuka saya sendiri. Saya menemukan saya lebih suka pendekatan ini, dan mudah dibaca dan digunakan (seperti yang Anda lihat di atas). Itu berarti menambahkan metode 'implement' di Function.prototypemana beberapa orang mungkin memiliki masalah dengan, tetapi saya menemukan itu berfungsi dengan baik.

Function.prototype.implement = function() {
    // Loop through each interface passed in and then check 
    // that its members are implemented in the context object (this).
    for(var i = 0; i < arguments.length; i++) {
       // .. Check member's logic ..
    }
    // Remember to return the class being tested
    return this;
}

4
Sintaks ini benar-benar melukai otak saya, tetapi implementasinya di sini cukup menarik.
Cypher

2
Javascript pasti akan melakukan itu (melukai otak) khususnya ketika datang dari implementasi bahasa OO yang lebih bersih.
Steven de Salas

10
@StevendeSalas: Eh. JS sebenarnya cenderung sangat bersih ketika Anda berhenti berusaha memperlakukannya sebagai bahasa berorientasi kelas. Semua omong kosong yang diperlukan untuk meniru kelas, antarmuka, dll ... itulah yang benar - benar akan membuat otak Anda sakit. Prototipe? Hal-hal sederhana, sungguh, begitu Anda berhenti melawan mereka.
cHao

apa yang ada di "// .. Periksa logika anggota." ? seperti apa itu?
PositiveGuy

Hai @Kami, memeriksa logika anggota berarti mengulang-ulang properti yang diinginkan dan melempar kesalahan jika ada yang hilang .. ada sesuatu di sepanjang baris var interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}. Lihat bagian bawah tautan artikel untuk contoh yang lebih rumit.
Steven de Salas

12

Antarmuka JavaScript:

Meskipun JavaScript tidak memiliki interfacetipe, seringkali diperlukan. Untuk alasan yang berkaitan dengan sifat dinamis JavaScript dan penggunaan Prototypical-Inheritance, sulit untuk memastikan antarmuka yang konsisten di seluruh kelas - namun, dimungkinkan untuk melakukannya; dan sering ditiru.

Pada titik ini, ada beberapa cara tertentu untuk meniru Antarmuka dalam JavaScript; varians pada pendekatan biasanya memuaskan beberapa kebutuhan, sementara yang lain tidak tersentuh. Seringkali, pendekatan yang paling kuat terlalu rumit dan menghalangi implementor (pengembang).

Berikut adalah pendekatan untuk Kelas Antarmuka / Abstrak yang tidak terlalu rumit, eksplikatif, menjaga implementasi dalam Abstraksi seminimal mungkin, dan menyisakan ruang yang cukup untuk metodologi dinamis atau khusus:

function resolvePrecept(interfaceName) {
    var interfaceName = interfaceName;
    return function curry(value) {
        /*      throw new Error(interfaceName + ' requires an implementation for ...');     */
        console.warn('%s requires an implementation for ...', interfaceName);
        return value;
    };
}

var iAbstractClass = function AbstractClass() {
    var defaultTo = resolvePrecept('iAbstractClass');

    this.datum1 = this.datum1 || defaultTo(new Number());
    this.datum2 = this.datum2 || defaultTo(new String());

    this.method1 = this.method1 || defaultTo(new Function('return new Boolean();'));
    this.method2 = this.method2 || defaultTo(new Function('return new Object();'));

};

var ConcreteImplementation = function ConcreteImplementation() {

    this.datum1 = 1;
    this.datum2 = 'str';

    this.method1 = function method1() {
        return true;
    };
    this.method2 = function method2() {
        return {};
    };

    //Applies Interface (Implement iAbstractClass Interface)
    iAbstractClass.apply(this);  // .call / .apply after precept definitions
};

Peserta

Penyelesai Sila

The resolvePreceptfungsi adalah fungsi utilitas & pembantu untuk menggunakan dalam Anda Kelas Abstrak . Tugasnya adalah untuk memungkinkan implementasi khusus - penanganan Sila yang dienkapsulasi (data & perilaku) . Itu bisa melempar kesalahan atau memperingatkan - DAN - menetapkan nilai default ke kelas Implementor.

iAbstractClass

The iAbstractClassmendefinisikan interface yang akan digunakan. Pendekatannya memerlukan perjanjian diam-diam dengan kelas implementornya. Antarmuka ini menetapkan setiap sila ke namespace sila persis yang sama - ATAU - untuk apa pun yang dihasilkan fungsi Precept Resolver . Namun, perjanjian diam-diam memutuskan untuk konteks - ketentuan Pelaksana.

Pelaksana

The Pelaksana hanya 'setuju' dengan Interface ( iAbstractClass dalam kasus ini) dan menerapkannya dengan menggunakan Constructor-Pembajakan : iAbstractClass.apply(this). Dengan mendefinisikan data & perilaku di atas, dan kemudian membajak konstruktor Antarmuka - meneruskan konteks Implementor ke konstruktor Antarmuka - kami dapat memastikan bahwa penggantian Implementor akan ditambahkan, dan bahwa Antarmuka akan menjelaskan peringatan dan nilai default.

Ini adalah pendekatan yang sangat tidak rumit yang telah melayani tim saya & saya sangat baik selama perjalanan waktu dan proyek yang berbeda. Namun, itu memang memiliki beberapa peringatan & kekurangan.

Kekurangannya

Meskipun ini membantu menerapkan konsistensi di seluruh perangkat lunak Anda ke tingkat yang signifikan, ini tidak mengimplementasikan antarmuka yang sebenarnya - tetapi mengemulasi mereka. Meskipun definisi, default, dan peringatan atau kesalahan yang explicated, yang penjelasan penggunaan yang ditegakkan & menegaskan oleh pengembang (seperti dengan banyak pengembangan JavaScript).

Tampaknya ini adalah pendekatan terbaik untuk "Antarmuka dalam JavaScript" , namun, saya ingin melihat yang berikut ini terselesaikan:

  • Pernyataan jenis pengembalian
  • Pernyataan tanda tangan
  • Membekukan objek dari deletetindakan
  • Pernyataan tentang hal lain yang lazim atau dibutuhkan dalam kekhususan komunitas JavaScript

Yang mengatakan, saya harap ini membantu Anda sebanyak tim saya dan saya.


7

Anda memerlukan antarmuka di Java karena diketik secara statis dan kontrak antara kelas harus diketahui selama kompilasi. Dalam JavaScript berbeda. JavaScript diketik secara dinamis; itu berarti bahwa ketika Anda mendapatkan objek Anda hanya dapat memeriksa apakah ia memiliki metode tertentu dan menyebutnya.


1
Sebenarnya, Anda tidak perlu antarmuka di Java, itu gagal aman untuk memastikan objek memiliki API tertentu sehingga Anda bisa menukar mereka untuk implementasi lainnya.
BGerrissen

3
Tidak, mereka sebenarnya dibutuhkan di Java agar dapat membangun vtables untuk kelas yang mengimplementasikan antarmuka pada waktu kompilasi. Mendeklarasikan bahwa sebuah kelas mengimplementasikan sebuah antarmuka menginstruksikan kompiler untuk membangun sebuah struct kecil yang berisi pointer ke semua metode yang dibutuhkan oleh antarmuka itu. Kalau tidak, itu harus mengirim dengan nama saat runtime (seperti bahasa yang diketik secara dinamis lakukan)
munificent

Saya pikir itu tidak benar. Pengiriman selalu dinamis di java (kecuali mungkin suatu metode sudah final), dan fakta bahwa metode itu milik antarmuka tidak mengubah aturan pencarian. Alasan antarmuka diperlukan dalam bahasa yang diketik secara statis adalah agar Anda dapat menggunakan 'tipe semu' yang sama (antarmuka) untuk merujuk ke kelas yang tidak terkait.
entonio

2
@entonio: Pengiriman tidak sedinamis kelihatannya. Metode yang sebenarnya sering tidak diketahui sampai runtime, berkat polimorfisme, tetapi bytecode tidak mengatakan "aktifkan metode Anda"; ia mengatakan "aktifkan Superclass.yourMethod". JVM tidak dapat memanggil metode tanpa mengetahui kelas untuk mencarinya. Selama menautkan, ia mungkin diletakkan yourMethodpada entri # 5 di Superclassvtable, dan untuk setiap subkelas yang memiliki sendiri yourMethod, cukup tunjukkan bahwa entri subkelas # 5 pada implementasi yang sesuai.
cHao

1
@entonio: Untuk antarmuka, aturan memang sedikit berubah. (Bukan bahasa, tetapi bytecode yang dihasilkan dan proses pencarian JVM berbeda.) Kelas bernama Implementationyang mengimplementasikan SomeInterfacetidak hanya mengatakan itu mengimplementasikan seluruh antarmuka. Ini memiliki info yang mengatakan "Saya mengimplementasikan SomeInterface.yourMethod" dan menunjuk pada definisi metode untuk Implementation.yourMethod. Ketika panggilan JVM SomeInterface.yourMethod, itu terlihat di kelas untuk info tentang implementasi metode antarmuka itu, dan menemukan itu perlu untuk memanggil Implementation.yourMethod.
cHao

6

Harapan, bahwa siapa pun yang masih mencari jawaban merasa terbantu.

Anda dapat mencoba menggunakan Proksi (Ini adalah standar sejak ECMAScript 2015): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

latLngLiteral = new Proxy({},{
    set: function(obj, prop, val) {
        //only these two properties can be set
        if(['lng','lat'].indexOf(prop) == -1) {
            throw new ReferenceError('Key must be "lat" or "lng"!');
        }

        //the dec format only accepts numbers
        if(typeof val !== 'number') {
            throw new TypeError('Value must be numeric');
        }

        //latitude is in range between 0 and 90
        if(prop == 'lat'  && !(0 < val && val < 90)) {
            throw new RangeError('Position is out of range!');
        }
        //longitude is in range between 0 and 180
        else if(prop == 'lng' && !(0 < val && val < 180)) {
            throw new RangeError('Position is out of range!');
        }

        obj[prop] = val;

        return true;
    }
});

Maka Anda dapat dengan mudah mengatakan:

myMap = {}
myMap.position = latLngLiteral;

5

Ketika Anda ingin menggunakan transcompiler, maka Anda bisa mencoba TypeScript. Ini mendukung rancangan fitur ECMA (dalam proposal, antarmuka disebut " protokol ") mirip dengan apa yang dilakukan bahasa seperti coffeescript atau babel.

Di TypeScript antarmuka Anda dapat terlihat seperti:

interface IMyInterface {
    id: number; // TypeScript types are lowercase
    name: string;
    callback: (key: string; value: any; array: string[]) => void;
    type: "test" | "notATest"; // so called "union type"
}

Apa yang tidak bisa Anda lakukan:


3

tidak ada antarmuka asli dalam JavaScript, ada beberapa cara untuk mensimulasikan antarmuka. saya telah menulis paket yang melakukannya

Anda dapat melihat implantasi di sini


2

Javascript tidak memiliki antarmuka. Tapi itu bisa diketik bebek, contohnya bisa ditemukan di sini:

http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html


Saya suka pola yang digunakan artikel di tautan itu untuk membuat pernyataan tentang jenis. Terjadi kesalahan saat sesuatu tidak menerapkan metode yang seharusnya tepat seperti yang saya harapkan, dan saya suka bagaimana saya dapat mengelompokkan metode yang diperlukan ini bersama-sama (seperti antarmuka) jika saya melakukannya dengan cara ini.
Eric Dubé

1
Saya benci transpiling (dan sumber peta untuk debugging) tetapi Typescript sangat dekat dengan ES6 bahwa saya cenderung menahan hidung dan menyelam ke dalam Filescript. ES6 / Typescript menarik karena memungkinkan Anda untuk memasukkan properti di samping metode ketika mendefinisikan antarmuka (perilaku).
Reinsbrain

1

Saya tahu ini adalah yang lama, tetapi saya baru-baru ini menemukan diri saya membutuhkan lebih dan lebih untuk memiliki API yang berguna untuk memeriksa objek terhadap antarmuka. Jadi saya menulis ini: https://github.com/tomhicks/methodical

Ini juga tersedia melalui NPM: npm install methodical

Ini pada dasarnya melakukan semua yang disarankan di atas, dengan beberapa opsi untuk menjadi sedikit lebih ketat, dan semua tanpa harus melakukan banyak if (typeof x.method === 'function')boilerplate.

Semoga ada yang merasa bermanfaat.


Tom, saya baru saja menonton video AngularJS TDD dan ketika dia menginstal framework, salah satu paket dependen adalah paket metodis Anda! Kerja bagus!
Cody

Haha luar biasa. Saya pada dasarnya meninggalkannya setelah orang-orang di tempat kerja meyakinkan saya bahwa antarmuka dalam JavaScript tidak dapat digunakan. Baru-baru ini saya punya ide tentang perpustakaan yang pada dasarnya proksi objek untuk memastikan bahwa hanya metode tertentu yang digunakan pada dasarnya yang merupakan antarmuka. Saya masih berpikir antarmuka mendapat tempat dalam JavaScript! Bisakah Anda menautkan video itu? Saya ingin melihatnya.
Tom

Anda bertaruh, Tom. Saya akan mencoba menemukannya segera. Juga anekdot tentang antarmuka sebagai proksi juga. Bersulang!
Cody

1

Ini adalah pertanyaan lama, namun topik ini tidak pernah berhenti menggangguku.

Karena banyak jawaban di sini dan di seluruh web fokus pada "menegakkan" antarmuka, saya ingin menyarankan pandangan alternatif:

Saya merasakan kurangnya antarmuka yang paling ketika saya menggunakan beberapa kelas yang berperilaku sama (yaitu mengimplementasikan antarmuka ).

Sebagai contoh, saya memiliki Generator Email yang mengharapkan untuk menerima Pabrik Bagian Email , yang "tahu" bagaimana menghasilkan konten bagian dan HTML. Karenanya, mereka semua perlu memiliki semacam getContent(id)dan getHtml(content)metode.

Pola terdekat ke antarmuka (meskipun masih merupakan solusi) yang bisa saya pikirkan adalah menggunakan kelas yang akan mendapatkan 2 argumen, yang akan menentukan 2 metode antarmuka.

Tantangan utama dengan pola ini adalah bahwa metode harus static, atau untuk mendapatkan sebagai argumen instance itu sendiri, untuk mengakses propertinya. Namun ada beberapa kasus di mana saya menemukan pertukaran ini sepadan dengan kerumitan.

class Filterable {
  constructor(data, { filter, toString }) {
    this.data = data;
    this.filter = filter;
    this.toString = toString;
    // You can also enforce here an Iterable interface, for example,
    // which feels much more natural than having an external check
  }
}

const evenNumbersList = new Filterable(
  [1, 2, 3, 4, 5, 6], {
    filter: (lst) => {
      const evenElements = lst.data.filter(x => x % 2 === 0);
      lst.data = evenElements;
    },
    toString: lst => `< ${lst.data.toString()} >`,
  }
);

console.log('The whole list:    ', evenNumbersList.toString(evenNumbersList));
evenNumbersList.filter(evenNumbersList);
console.log('The filtered list: ', evenNumbersList.toString(evenNumbersList));


0

antarmuka abstrak seperti ini

const MyInterface = {
  serialize: () => {throw "must implement serialize for MyInterface types"},
  print: () => console.log(this.serialize())
}

buat sebuah instance:

function MyType() {
  this.serialize = () => "serialized "
}
MyType.prototype = MyInterface

dan gunakan itu

let x = new MyType()
x.print()
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.