Bagaimana cara mengatur prototipe objek JavaScript yang sudah dipakai?

106

Misalkan saya memiliki objek foodalam kode JavaScript saya. fooadalah objek yang kompleks dan dihasilkan di tempat lain. Bagaimana cara mengubah prototipe fooobjek?

Motivasi saya adalah menyetel prototipe yang sesuai ke objek yang diserialkan dari .NET ke literal JavaScript.

Misalkan saya telah menulis kode JavaScript berikut dalam halaman ASP.NET.

var foo = <%=MyData %>;

Misalkan itu MyDataadalah hasil pemanggilan .NET JavaScriptSerializerpada sebuah Dictionary<string,string>objek.

Pada saat run-time, ini menjadi sebagai berikut:

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

Seperti yang Anda lihat, foomenjadi larik objek. Saya ingin dapat memulai foodengan prototipe yang sesuai. Saya tidak ingin mengubah Object.prototypeatau Array.prototype. Bagaimana saya bisa melakukan ini?

Sungai Vivian
sumber
Apakah Anda ingin menambahkan prototipe yang sudah ada atau mengalihkannya ke prototipe baru?
SLaks
Apakah yang Anda maksud adalah membuat perubahan pada prototipe - atau benar-benar mengubah prototipe seperti mengganti satu prototipe dan menggantinya dengan yang lain. Saya bahkan tidak yakin kasus selanjutnya mungkin.
James Gaunt
2
Apakah yang Anda maksud adalah properti prototipe eksplisit atau tautan prototipe implisit ? (Keduanya adalah dua hal yang sangat berbeda)
Šime Vidas
Saya awalnya khawatir dengan ini: stackoverflow.com/questions/7013545/…
Sungai Vivian
1
Apakah Anda terbiasa dengan Backbone extendatau Google goog.inherit? Banyak pengembang menyediakan cara untuk membangun warisan sebelum memanggil newkonstruktor tua — yang sebelum kami diberikan Object.createdan tidak perlu khawatir tentang menimpa Object.prototype.
Ryan

Jawaban:

114

EDIT Februari 2012: jawaban di bawah ini sudah tidak akurat lagi. __proto__ sedang ditambahkan ke ECMAScript 6 sebagai "normatif opsional" yang berarti tidak diharuskan untuk diterapkan tetapi jika demikian, itu harus mengikuti seperangkat aturan yang diberikan. Ini saat ini belum terselesaikan tetapi setidaknya itu akan menjadi bagian resmi dari spesifikasi JavaScript.

Pertanyaan ini jauh lebih rumit daripada yang terlihat di permukaan, dan di luar nilai gaji kebanyakan orang dalam hal pengetahuan tentang internal Javascript.

The prototypeproperti dari objek yang digunakan saat membuat objek anak baru dari objek itu. Mengubahnya tidak mencerminkan objek itu sendiri, melainkan tercermin ketika objek itu digunakan sebagai konstruktor untuk objek lain, dan tidak ada gunanya mengubah prototipe objek yang sudah ada.

function myFactory(){};
myFactory.prototype = someOtherObject;

var newChild = new myFactory;
newChild.__proto__ === myFactory.prototype === someOtherObject; //true

Objek memiliki properti [[prototipe]] internal yang mengarah ke prototipe saat ini. Cara kerjanya adalah setiap kali sebuah properti pada sebuah objek dipanggil, ia akan mulai pada objek tersebut dan kemudian naik melalui rantai [[prototipe]] sampai ia menemukan kecocokan, atau gagal, setelah prototipe objek root. Ini adalah bagaimana Javascript memungkinkan untuk pembangunan runtime dan modifikasi objek; itu memiliki rencana untuk mencari apa yang dibutuhkannya.

The __proto__properti ada di beberapa implementasi (banyak sekarang): setiap pelaksanaan Mozilla, semua yang webkit saya tahu, beberapa orang lain. Properti ini menunjuk ke properti [[prototipe]] internal dan memungkinkan modifikasi pasca-pembuatan pada objek. Semua properti dan fungsi akan langsung beralih agar sesuai dengan prototipe karena pencarian berantai ini.

Fitur ini, meskipun distandarisasi sekarang, masih belum menjadi bagian wajib dari JavaScript, dan dalam bahasa yang mendukungnya memiliki kemungkinan besar untuk menjatuhkan kode Anda ke dalam kategori "tidak dioptimalkan". Mesin JS harus melakukan yang terbaik untuk mengklasifikasikan kode, terutama kode "panas" yang sangat sering diakses, dan jika Anda melakukan sesuatu yang menarik seperti memodifikasi __proto__, mereka tidak akan mengoptimalkan kode Anda sama sekali.

Posting ini https://bugzilla.mozilla.org/show_bug.cgi?id=607863 secara khusus membahas implementasi saat ini __proto__dan perbedaan di antara mereka. Setiap implementasi melakukannya secara berbeda, karena ini masalah yang sulit dan tidak terpecahkan. Segala sesuatu di Javascript bisa berubah, kecuali a.) Sintaks b.) Objek host (DOM ada di luar Javascript secara teknis) dan c.) __proto__. Sisanya sepenuhnya ada di tangan Anda dan setiap pengembang lainnya, jadi Anda dapat melihat mengapa __proto__menonjol seperti ibu jari yang sakit.

Ada satu hal yang __proto__memungkinkan hal itu tidak mungkin dilakukan: penunjukan prototipe objek pada waktu proses terpisah dari konstruktornya. Ini adalah kasus penggunaan yang penting dan merupakan salah satu alasan utama __proto__belum mati. Cukup penting bahwa ini menjadi poin diskusi yang serius dalam perumusan Harmoni, atau segera dikenal sebagai ECMAScript 6. Kemampuan untuk menentukan prototipe suatu objek selama pembuatan akan menjadi bagian dari versi Javascript berikutnya dan ini akan menjadi bel yang menunjukkan __proto__hari diberi nomor secara resmi.

Dalam jangka pendek, Anda dapat menggunakan __proto__jika Anda menargetkan browser yang mendukungnya (bukan IE, dan tidak ada IE yang akan mendukungnya). Kemungkinan itu akan berfungsi di webkit dan moz selama 10 tahun ke depan karena ES6 tidak akan selesai hingga 2013.

Brendan Eich - re: Pendekatan metode Objek baru di ES5 :

Maaf, ... tapi dapat diatur __proto__, terlepas dari kasus penggunaan initialiser objek (yaitu, pada objek baru yang belum dapat dijangkau, analog dengan Object.create ES5), adalah ide yang buruk. Saya menulis ini setelah merancang dan mengimplementasikan settable __proto__lebih dari 12 tahun yang lalu.

... kurangnya stratifikasi adalah masalah (pertimbangkan data JSON dengan kunci "__proto__"). Dan lebih buruk, mutabilitas berarti implementasi harus memeriksa rantai prototipe siklik untuk menghindari ilooping. [pemeriksaan konstan untuk rekursi tak terbatas diperlukan]

Akhirnya, mutasi __proto__pada objek yang sudah ada dapat merusak metode non-umum dalam objek prototipe baru, yang tidak mungkin bekerja pada objek penerima (langsung) yang __proto__sedang disetel. Ini hanyalah praktik buruk, suatu bentuk kebingungan tipe yang disengaja, secara umum.

pangular
sumber
Kerusakan luar biasa! Meskipun tidak persis sama dengan prototipe yang bisa berubah, ECMA Harmony mungkin akan mengimplementasikan proxy , yang memungkinkan Anda mengarahkan fungsionalitas tambahan ke objek tertentu menggunakan pola catchall.
Nick Husher
2
Masalah Brendan Eich berasal dari bahasa prototipe secara keseluruhan. Membuat __proto__ mungkin non-writable, configurableakan memperbaiki masalah ini dengan memaksa pengguna untuk mengkonfigurasi ulang properti secara eksplisit. Pada akhirnya, praktik buruk dikaitkan dengan penyalahgunaan kapabilitas bahasa, bukan kapabilitas itu sendiri. __Proto__ yang dapat ditulis bukannya tidak pernah terdengar. Ada banyak properti lain yang tidak dapat dihitung dan, meskipun ada bahayanya, ada praktik terbaik juga. Kepala palu tidak boleh dilepas hanya karena dapat digunakan secara tidak benar untuk melukai seseorang.
Putar
Mutasi __proto__diperlukan, karena Object.create hanya akan menghasilkan Objek, bukan fungsi misalnya, atau Simbol, Regex, elemen DOM atau objek host lainnya. Jika Anda ingin objek Anda dapat dipanggil, atau khusus dengan cara lain, tetapi masih mengubah rantai prototipe mereka, Anda terjebak tanpa dapat __proto__
diatur
2
Akan lebih baik jika tidak dilakukan dengan properti magis tetapi sebaliknya dengan Object.setPrototype, menghilangkan masalah " __proto__dalam JSON". Kekhawatiran Brendan Eich lainnya menggelikan. Rantai prototipe rekursif atau menggunakan prototipe dengan metode yang tidak memadai untuk objek yang diberikan adalah kesalahan programmer, bukan kesalahan bahasa dan seharusnya tidak menjadi faktor, dan selain itu bisa terjadi dengan Object.create serta dengan setel bebas __proto__.
pengguna2451227
1
IE 10 mendukung __proto__.
kzh
14

Anda dapat menggunakan constructorsebuah instance dari sebuah objek untuk mengubah prototipe sebuah objek di tempat. Saya percaya inilah yang Anda minta untuk dilakukan.

Ini berarti jika Anda memiliki fooyang merupakan contoh dari Foo:

function Foo() {}

var foo = new Foo();

Anda dapat menambahkan properti barke semua contoh Foodengan melakukan hal berikut:

foo.constructor.prototype.bar = "bar";

Berikut biola yang menunjukkan bukti konsep: http://jsfiddle.net/C2cpw/ . Tidak terlalu yakin bagaimana browser lama akan bekerja dengan pendekatan ini, tapi saya cukup yakin ini akan melakukan pekerjaan dengan cukup baik.

Jika Anda bermaksud untuk mencampur fungsionalitas ke dalam objek, cuplikan ini harus melakukan tugasnya:

function mix() {
  var mixins = arguments,
      i = 0, len = mixins.length;

  return {
    into: function (target) {
      var mixin, key;

      if (target == null) {
        throw new TypeError("Cannot mix into null or undefined values.");
      }

      for (; i < len; i += 1) {
        mixin = mixins[i];
        for (key in mixin) {
          target[key] = mixin[key];
        }

        // Take care of IE clobbering `toString` and `valueOf`
        if (mixin && mixin.toString !== Object.prototype.toString) {
          target.toString = mixin.toString;
        } else if (mixin && mixin.valueOf !== Object.prototype.valueOf) {
          target.valueOf = mixin.valueOf;
        }
      }
      return target;
    }
  };
};
Tim
sumber
1
+1: Ini informatif dan menarik, tetapi tidak terlalu membantu saya mendapatkan apa yang saya inginkan. Saya memperbarui pertanyaan saya menjadi lebih spesifik.
Sungai Vivian
Anda juga bisa menggunakanfoo.__proto__.bar = 'bar';
Jam Risser
9

Anda bisa melakukannya foo.__proto__ = FooClass.prototype, AFAIK yang didukung oleh Firefox, Chrome dan Safari. Perlu diingat bahwa __proto__properti tersebut tidak standar dan mungkin akan hilang pada suatu saat.

Dokumentasi: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto . Lihat juga http://www.mail-archive.com/[email protected]/msg00392.html untuk penjelasan mengapa tidak ada Object.setPrototypeOf()dan mengapa __proto__dihentikan.

Wladimir Palant
sumber
3

Anda dapat menentukan fungsi konstruktor proxy Anda dan kemudian membuat instance baru dan menyalin semua properti dari objek asli ke sana.

// your original object
var obj = { 'foo': true };

// your constructor - "the new prototype"
function Custom(obj) {
    for ( prop in obj ) {
        if ( obj.hasOwnProperty(prop) ) {
            this[prop] = obj[prop];
        }
    }
}

// the properties of the new prototype
Custom.prototype.bar = true;

// pass your original object into the constructor
var obj2 = new Custom(obj);

// the constructor instance contains all properties from the original 
// object and also all properties inherited by the new prototype
obj2.foo; // true
obj2.bar; // true

Demo langsung: http://jsfiddle.net/6Xq3P/

The Customkonstruktor merupakan prototipe baru, ergo, yang Custom.prototypeobjek berisi semua properti baru yang ingin Anda gunakan dengan objek asli Anda.

Di dalam Customkonstruktor, Anda cukup menyalin semua properti dari objek asli ke objek instance baru.

Objek instance baru ini berisi semua properti dari objek asli (mereka disalin ke dalamnya di dalam konstruktor), dan juga semua properti baru yang ditentukan di dalamnya Custom.prototype(karena objek baru adalah Custominstance).

Šime Vidas
sumber
3

Anda tidak dapat mengubah prototipe objek JavaScript yang sudah dibuat instance-nya dengan cara lintas browser. Seperti yang telah disebutkan orang lain, opsi Anda meliputi:

  1. mengubah non standar / cross browser __proto__properti
  2. Salin properti Objek ke objek baru

Keduanya tidak terlalu bagus, terutama jika Anda harus mengulang secara rekursif melalui objek menjadi objek dalam untuk secara efektif mengubah seluruh elemen prototipe.

Solusi alternatif untuk pertanyaan

Saya akan melihat lebih abstrak pada fungsionalitas yang Anda inginkan.

Pada dasarnya prototipe / metode hanya memungkinkan cara untuk mengelompokkan fungsi berdasarkan suatu objek.
Alih-alih menulis

function trim(x){ /* implementation */ }
trim('   test   ');

Anda menulis

'   test  '.trim();

Sintaks di atas telah menciptakan istilah OOP karena sintaks object.method (). Beberapa keunggulan utama OOP dibandingkan pemrograman fungsional tradisional meliputi:

  1. Nama metode yang pendek dan variabel yang lebih sedikit obj.replace('needle','replaced')vs harus mengingat nama seperti str_replace ( 'foo' , 'bar' , 'subject')dan lokasi variabel yang berbeda
  2. metode chaining ( string.trim().split().join()) adalah fungsi yang berpotensi lebih mudah untuk dimodifikasi dan ditulis kemudian fungsi bersarangjoin(split(trim(string))

Sayangnya di JavaScript (seperti yang ditunjukkan di atas) Anda tidak dapat memodifikasi prototipe yang sudah ada. Idealnya di atas Anda dapat memodifikasi Object.prototypehanya untuk Objek yang diberikan di atas, tetapi sayangnya memodifikasi Object.prototypeakan berpotensi merusak skrip (mengakibatkan benturan properti dan menimpa).

Tidak ada jalan tengah yang umum digunakan antara 2 gaya pemrograman ini, dan tidak ada cara OOP untuk mengatur fungsi kustom.

UnlimitJS menyediakan jalan tengah yang memungkinkan Anda menentukan metode kustom. Ini menghindari:

  1. Tabrakan properti, karena itu tidak memperluas prototipe Objek
  2. Masih memungkinkan untuk sintaks rantai OOP
  3. Ini adalah skrip browser lintas 450 byte (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) yang menyebabkan banyak masalah tabrakan properti prototipe JavaScript Unlimit

Menggunakan kode Anda di atas, saya hanya akan membuat namespace dari fungsi yang ingin Anda panggil terhadap objek.

Berikut ini contohnya:

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

// define namespace with methods
var $ = {
  log:function(){
    console.log(this);
    return this;
  }[Unlimit](),
  alert:function(){
    alert(''+this);
  }[Unlimit]()
}


foo[$.log]()
   [$.log]()
   [$.alert]();

Anda dapat membaca lebih banyak contoh di sini UnlimitJS . Pada dasarnya saat Anda memanggil [Unlimit]()suatu fungsi, ini memungkinkan fungsi tersebut dipanggil sebagai metode pada Objek. Ini seperti jalan tengah antara OOP dan jalan fungsional.

jeruk nipis
sumber
2

[[prototype]]Sejauh yang saya tahu , referensi dari objek yang sudah dibuat tidak dapat diubah . Anda dapat mengubah properti prototipe dari fungsi konstruktor asli tetapi, seperti yang telah Anda komentari, konstruktor itu adalah Object, dan mengubah konstruksi inti JS adalah Hal yang Buruk.

Anda dapat membuat objek proxy dari objek yang dibangun yang mengimplementasikan fungsionalitas tambahan yang Anda butuhkan. Anda juga bisa mencocokkan metode dan perilaku tambahan dengan menugaskan langsung ke objek yang dimaksud.

Mungkin Anda bisa mendapatkan apa yang Anda inginkan dengan cara lain, jika Anda bersedia melakukan pendekatan dari sudut yang berbeda: Apa yang perlu Anda lakukan yang melibatkan mengotak-atik prototipe?

Nick Husher
sumber
Adakah orang lain yang dapat mengomentari keakuratan ini?
Sungai Vivian
Tampaknya, berdasarkan jsfiddle yang mengerikan ini , Anda dapat mengubah prototipe objek yang ada dengan mengubah __proto__propertinya. Ini hanya akan berfungsi di browser yang mendukung __proto__notasi, yaitu Chrome dan Firefox, dan sudah tidak digunakan lagi . Jadi, singkatnya, Anda dapat mengubah [[prototype]]suatu objek, tetapi mungkin tidak seharusnya.
Nick Husher
1

Jika Anda mengetahui prototipe tersebut, mengapa tidak memasukkannya ke dalam kode?

var foo = new MyPrototype(<%= MyData %>);

Jadi, setelah data diserialkan, Anda mendapatkan

var foo = new MyPrototype([{"A":"1","B":"2"},{"X":"7","Y":"8"}]);

sekarang Anda hanya membutuhkan konstruktor yang menggunakan array sebagai argumen.

ditulis ulang
sumber
0

Tidak ada cara untuk benar-benar mewarisi dari Arrayatau "sub-kelas" itu.

Apa yang dapat Anda lakukan adalah ini ( PERINGATAN: KODE FESTERING DEPAN ):

function Foo(arr){
  [].push.apply(this, arr)
}
Foo.prototype = []
Foo.prototype.something = 123

var foo = new Foo(<%=MyData %>)

foo.length // => 2
foo[0] // => {"A":"1","B":"2"}
foo.something // => 123

Ini berfungsi, tetapi akan menyebabkan masalah tertentu bagi siapa pun yang melintasi jalurnya (terlihat seperti sebuah array, tetapi ada yang salah jika Anda mencoba memanipulasinya).

Mengapa Anda tidak menggunakan rute yang waras dan menambahkan metode / properti secara langsung foo, atau menggunakan konstruktor dan menyimpan array Anda sebagai properti?

function Foo(arr){
  this.items = arr
}
Foo.prototype = {
  someMethod : function(){ ... }
  //...
}

var foo = new Foo(<%=MyData %>)
foo.items // => [{"A":"1","B":"2"},{"X":"7","Y":"8"}]
Ricardo Tomasi
sumber
0

jika Anda ingin membuat prototipe dengan cepat, ini adalah salah satu caranya

function OntheFlyProto (info){
    this.items = info;
    this.y =-1;
    for(var i = 0; i < this.items.length ; i++){
        OntheFlyProto.prototype["get"+this.items[i].name] = function (){
            this.y++;
            return this.items[this.y].value;
        }
    }
}

var foo = [{name:"one", value:1},{name:"two", value:2}];
v = new OntheFlyProto(foo);
Yene Mulatu
sumber
-1
foo.prototype.myFunction = function(){alert("me");}
PhD
sumber
Tidak, tidak boleh. Itu adalah properti statis.
SLaks
@SLaks prototypestatis? Saya tidak yakin apa yang Anda maksud.
Šime Vidas
1
prototypeadalah properti suatu fungsi, bukan objek. Object.prototypeada; {}.prototypetidak.
SLaks
Jika Anda tidak peduli tentang kompatibilitas browser, Anda dapat menggunakan Object.getPrototypeOf(foo)untuk mendapatkan objek prototipe konstruktor. Anda dapat mengubah properti itu untuk memodifikasi prototipe objek. Ini hanya berfungsi di browser terbaru, tidak bisa memberi tahu Anda yang mana.
Nick Husher
@TeslaNick: Anda dapat langsung pergi dan memodifikasi Object.prototypekarena kemungkinan besar itulah yang Object.getPrototypeOf(foo)akan kembali. Tidak terlalu berguna.
Wladimir Palant