Zaten somutlaştırılmış bir JavaScript nesnesinin prototipi nasıl ayarlanır?


106

fooJavaScript kodumda bir nesnem olduğunu varsayalım . fookarmaşık bir nesnedir ve başka bir yerde üretilir. fooNesnenin prototipini nasıl değiştirebilirim ?

Benim motivasyonum, .NET'ten JavaScript değişmez değerlerine serileştirilmiş nesnelere uygun prototipler ayarlamaktır.

Bir ASP.NET sayfasında aşağıdaki JavaScript kodunu yazdığımı varsayalım.

var foo = <%=MyData %>;

Bunun bir nesnede MyData.NET'i çağırmanın sonucu olduğunu varsayalım .JavaScriptSerializerDictionary<string,string>

Çalışma zamanında bu şu olur:

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

Gördüğünüz gibi foo, nesneler dizisi haline geliyor. fooUygun bir prototip ile başlatabilmek istiyorum . Ben do not değiştirmek istiyor Object.prototypene de Array.prototype. Bunu nasıl yapabilirim?


Mevcut prototipine eklemek mi yoksa yeni bir prototipe geçirmek mi istiyorsunuz?
SLaks

Prototipte değişiklik yapmayı mı kastediyorsunuz - yoksa bir prototipi değiştirip başka bir prototipi değiştirirken olduğu gibi prototipi gerçekten değiştirmeyi mi kastediyorsunuz? Daha sonraki davanın mümkün olduğundan bile emin değilim.
James Gaunt

2
Açık prototip özelliğini mi yoksa örtük prototip bağlantısını mu kastediyorsunuz ? (Bu ikisi çok farklı iki şey)
Šime Vidas

Başlangıçta bununla ilgilenmiştim: stackoverflow.com/questions/7013545/…
Vivian River

1
Backbone extendveya Google'ınkini biliyor musunuz goog.inherit? Birçok geliştirici, yaşlı kurucuyu aramadan önce miras oluşturmanın yollarını sağlar - newbu, bize verilmeden önceydi Object.createve geçersiz kılma konusunda endişelenmemize gerek yoktu Object.prototype.
Ryan

Yanıtlar:


114

Şubat 2012'de DÜZENLE: Aşağıdaki cevap artık doğru değil. __proto__, ECMAScript 6'ya "normatif isteğe bağlı" olarak ekleniyor; bu, uygulanması gerekmediği, ancak uygulanıyorsa, verilen kurallara uyması gerektiği anlamına gelir. Bu şu anda çözülmedi, ancak en azından resmi olarak JavaScript'in spesifikasyonunun bir parçası olacak.

Bu soru, yüzeyde göründüğünden çok daha karmaşıktır ve çoğu insanın Javascript iç bilgisine göre maaş notunun ötesinde.

prototypeBu nesnenin yeni bir alt nesneler oluştururken, bir nesnenin özelliği kullanılır. Bunu değiştirmek nesnenin kendisine yansımaz, daha ziyade bu nesne diğer nesneler için bir kurucu olarak kullanıldığında yansıtılır ve mevcut bir nesnenin prototipini değiştirmede hiçbir faydası yoktur.

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

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

Nesnelerin, geçerli prototipe işaret eden dahili bir [[prototip]] özelliği vardır. Çalışma şekli, bir nesne üzerindeki bir özellik çağrıldığında, nesnede başlayacak ve ardından bir eşleşme bulana kadar veya kök Nesne prototipinden sonra başarısız olana kadar [[prototip]] zincirinde yukarı çıkacaktır. Javascript, nesnelerin çalışma zamanında oluşturulmasına ve değiştirilmesine bu şekilde izin verir; ihtiyacı olanı aramak için bir planı vardır.

__proto__Mülkiyet (şimdi çok) bazı uygulamalarda bulunmaktadır: Herhangi Mozilla uygulanmasını Bildiğim bütün webkit olanlar, bazı diğerleri. Bu özellik dahili [[prototip]] özelliğine işaret eder ve nesneler üzerinde oluşturma sonrası değişikliklere izin verir. Bu zincirleme arama sayesinde, herhangi bir özellik ve işlev anında prototipe uyacak şekilde geçiş yapacaktır.

Bu özellik, şu anda standartlaştırılmış olsa da, hala JavaScript'in gerekli bir parçası değildir ve onu destekleyen dillerde, kodunuzu "optimize edilmemiş" kategorisine düşürme olasılığı yüksektir. JS motorları, kodu, özellikle çok sık erişilen "sıcak" kodu sınıflandırmak için ellerinden gelenin en iyisini yapmalıdır ve değiştirmek gibi süslü bir şey yapıyorsanız __proto__, kodunuzu hiç optimize etmezler.

Bu yayınlar https://bugzilla.mozilla.org/show_bug.cgi?id=607863 özellikle mevcut uygulamaları __proto__ve bunlar arasındaki farkları tartışmaktadır . Her uygulama bunu farklı şekilde yapar, çünkü bu zor ve çözülmemiş bir sorundur. A.) Sözdizimi b.) Ana nesneler (DOM teknik olarak Javascript dışında bulunur) ve c.) Dışında Javascript'teki her şey değiştirilebilir __proto__. Gerisi tamamen sizin ve diğer tüm geliştiricilerin elinde, bu yüzden neden __proto__ağrılı bir başparmak gibi dışarı çıktığını anlayabilirsiniz .

Buna __proto__izin veren bir şey , aksi takdirde imkansızdır: bir nesnenin prototipinin, kurucusundan ayrı olarak çalışma zamanında atanması. Bu önemli bir kullanım durumudur ve __proto__zaten ölmemiş olan birincil nedenlerden biridir . Harmony'nin formülasyonunda ciddi bir tartışma noktası olması veya yakında ECMAScript 6 olarak bilinmesi yeterince önemlidir. Oluşturma sırasında bir nesnenin prototipini belirleme yeteneği, Javascript'in bir sonraki sürümünün bir parçası olacak ve bu, __proto__günlerini gösteren zil resmi olarak numaralandırılmıştır.

Kısa vadede, __proto__onu destekleyen tarayıcıları hedefliyorsanız kullanabilirsiniz (IE'yi değil ve hiçbir IE'yi desteklemeyecek). ES6 2013'e kadar tamamlanmayacağından önümüzdeki 10 yıl boyunca webkit ve moz'da çalışacak.

Brendan Eich - re: ES5'teki yeni Nesne yöntemlerine yaklaşım :

Maalesef ... ama __proto__nesne başlatıcı kullanım durumu dışında ayarlanabilir (yani, ES5'in Object.create ile benzer şekilde, henüz erişilemeyen yeni bir nesnede), korkunç bir fikir. Bunu __proto__12 yıl önce tasarlanıp uygulanarak yazıyorum .

... tabakalaşma eksikliği bir sorundur (JSON verilerini bir anahtarla düşünün "__proto__"). Daha da kötüsü, değişkenlik, uygulamaların ilooping'i önlemek için döngüsel prototip zincirlerini kontrol etmesi gerektiği anlamına gelir. [sonsuz özyineleme için sürekli kontroller gereklidir]

Son olarak, __proto__mevcut bir nesnede mutasyon , yeni prototip nesnesindeki jenerik olmayan yöntemleri bozabilir ve __proto__bu, ayarlanan alıcı (doğrudan) nesne üzerinde muhtemelen çalışamaz . Bu sadece kötü bir uygulamadır, genel olarak kasıtlı bir tür kafa karışıklığıdır.


Mükemmel arıza! Değişken bir prototip ile tam olarak aynı şey olmasa da, ECMA Harmony büyük olasılıkla proxy'ler uygulayacaktır , bu da bir catchall modeli kullanarak belirli nesnelere ek işlevsellik sunmanıza izin verir.
Nick Husher

2
Brendan Eich'in sorunu, bir bütün olarak dillerin prototipini oluşturmaya özgüdür. Muhtemelen __proto__ yapmak non-writable, configurable, kullanıcıyı mülkü açıkça yeniden yapılandırmaya zorlayarak bu endişeleri giderebilir . Sonunda, kötü uygulamalar, yeteneklerle değil, bir dilin yeteneklerinin kötüye kullanılmasıyla ilişkilidir. Yazılabilir __proto__ duyulmamış değil. Sayılamayan birçok yazılabilir özellik vardır ve tehlikeler olsa da en iyi uygulamalar da vardır. Çekiçin başı, uygunsuz bir şekilde birini yaralamak için kullanılabileceği için çıkarılmamalıdır.
döner

Mutasyona __proto__Object.create sadece Nesneleri değil işlevlerini örneğin, ne Sembolleri, Regexes, DOM öğelerini veya diğer ev sahibi nesneleri üretecek çünkü gereklidir. Nesnelerinizin çağrılabilir veya başka şekillerde özel olmasını istiyorsanız, ancak yine de prototip zincirlerini değiştiriyorsanız, ayarlanabilir __proto__veya Proxy'ler olmadan sıkışıp
kalıyorsunuz

2
Büyülü bir özellik ile yapılmasaydı, bunun yerine Object.setPrototype ile "JSON'da __proto__" endişesini ortadan kaldırarak daha iyi olurdu . Brendan Eich'in diğer endişeleri gülünç. Özyinelemeli prototip zincirleri veya verilen nesne için yetersiz yöntemlerle bir prototip kullanmak, programcı hatalarıdır, dil hataları değildir ve bir faktör olmamalıdır ve ayrıca Object.create ile olduğu gibi serbestçe ayarlanabilir de olabilirler __proto__.
user2451227

1
IE 10 destekler __proto__.
kzh


14

constructorYerinde bir nesnenin prototipini değiştirmek için bir nesnenin bir örneğini kullanabilirsiniz . Yapmak istediğin şeyin bu olduğuna inanıyorum.

Bu foo, aşağıdakilerin bir örneğine sahipseniz anlamına gelir Foo:

function Foo() {}

var foo = new Foo();

Aşağıdakileri yaparak bartüm örneklerine bir özellik ekleyebilirsiniz Foo:

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

İşte kavram kanıtını gösteren bir keman: http://jsfiddle.net/C2cpw/ . Eski tarayıcıların bu yaklaşımı kullanarak ne kadar başarılı olacağından çok emin değilim, ancak bunun işi oldukça iyi yapacağından oldukça eminim.

Amacınız işlevselliği nesnelerle karıştırmaksa, bu kod parçacığı işi yapmalıdır:

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;
    }
  };
};

1
+1: Bu bilgilendirici ve ilginç, ancak istediğimi elde etmeme gerçekten yardımcı olmuyor. Sorumu daha spesifik olacak şekilde güncelliyorum.
Vivian Nehri

Ayrıca şunu da kullanabilirsinizfoo.__proto__.bar = 'bar';
Jam Risser

9

foo.__proto__ = FooClass.prototypeFirefox, Chrome ve Safari tarafından desteklenen AFAIK yapabilirsiniz . __proto__Mülkün standart dışı olduğunu ve bir noktada ortadan kalkabileceğini unutmayın.

Belgeler: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto . Ayrıca neden hayır olmadığına ve neden kullanımdan kaldırıldığına ilişkin bir açıklama için http://www.mail-archive.com/jsmentors@googlegroups.com/msg00392.html adresine bakın .Object.setPrototypeOf()__proto__


3

Proxy yapıcı işlevinizi tanımlayabilir ve ardından yeni bir örnek oluşturabilir ve tüm özellikleri orijinal nesneden ona kopyalayabilirsiniz.

// 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

Canlı demo: http://jsfiddle.net/6Xq3P/

CustomYapıcı yeni prototip, ergo, onun temsil ettiği Custom.prototypenesne orijinal nesneyle kullanmak istediğiniz tüm yeni özellikler içeriyor.

Yapıcının içinde, Customorijinal nesnenin tüm özelliklerini yeni örnek nesnesine kopyalamanız yeterlidir.

Bu yeni örnek nesne, orijinal nesnenin tüm özelliklerini (yapıcı içinde ona kopyalanmışlardır) ve ayrıca içinde tanımlanan tüm yeni özellikleri Custom.prototype(çünkü yeni nesne bir Customörnek olduğu için ) içerir.


3

Tarayıcılar arası bir şekilde zaten örneği oluşturulmuş bir JavaScript nesnesinin prototipini değiştiremezsiniz. Başkalarının da bahsettiği gibi, seçenekleriniz şunları içerir:

  1. standart olmayan / çapraz tarayıcı __proto__özelliğini değiştirme
  2. Nesneler özelliklerini yeni bir nesneye kopyalayın

Bunların hiçbiri özellikle harika değildir, özellikle bir elementi tüm prototipi etkili bir şekilde değiştirmek için bir nesneyi iç nesnelere tekrar tekrar döndürmek zorunda kalırsanız.

Soruya alternatif çözüm

İstediğiniz gibi görünen işlevselliğe daha soyut bir göz atacağım.

Temel olarak prototip / yöntemler, işlevleri bir nesneye dayalı olarak gruplamanın bir yolunu sağlar.
Yazmak yerine

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

Sen yaz

'   test  '.trim();

Yukarıdaki sözdizimi, object.method () sözdizimi nedeniyle OOP terimi olarak oluşturulmuştur. OOP'lerin geleneksel fonksiyonel programlamaya göre temel avantajlarından bazıları şunları içerir:

  1. Kısa metot isimleri ve daha az değişken obj.replace('needle','replaced')vs gibi isimleri str_replace ( 'foo' , 'bar' , 'subject')ve farklı değişkenlerin yerlerini hatırlamak zorunda olma
  2. method chaining ( string.trim().split().join()), daha sonra iç içe geçmiş işlevlerin değiştirilmesi ve yazılması potansiyel olarak daha kolaydırjoin(split(trim(string))

Maalesef JavaScript'te (yukarıda gösterildiği gibi) zaten var olan bir prototipi değiştiremezsiniz. İdeal olarak yukarıda Object.prototype, yalnızca yukarıda verilen Nesneler için değişiklik yapabilirsiniz , ancak ne yazık ki değişiklik Object.prototype, komut dosyalarını bozabilir (özellik çakışması ve geçersiz kılma ile sonuçlanır).

Bu 2 programlama stili arasında yaygın olarak kullanılan bir orta yol yoktur ve özel işlevleri düzenlemenin OOP yolu yoktur.

UnlimitJS , özel yöntemler tanımlamanıza izin veren bir orta yol sağlar. Şunları önler:

  1. Nesnelerin prototiplerini genişletmediği için özellik çakışması
  2. Yine de bir OOP zincirleme sözdizimine izin verir
  3. Unlimit'in JavaScript'in prototip özelliği çarpışma sorunlarının çoğunu yapan 450 bayt çapraz tarayıcı (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) komut dosyasıdır.

Yukarıdaki kodunuzu kullanarak, nesneye karşı çağırmayı düşündüğünüz işlevlerin bir ad alanını oluşturabilirim.

İşte bir örnek:

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]();

UnlimitJS'de daha fazla örnek okuyabilirsiniz . Temel [Unlimit]()olarak bir işlevi çağırdığınızda , işlevin bir Nesne üzerinde bir yöntem olarak çağrılmasına izin verir. OOP ile işlevsel yollar arasında bir orta yol gibi.


2

[[prototype]]Bildiğim kadarıyla, önceden oluşturulmuş nesnelerin referansını değiştiremezsiniz . Orijinal yapıcı işlevinin prototip özelliğini değiştirebilirsiniz, ancak daha önce yorumladığınız gibi, bu yapıcıdır Objectve çekirdek JS yapılarını değiştirmek Kötü Bir Şeydir.

Yine de ihtiyacınız olan ek işlevselliği uygulayan, yapılandırılmış nesnenin bir proxy nesnesini oluşturabilirsiniz. Ayrıca, söz konusu nesneye doğrudan atayarak ek yöntemleri ve davranışları da ekleyebilirsin.

Belki de istediğini başka bir yoldan elde edebilirsin, eğer farklı bir açıdan yaklaşmaya istekliysen: Prototiple uğraşmayı içeren neye ihtiyacın var?


Bunun doğruluğu hakkında başka biri yorum yapabilir mi?
Vivian Nehri

Bu korkunç jsfiddle'a dayanarak, var olan bir nesnenin prototipini özelliğini değiştirerek değiştirebileceğiniz anlaşılıyor__proto__ . Bu, yalnızca __proto__gösterimi destekleyen Chrome ve Firefox tarayıcılarda çalışacaktır ve kullanımdan kaldırılmıştır . Kısacası, [[prototype]]bir nesnenin şeklini değiştirebilirsiniz , ancak muhtemelen değiştirmemelisiniz .
Nick Husher

1

Prototipi biliyorsanız, neden koda eklemiyorsunuz?

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

Böylece, veriler bir kez serileştirildikten sonra,

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

şimdi sadece bir diziyi bağımsız değişken olarak alan bir kurucuya ihtiyacınız var.


0

ArrayOnu gerçekten miras almanın veya "alt sınıflara" almanın bir yolu yoktur.

Yapabilecekleriniz şudur ( UYARI: KODU ÖNCE YAKALAYIN ):

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

Bu işe yarar, ancak yolundan geçen herkes için belirli sorunlara neden olur (bir dizi gibi görünür, ancak onu değiştirmeyi denerseniz işler ters gidecektir).

Neden mantıklı rotaya gidip doğrudan yöntem / özellikler eklemiyorsunuz fooveya bir kurucu kullanıp dizinizi bir özellik olarak kaydetmiyorsunuz?

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"}]

0

anında prototip oluşturmak istiyorsanız, bu yöntemlerden biri

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);

-1
foo.prototype.myFunction = function(){alert("me");}

Hayır, yapamazsınız. Bu statik bir özelliktir.
SLaks

@SLaks prototypestatik mi? Ne anlatmak istediğinden emin değilim.
Šime Vidas

1
prototypebir nesnenin değil, bir işlevin özelliğidir. Object.prototypevar; {}.prototypedeğil.
SLaks

Tarayıcı uyumluluğunu umursamıyorsanız Object.getPrototypeOf(foo), yapıcı prototip nesnesini almak için kullanabilirsiniz . Nesnenin prototipini değiştirmek için bunun özelliklerini değiştirebilirsiniz. Yalnızca son tarayıcılarda çalışır, hangilerinin olduğunu söyleyemezdi.
Nick Husher

@TeslaNick: Object.prototypeDoğrudan gidip değişiklik yapabilirsiniz çünkü büyük olasılıkla Object.getPrototypeOf(foo)geri dönecek olan budur. Pek kullanışlı değil.
Wladimir Palant
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.