JavaScript'te yeni operatörden kaçınmak - daha iyi bir yol


16

Uyarı: Bu uzun bir gönderi.

Basit tutalım. JavaScript'te her yapıcıyı çağırdığımda yeni operatörün önekini kullanmaktan kaçınmak istiyorum. Çünkü unutmaya eğilimliyim ve kodum kötü bir şekilde vidalanıyor.

Bunun basit yolu şudur ...

function Make(x) {
  if ( !(this instanceof arguments.callee) )
  return new arguments.callee(x);

  // do your stuff...
}

Ancak, değişken no'yu kabul etmek için buna ihtiyacım var. gibi argümanların ...

m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');

İlk acil çözüm böyle 'uygula' yöntemi gibi görünüyor ...

function Make() {
  if ( !(this instanceof arguments.callee) )
    return new arguments.callee.apply(null, arguments);

  // do your stuff
}

Ancak bu YANLIŞ - yeni nesne yapıcıya applydeğil , yönteme geçirilir arguments.callee.

Şimdi üç çözüm buldum. Basit sorum şu: hangisi daha iyi görünüyor. Veya daha iyi bir yönteminiz varsa, söyleyin.

İlk olarakeval() , yapıcıyı çağıran JavaScript kodunu dinamik olarak oluşturmak için kullanın .

function Make(/* ... */) {
  if ( !(this instanceof arguments.callee) ) {
    // collect all the arguments
    var arr = [];
    for ( var i = 0; arguments[i]; i++ )
      arr.push( 'arguments[' + i + ']' );

    // create code
    var code = 'new arguments.callee(' + arr.join(',') + ');';

    // call it
    return eval( code );
  }

  // do your stuff with variable arguments...
}

İkincisi - Her nesnenin, __proto__prototip nesnesine bir 'gizli' bağlantı olan özelliği vardır. Neyse ki bu özellik yazılabilir.

function Make(/* ... */) {
  var obj = {};

  // do your stuff on 'obj' just like you'd do on 'this'
  // use the variable arguments here

  // now do the __proto__ magic
  // by 'mutating' obj to make it a different object

  obj.__proto__ = arguments.callee.prototype;

  // must return obj
  return obj;
}

Üçüncü - Bu ikinci çözüme benzer bir şeydir.

function Make(/* ... */) {
  // we'll set '_construct' outside
  var obj = new arguments.callee._construct();

  // now do your stuff on 'obj' just like you'd do on 'this'
  // use the variable arguments here

  // you have to return obj
  return obj;
}

// now first set the _construct property to an empty function
Make._construct = function() {};

// and then mutate the prototype of _construct
Make._construct.prototype = Make.prototype;

  • eval çözüm beceriksiz görünüyor ve "kötü eval" tüm sorunları ile geliyor.

  • __proto__ çözüm standart değildir ve "mIsERY'nin Büyük Tarayıcısı" onurlandırmaz.

  • Üçüncü çözüm aşırı karmaşık görünüyor.

Ancak yukarıdaki üç çözümün hepsiyle, bunun gibi bir şey yapabiliriz, aksi takdirde yapamayız ...

m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');

m1 instanceof Make; // true
m2 instanceof Make; // true
m3 instanceof Make; // true

Make.prototype.fire = function() {
  // ...
};

m1.fire();
m2.fire();
m3.fire();

Dolayısıyla yukarıdaki çözümler bize değişken no'yu kabul eden "gerçek" kurucuları vermektedir. ve gerektirmez new. Bu konuda ne düşünüyorsun.

-- GÜNCELLEME --

Bazıları "sadece bir hata at" dedi. Benim cevabım: 10'dan fazla kurucu ile ağır bir uygulama yapıyoruz ve her kurucu konsolda hata mesajları atmadan bu hatayı "akıllıca" ele alsaydı çok daha zahmetli olacağını düşünüyorum.


2
ya da sadece kötü olduğunda bir hata atın ve yığın izini inceleyin ve kodu düzeltebilirsiniz
cırcır ucube

2
Bence bu soru Stack Overflow veya Code Review'da daha iyi sorulur . Kavramsal bir sorudan ziyade oldukça kod merkezli görünüyor.
Adam Lear

1
@greengit yerine bir hata atmak jslint kullanın. Make()Olmadan yaparsanız sizi uyaracaktır , newçünkü Make büyük harfle yazılmıştır ve bu nedenle bir kurucu olduğunu varsayar
Raynos

1
Öyleyse bekleyin - bunu başarmanın daha iyi bir yolunu mu arıyorsunuz, yoksa sadece size kod verecek birisini mi arıyorsunuz, böylece değişken argüman nesnesi oluşturmadan new? Çünkü ikinciyse, muhtemelen yanlış sitede soruyorsunuzdur. Eski ise, yeni ve hataların bu kadar hızlı algılanmasıyla ilgili önerileri reddetmek istemeyebilirsiniz ... Uygulamanız gerçekten "ağır" ise, istediğiniz son şey yavaşlatmak için aşırı işlenmiş bir inşaat mekanizmasıdır. new, aldığı tüm flack için oldukça hızlı.
Shog9

5
İronik bir şekilde, programcı hatalarını 'akıllıca' ele almaya çalışmak, JavaScript'in 'kötü kısımlarının' çoğundan sorumludur.
Daniel Pratt

Yanıtlar:


19

Öncelikle arguments.calleeES5'te kesin olarak kullanılmıyor, bu yüzden kullanmıyoruz. Gerçek çözüm oldukça basit.

Hiç kullanmıyorsun new.

var Make = function () {
  if (Object.getPrototypeOf(this) !== Make.prototype) {
    var o = Object.create(Make.prototype);
    o.constructor.apply(o, arguments);
    return o;
  }
  ...
}

Bu eşek için doğru bir acı değil mi?

Deneyin enhance

var Make = enhance(function () {
  ...
});

var enhance = function (constr) {
  return function () {
    if (Object.getPrototypeOf(this) !== constr.prototype) {
      var o = Object.create(constr.prototype);
      constr.apply(o, arguments);
      return o;
    }
    return constr.apply(this, arguments);
  }
}

Şimdi elbette bu ES5 gerektirir, ancak herkes ES5-shim kullanıyor değil mi?

Ayrıca ilginizi çekebilir alternatif js OO desenleri

Bir yana, ikinci seçeneği

var Make = function () {
  var that = Object.create(Make.prototype);
  // use that 

  return that;
}

Kendi ES5 şiminizi istiyorsanız, Object.createo zaman gerçekten kolay

Object.create = function (proto) {
  var dummy = function () {};
  dummy.prototype = proto;
  return new dummy;
};

Evet doğru - ama buradaki tüm sihir yüzünden Object.create. ES5 öncesi ne olacak? ES5-Shim, Object.createDUBIOUS olarak listelenir.
treecoder

@greengit Tekrar okursanız, ES5 shim Object.create için ikinci argümanın DUBIOUS olduğunu belirtir. ilki iyidir.
Raynos

1
Evet okudum. Ve sanırım (EĞER) __proto__orada bir şey kullanıyorlarsa , o zaman hala aynı noktadayız. Çünkü ES5 öncesi prototipi değiştirmenin daha kolay bir yolu YOKTUR. Her neyse, çözümünüz en zarif ve ileriye dönük görünüyor. Bunun için +1. (oylama
sınırımma

1
Teşekkürler @psr. Ve @Raynos şimşek Object.createbenim üçüncü çözümüm ama tabii ki benimkinden daha az karmaşık ve daha iyi görünüyor.
treecoder

37

Basit tutalım. JavaScript'te her yapıcıyı çağırdığımda yeni operatörün önekini kullanmak zorunda kalmamak istiyorum. Çünkü unutmaya eğilimliyim ve kodum kötü bir şekilde vidalanıyor.

Açık cevap newanahtar kelimeyi unutma olacaktır .

Dilin yapısını ve anlamını değiştiriyorsunuz.

Bence ve kodunuzun gelecekteki koruyucular uğruna korkunç bir fikir.


9
+1 Bir dili kötü kodlama alışkanlıklarına çevirmek tuhaf görünüyor. Şüphesiz bazı sözdizimi vurgulama / check-in politikaları hataya eğilimli kalıplardan / olası yazım hatalarından kaçınmayı zorlayabilir.
Stoive

Eğer tüm yapıcıların kullandığınız newya da kullanmamanızın umurunda olmadığı bir kütüphane bulduğumda , bunu daha sürdürülebilir bulurdum .
Jack,

@Jack, ama hataları bulmak için çok daha zor ve daha süptil tanıtacaktır. Sadece ;son ifadeleri eklerseniz javascript "umursamıyorum" kaynaklanan tüm hatalara bakın . (Otomatik Noktalı virgül
ekleme

@CaffGeek "Noktalı virgüllerle ilgilenmeyen Javascript" doğru değildir - noktalı virgül kullanmanın ve kullanmamanın çok farklı olduğu durumlar vardır ve olmadıkları durumlar da vardır. İşte sorun bu. Kabul edilen cevapta sunulan çözüm tam tersidir - bununla birlikte, tüm durumlarda kullanılan newveya olmayan anlamsal olarak aynıdır . Orada olan bu değişmez bozuldu hiçbir ince davaları. Bu yüzden iyi ve neden kullanmak istersiniz.
Jack

14

En kolay çözüm, newunuttuğunuzun anlaşılması için sadece hatırlamak ve bir hata atmaktır.

if (Object.getPrototypeOf(this) !== Make.prototype) {
    throw new Error('Remember to call "new"!');
}

Ne yaparsan yap, kullanma eval. Standart olmayan özellikler __proto__ve işlevlerini değiştirebileceği için standart olmayan özellikleri kullanmaktan çekiniyorum .


Meydan okurcasına önlemek .__proto__şeytan
Raynos

3

Aslında bu konuda bir yazı yazdım. http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html

function Ctor() {
    if (!(this instanceof Ctor) {
        return new Ctor(); 
    }
    // regular construction code
}

Ve hatta genelleştirebilirsiniz, böylece bunu her kurucuya eklemek zorunda kalmazsınız. Yazımı ziyaret ederek görebilirsiniz

Yasal Uyarı Ben bu benim kod kullanmıyorum, ben sadece didaktik değeri için gönderdi. Bir unutmanın newkolay bir hata olduğunu gördüm . Diğerleri gibi, aslında çoğu kod için buna ihtiyacımız olduğunu düşünmüyorum. JS mirası oluşturmak için bir kitaplık yazmadığınız sürece, bu durumda tek bir yerden kullanabilirsiniz ve zaten düz mirastan farklı bir yaklaşım kullanıyorsunuzdur.


Potansiyel hata gizli: Ben varsa var x = new Ctor();ve daha sonra x as var thisve yapmak var y = Ctor();beklendiği gibi bu davranmaya olmaz.
luiscubal

@luiscubal "Daha sonra x gibi this" dediğinizden emin değilseniz , olası sorunu göstermek için bir jsfiddle gönderebilirsiniz.
Juan Mendes

Kodunuz başlangıçta düşündüğümden biraz daha sağlam, ancak (biraz kıvrık ama yine de geçerli) bir örnek bulmayı
başardım

@luiscubal Ben senin fikrini görüyorum ama gerçekten kıvrımlı bir konu. Arama yapmanın uygun olduğunu varsayıyorsunuz Ctor.call(ctorInstance, 'value'). Yaptıklarınız için geçerli bir senaryo görmüyorum. Bir nesneyi oluşturmak için, birini kullanabilirsiniz var x = new Ctor(val)veya var y=Ctor(val). Geçerli bir senaryo olsaydı bile, benim iddiam sen kullanmadan kurucuya sahip olmasıdır new Ctor, kullanmakta çalışır kurucuya sahip olabilir değil Ctor.callSee jsfiddle.net/JHNcR/2
Juan Mendes

0

Buna ne dersin?

/* thing maker! it makes things! */
function thing(){
    if (!(this instanceof thing)){
        /* call 'new' for the lazy dev who didn't */
        return new thing(arguments, "lazy");
    };

    /* figure out how to use the arguments object, based on the above 'lazy' flag */
    var args = (arguments.length > 0 && arguments[arguments.length - 1] === "lazy") ? arguments[0] : arguments;

    /* set properties as needed */
    this.prop1 = (args.length > 0) ? args[0] : "nope";
    this.prop2 = (args.length > 1) ? args[1] : "nope";
};

/* create 3 new things (mixed 'new' and 'lazy') */
var myThing1 = thing("woo", "hoo");
var myThing2 = new thing("foo", "bar");
var myThing3 = thing();

/* test your new things */
console.log(myThing1.prop1); /* outputs 'woo' */
console.log(myThing1.prop2); /* outputs 'hoo' */

console.log(myThing2.prop1); /* outputs 'foo' */
console.log(myThing2.prop2); /* outputs 'bar' */

console.log(myThing3.prop1); /* outputs 'nope' */
console.log(myThing3.prop2); /* outputs 'nope' */

EDIT: Eklemeyi unuttum:

"Uygulamanız gerçekten 'ağır' ise, istediğiniz son şey, uygulamayı yavaşlatmak için aşırı işlenmiş bir inşaat mekanizmasıdır"

Kesinlikle katılıyorum - 'yeni' anahtar kelime olmadan yukarıdaki 'şey' oluştururken, ondan daha yavaş / daha ağır. Hatalar arkadaşın, çünkü sana neyin yanlış olduğunu söylüyorlar. Dahası, ne diğer geliştiricilerin anlatmak onlar konum yanlış yapıyor.


Eksik yapıcı argümanları için değerlerin icat edilmesi hataya açıktır. Eksikse, özellikleri tanımsız bırakın. Eğer gerekliyse, eksiklerse bir hata atın. Prop1'in bool olması gerekiyorsa ne olur? "Hayır" ı doğruluk açısından test etmek bir hata kaynağı olacaktır.
JBRWilkinson
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.