JavaScript'te “düzgün” bir nesne nasıl oluşturulur?


471

Özellikleri ve yöntemleri olan bir JavaScript nesnesi oluşturmak için en iyi yolu nedir merak ediyorum.

Kişinin kullandığı var self = thisve daha sonra self.kapsamın her zaman doğru olduğundan emin olmak için tüm işlevlerde kullandığı örnekleri gördüm .

Sonra .prototypeözellikleri eklemek için kullanma örnekleri gördüm , diğerleri inline yapıyor.

Birisi bana bazı özelliklere ve yöntemlere sahip bir JavaScript nesnesinin uygun bir örneğini verebilir mi?


13
"En iyi" yol yoktur.
Triptik

selfAyrılmış bir kelime değil mi? Değilse, olmalı; çünkü selfgeçerli pencereye gönderme yapan önceden tanımlanmış bir değişkendir. self === window
Shaz

2
@Shaz: windowTarayıcı Nesne Modelindeki documentveya benzeri diğer özelliklerden daha fazla ayrılmış bir sözcük değil frames; tanımlayıcıyı değişken adı olarak kesinlikle yeniden kullanabilirsiniz. Her ne kadar evet, stilistik olarak var that= thisolası karışıklıklardan kaçınmayı tercih ederim . window.selfSonunda anlamsız olsa da , ona dokunmak için nadiren bir neden var.
bobince

7
JS küçültüldüğünde, thisyerel bir değişkene (örneğin self) atamak dosya boyutlarını azaltır.
Patrick Fisher

Classjs yeni bağlantı: github.com/divio/classjs
Nikola

Yanıtlar:


889

JavaScript'te sınıfları ve örnekleri uygulamak için iki model vardır: prototip oluşturma yolu ve kapatma yolu. Her ikisinin de avantajları ve dezavantajları vardır ve birçok genişletilmiş varyasyon vardır. Birçok programcı ve kütüphane, dilin bazı çirkin kısımlarını ele almak için farklı yaklaşımlara ve sınıf yönetimi yardımcı programlarına sahiptir.

Sonuç, karışık şirkette, biraz farklı davranan bir metasınıf karması olacak. Daha da kötüsü, çoğu JavaScript öğretici materyali korkunçtur ve tüm üsleri kapsayacak şekilde bir çeşit uzlaşma sağlar ve sizi çok karışık bırakır. (Muhtemelen yazarın kafası da karışmıştır. JavaScript'in nesne modeli çoğu programlama dilinden çok farklıdır ve birçok yerde düzensiz tasarlanmıştır.)

Prototip yolu ile başlayalım . Bu alabileceğiniz en JavaScript yereldir: minimum ek yük kodu vardır ve instanceof bu tür nesnelerin örnekleriyle çalışacaktır.

function Shape(x, y) {
    this.x= x;
    this.y= y;
}

Oluşturulan örneğe , bu yapıcı işlevinin aranmasına new Shapeyazarak yöntemler ekleyebiliriz prototype:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

Şimdi alt sınıflamak için, JavaScript'in alt sınıflandırma yaptıklarını çağırabildiğiniz kadar. Bunu, tuhaf büyü prototypeözelliğini tamamen değiştirerek yapıyoruz :

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

ona yöntem eklemeden önce:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

Bu örnek işe yarayacak ve birçok öğreticide bunun gibi bir kod göreceksiniz. Ama adamım, bu new Shape()çirkin: hiçbir gerçek Şekil yaratılmamasına rağmen temel sınıfı somutlaştırıyoruz. JavaScript kadar saçma olduğu için bu basit durumda işin olur: sıfır argümanlar hangi durumda, normal olarak iletilmesine izin veren xve yolmak undefinedve prototip en atanır this.xve this.y. Yapıcı işlevi daha karmaşık bir şey yapıyor olsaydı, yüzüne düz düşecekti.

Yapmamız gereken şey, temel sınıfın yapıcı işlevini çağırmadan, sınıf düzeyinde istediğimiz yöntemleri ve diğer üyeleri içeren bir prototip nesnesi oluşturmak için bir yol bulmaktır. Bunu yapmak için yardımcı kod yazmaya başlamamız gerekecek. Bu bildiğim en basit yaklaşım:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

Bu, temel sınıfın prototipindeki üyelerini, hiçbir şey yapmayan yeni bir yapıcı işlevine aktarır, daha sonra bu yapıcıyı kullanır. Şimdi basitçe yazabiliriz:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

new Shape()yanlışlık yerine . Artık inşa edilmiş sınıflar için kabul edilebilir bir ilkel setimiz var.

Bu model altında ele alabileceğimiz birkaç ayrıntılandırma ve uzantı var. Örneğin, sözdizimsel şeker versiyonu:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

Her iki sürümde de, birçok dilde olduğu gibi yapıcı işlevinin devralınamaması dezavantajı vardır. Bu nedenle, alt sınıfınız inşaat sürecine hiçbir şey eklemese bile, taban yapıcısına, tabanın istediği bağımsız değişkenleri çağırmayı hatırlamalıdır. Bu, kullanarak biraz otomatik hale getirilebilir apply, ancak yine de yazmanız gerekir:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

Bu nedenle yaygın bir uzantı, başlatma öğelerini yapıcıdan ziyade kendi işlevine ayırmaktır. Bu işlev daha sonra tabandan iyi bir şekilde miras alabilir:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

Şimdi her sınıf için aynı yapıcı işlev kazanı var. Belki bunu kendi yardımcı işlevine taşıyabiliriz, örneğin yazmaya devam etmek zorunda kalmamıza gerek kalmaz, onu Function.prototype.subclassçevirmek ve temel sınıfın İşlevinin alt sınıfları tükürmesine izin vermek yerine :

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

... biraz daha dağınık sözdizimiyle de olsa, diğer dillere benzemeye başlıyor. İsterseniz birkaç ekstra özellik serpebilirsiniz. Belki de makeSubclassbir sınıf adını alıp hatırlamak ve toStringbunu kullanarak varsayılan bir ad vermek istersiniz . Belki de yapıcıyı newoperatör olmadan yanlışlıkla çağrıldığında algılamak istersiniz (aksi takdirde genellikle çok can sıkıcı hata ayıklama ile sonuçlanır):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

Belki de tüm yeni üyeleri geçmek ve makeSubclassonları Class.prototype...çok fazla yazmak zorunda kalmamak için prototipe eklemek istiyorsunuz . Birçok sınıf sistemi bunu yapar, örneğin:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

Bir nesne sisteminde istenebileceğini düşündüğünüz birçok potansiyel özellik vardır ve hiç kimse belirli bir formülü gerçekten kabul etmez.


Kapatma yolu , o zaman. Bu, JavaScript'i prototip tabanlı kalıtım sorunlarından kaçınarak kalıtım kullanmaz. Yerine:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

Şimdi her bir örneğinin Shapekendi toStringyöntem kopyası (ve eklediğimiz diğer yöntemler veya diğer sınıf üyeleri) olacaktır.

Her sınıf üyesinin kendi kopyasına sahip her örnek için kötü olan şey, daha az verimli olmasıdır. Çok sayıda alt sınıf örneği ile uğraşıyorsanız, prototipsel miras size daha iyi hizmet edebilir. Ayrıca, temel sınıfın bir yöntemini çağırmak, gördüğünüz gibi biraz can sıkıcıdır: alt sınıf yapıcısının üzerine yazmadan önce yöntemin ne olduğunu hatırlamamız gerekir veya kaybolur.

[Ayrıca burada kalıtım olmadığından, instanceofoperatör çalışmaz; İhtiyacınız olursa sınıf koklama için kendi mekanizmanızı sağlamalısınız. Eğer iken olabilir prototip miras olduğu gibi benzer bir şekilde prototip nesneleri keman, bunun sadece almak için gerçekten değer biraz zor ve değil instanceofçalışma.]

Kendi yöntemine sahip her örnek için iyi olan şey, yöntemin daha sonra sahip olduğu belirli örneğe bağlanabilmesidir. Bu, JavaScript'in thisyöntem çağrılarındaki garip bağlanma yöntemi nedeniyle kullanışlıdır , ki bu yöntem, bir yöntemi sahibinden ayırırsanız:

var ts= mycircle.toString;
alert(ts());

Daha sonra thisbeklendiği gibi yöntem Çember örneği olmayacak içindeki (aslında küresel olacağım windowyaygın hata ayıklama vah neden nesne). Bir yöntem alınır ve bir atandığında Gerçekte bu durumla karşılaşılır setTimeout, onclickya EventListenergenel olarak.

Prototip yöntemiyle, bu tür her atama için bir kapatma eklemeniz gerekir:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

ya da gelecekte (ya da şimdi Function.prototype'i hack ederseniz) aşağıdakileri de yapabilirsiniz function.bind():

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

örnekleriniz kapanış yolunda yapıldıysa, bağlayıcı, örnek değişkeni üzerindeki kapak tarafından ücretsiz olarak yapılır (genellikle çağrılır thatveya selfkişisel olarak self, JavaScript'e başka bir anlamı olduğu için ikincisine karşı öneririm ). 1, 1Yukarıdaki snippet'teki argümanları ücretsiz olarak almazsınız , bu yüzden yine de başka bir kapanışa veya bind()bunu yapmanız gerekiyorsa.

Kapatma yönteminde de birçok varyant var. Operatörü kullanmak yerine thistamamen atlamayı , yeni bir tane oluşturmayı thatve geri göndermeyi tercih edebilirsiniz new:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

Hangi yol “uygun”? Her ikisi de. Hangisi en iyi"? Bu sizin durumunuza bağlıdır. FWIW Ben güçlü OO şeyler yaparken basit JavaScript kalıtım için prototipleme eğilimi ve basit tek kullanımlık sayfa efektleri için kapanış eğilimi.

Ancak her iki yol da çoğu programcı için oldukça sezgiseldir. Her ikisinin de birçok potansiyel dağınık varyasyonu vardır. Başkalarının kodlarını / kitaplıklarını kullanırsanız, her ikisiyle de karşılaşacaksınız (aralarında ve genellikle bozuk olan birçok programda). Genel kabul görmüş bir cevap yok. JavaScript nesnelerinin harika dünyasına hoş geldiniz.

[Bu neden JavaScript'in En Sevdiğim Programlama Dili Değil?


13
"Sınıf" def den nesne somutlaştırmaya çok güzel kademeli adım. Ve baypasta hoş bir dokunuş new.
Crescent Fresh

8
Görünüşe göre JavaScript favori diliniz değil, çünkü sanki dersleri varmış gibi kullanmak istiyorsunuz.
Jonathan Feinberg

59
Tabii ki, herkes de öyle: sınıf ve örnek model, programcıların bugün karşılaştıkları ortak sorunların çoğu için daha doğal olan modeldir. Teoriye dayalı olarak, prototip tabanlı kalıtımın daha esnek bir çalışma yöntemi sunabileceğini kabul ediyorum, ancak JavaScript tamamen bu sözü sunmuyor. Onun tıknaz yapıcı fonksiyon sistemi bize her iki dünyanın en kötüsünü verir, sınıf benzeri kalıtım zorlaştırırken, hiçbir esneklik veya basitlik prototipleri sunamaz. Kısacası, kaka.
bobince

4
Bob bence bu harika bir cevap - bir süredir bu iki modelle boğuşuyorum ve sanırım bir Resig'ten daha kısaca bir şey kodladın ve bir Crockford'dan daha fazla bilgi ile açıkladın. Daha fazla övgü düşünemiyorum ....
James Westgate

4
Klasik kalıtım paradigmalarını javascript gibi prototip dillere göre grafiklendirmenin bir kare peg ve yuvarlak bir delik olduğu her zaman bana benziyordu. Bunun gerçekten gerekli olduğu zamanlar var mı ya da bu, insanların sadece dili ne olduğu için kullanmak yerine dilin istedikleri şekilde yumruklamalarının bir yolu mu?
slf

90

Bu kalıbı oldukça sık kullanıyorum - ihtiyaç duyduğumda bana oldukça büyük bir esneklik sağladığını buldum. Kullanımda Java tarzı sınıflara oldukça benzer.

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

Bu, oluşturma sırasında çağrılan ve yeni bir yapıcı işlevi döndüren anonim bir işlev kullanır. Anonim işlev yalnızca bir kez çağrıldığından, içinde özel statik değişkenler oluşturabilirsiniz (bunlar sınıfın diğer üyeleri tarafından görülebilen, kapatmanın içindedir). Yapıcı işlevi temel olarak standart bir Javascript nesnesidir - içinde özel nitelikler tanımlarsınız ve thisdeğişkene genel nitelikler eklenir .

Temel olarak, bu yaklaşım daha güçlü bir sınıf oluşturmak için Crockfordian yaklaşımını standart Javascript nesneleriyle birleştirir.

Bunu diğer herhangi bir Javascript nesnesini kullandığınız gibi kullanabilirsiniz:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

4
Bu ilginç görünüyor, çünkü oldukça benim "ev çim" C # yakın. Ben de privateStaticVariable'ın neden gerçekten özel olduğunu anlamaya başladığımı düşünüyorum (bir fonksiyon kapsamında tanımlandığı ve referanslar olduğu sürece hayatta kaldığı gibi?)
Michael Stum

Kullanılmadığı thisiçin hala somutlaştırılması gerekiyor newmu?
Jordan Parmer

Aslında this gelmez de alışması constructorolur işlevi, Fooörnekteki.
ShZ

4
Burada sorun, her nesnenin tüm özel ve kamusal işlevlerin kendi kopyasını almasıdır.
virtualnobi

2
@virtualnobi: Bu model protytpe yöntemleri yazma engellemez değildir: constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d'Ennequin

25

Douglas Crockford bu konuyu İyi Bölümlerde kapsamlı bir şekilde tartışıyor . Yeni operatörün yeni nesneler yaratmasını önler . Bunun yerine özelleştirilmiş kurucular oluşturmayı öneriyor. Örneğin:

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

Javascript'te bir işlev bir nesnedir ve yeni işleçle birlikte nesneleri oluşturmak için kullanılabilir . Kural olarak, kurucu olarak kullanılması amaçlanan fonksiyonlar büyük harfle başlar. Sık sık şöyle şeyler görürsünüz:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

Yeni bir nesneyi başlatırken yeni işleci kullanmayı unutmanız durumunda , elde ettiğiniz şey sıradan bir işlev çağrısıdır ve bu , yeni nesneye değil, global nesneye bağlıdır.


5
Benim mi yoksa Crockford'un yeni operatöre dayandığıyla kesinlikle bir anlamı yok mu?
meder omuraliev

3
@meder: Sadece sen değil. En azından, yeni operatörde yanlış bir şey olmadığını düşünüyorum. Ve bir örtülü var newiçinde var that = {};oldu Neyse.
Tim Down

17
Crockford huysuz yaşlı bir adam ve onunla çok fazla katılmıyorum, ama en azından JavaScript'e eleştirel bir bakış atmayı destekliyor ve söylediklerini dinlemeye değer.
bobince

2
@bobince: Kabul etti. Kapanışlarla ilgili yazımı, yaklaşık 5 yıl önce gözlerimi çok şeylere açtı ve düşünceli bir yaklaşımı teşvik ediyor.
Tim Down

20
Crockford'a katılıyorum. Yeni işleçle ilgili sorun, JavaScript'in "bu" bağlamını, aksi takdirde bir işlevi çağırmaktan çok farklı hale getirmesidir. Uygun vaka kuralına rağmen, geliştiricilerin yeni kullanmayı, büyük harf kullanmayı vb. kodda daha fazla hata noktası tanıtmak? JS, sınıf tabanlı değil, prototip bir dildir. Öyleyse neden statik olarak yazılmış bir dil gibi davranmasını istiyoruz? Kesinlikle bilmiyorum.
Joshua Ramirez

13

Bobince'nin cevabından devam etmek

Es6'da artık gerçekten bir class

Şimdi şunları yapabilirsiniz:

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

Yani (diğer cevapta olduğu gibi) bir daireye uzanabilirsiniz:

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

Es6'da biraz daha temiz ve okunması biraz daha kolay.


İşte bunun iyi bir örneği:


6

Bunu yapıları kullanarak da bu şekilde yapabilirsiniz:

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

Sonra :

var counter1 = createCounter();
counter1.increaseBy(4);

6
Bu şekilde sevmiyorum çünkü boşluk önemli. Çapraz tarayıcı uyumluluğu için dönüşten sonraki kıvırcık aynı satırda olmalıdır.
geowa4

5

Başka bir yol http://jsfiddle.net/nnUY4/ (nesne yaratma ve ifşa etme fonksiyonlarının bu tür herhangi bir paterni takip edip etmediğini bilmiyorum)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"

4

Bir kurucu çağırma sırasında "bu" üzerine kapanma hilesi kullanıldığında, bir nesne üzerinde bir yöntem çağırmak istemeyen başka bir nesne tarafından geri çağrı olarak kullanılabilen bir işlev yazmak içindir. "Kapsamı doğru yapmak" ile ilgili değildir.

İşte bir vanilya JavaScript nesnesi:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

Douglas Crockford'un JavaScript hakkında söylediklerini okumaktan çok şey alabilirsiniz . John Resig de harika. İyi şanslar!


1
Ah, etrafı kapatmak this"kapsamı doğru yapmak" ile ilgili her şeye sahiptir.
Roatin Marth

3
Jonathan haklı. Bir js fonksiyonunun kapsamı, onu ne şekilde tasarlarsanız tasarlayın. Self = bu hile, onu belirli bir örneğe bağlamanın bir yoludur, bu nedenle başka bir bağlamda çağrıldığında değişmez. Ama bazen aslında bunu istersiniz. Bağlama bağlıdır.
Marco

Sanırım hepiniz aynı şeyi söylüyorsunuz aslında. self=thisHer ne thiskadar kalıcı olmaya zorlamasa da, bir kapatma yoluyla kolayca "doğru" kapsam belirlemeye izin verir.
Crescent Fresh

2
Bunu yapmanızın nedeni = bu, iç içe işlevlere yapıcı işlevinde olduğu gibi bunun kapsamına erişim vermektir. Yuvalanmış işlevler yapıcı işlevlerinin içindeyken, "bu" kapsamı genel kapsama geri döner.
Joshua Ramirez

4

Closureçok yönlüdür. bobince , nesne oluştururken prototip ve kapatma yaklaşımlarını iyi özetledi . Ancak, OOPkapatmayı işlevsel bir programlama yöntemiyle kullanmanın bazı yönlerini taklit edebilirsiniz . Unutmayın işlevler JavaScript'teki nesnelerdir ; bu nedenle işlevi nesne olarak farklı bir şekilde kullanın.

İşte bir kapanış örneği:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

Bir süre önce Mozilla'nın Kapanış makalesine rastladım. Gözlerime atladığım şey şu: "Bir kapatma, bazı verileri (çevre) bu veriler üzerinde çalışan bir işlevle ilişkilendirmenizi sağlar. Bu, nesnelerin bazı verileri (nesnenin özellikleri ) bir veya daha fazla yöntemle ". İlk kez kapatma ile klasik OOP arasında bir prototip referansı olmayan bir paralellik okudum.

Nasıl?

Bazı öğelerin KDV'sini hesaplamak istediğinizi varsayalım. KDV, bir uygulamanın ömrü boyunca sabit kalacaktır. OOP (sözde kod) içinde yapmanın bir yolu:

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

Temel olarak kurucunuza bir KDV değeri geçirirsiniz ve hesaplama yönteminiz, kapatma yoluyla üzerinde çalışabilir . Şimdi bir sınıf / kurucu kullanmak yerine KDV'nizi bağımsız değişken olarak bir işleve geçirin. İlgilendiğiniz tek şey hesaplamanın kendisi olduğundan, hesaplama yöntemi olan yeni bir işlev döndürür:

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

Projenizde, hesaplama için KDV'nin ne olduğuna iyi aday olan üst düzey değerleri belirleyin. Aynı argümanları her açtığınızda açık bir kural olarak, kapatmayı kullanarak onu geliştirmenin bir yolu vardır. Geleneksel nesneler yaratmaya gerek yok.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures


3

Nesne oluşturma

JavaScript'te nesne oluşturmanın en kolay yolu aşağıdaki sözdizimini kullanmaktır:

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

Bu, verileri yapılandırılmış bir şekilde depolamak için harika çalışır.

Bununla birlikte, daha karmaşık kullanım durumları için, işlev örnekleri oluşturmak genellikle daha iyidir:

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Bu, sınıfları nasıl kullandığınıza benzer şekilde, aynı "planı" paylaşan birden çok nesne oluşturmanıza olanak tanır. Java.

Bununla birlikte, bu bir prototip kullanılarak daha verimli bir şekilde yapılabilir.

Bir işlevin farklı örnekleri aynı yöntemleri veya özellikleri paylaştığında, bunları o nesnenin prototipine taşıyabilirsiniz. Bu şekilde, bir işlevin her örneğinin o yönteme veya özelliğe erişimi vardır, ancak her örnek için çoğaltılması gerekmez.

Bizim durumumuzda, yöntemi fprototipe taşımak mantıklıdır :

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

miras

JavaScript'te kalıtım yapmanın basit ama etkili bir yolu, aşağıdaki iki astarı kullanmaktır:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Bunu yapmaya benzer:

B.prototype = new A();

Her ikisi arasındaki temel fark, kurucunun Akullanıldığında çalıştırılmamasıdır Object.create, bu da daha sezgisel ve sınıf tabanlı mirasa daha benzerdir.

AYeni bir örneği oluştururken Bbunu yapıcısına ekleyerek eklentiyi her zaman isteğe bağlı olarak çalıştırmayı seçebilirsiniz B:

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

Eğer tüm argümanları geçmek istiyorsanız Biçin A, ayrıca kullanabilirsiniz Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

Eğer yapıcı zincirine başka bir nesneyi mixin istiyorsanız B, sen birleştirebilirsiniz Object.createile Object.assign:

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

gösteri

function A(name) {
  this.name = name;
}

A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;

function B() {
  A.apply(this, arguments);
  this.street = "Downing Street 10";
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function mixin() {

}

mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;

mixin.prototype.getProperties = function() {
  return {
    name: this.name,
    address: this.street,
    year: this.year
  };
};

function C() {
  B.apply(this, arguments);
  this.year = "2018"
}

C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;

var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());


Not

Object.createIE9 + dahil her modern tarayıcıda güvenle kullanılabilir. Object.assignIE'nin bazı sürümlerinde veya bazı mobil tarayıcılarda çalışmaz. Bu tavsiye edilir Polyfill Object.create ve / veyaObject.assign bunları kullanmak ve uygulamayan tarayıcıları desteklemek istiyorsanız .

Sen bir polyfill bulabilirsiniz Object.create burada ve için bir tane Object.assign burada .


0
var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);

4
Bu, halihazırda sunulan sayısız kapsamlı cevaba ne ekliyor?
blm

Bu cevabı kısa ve uygulamanın üç bölümünü gösterdiği için beğendim: 1) Nesneyi tanımlayın, 2) Nesnenin bir örneğini başlatın, 3) Örneği kullanın - ayrıştırmak yerine hepsini bir bakışta gösterir yukarıdaki tüm ayrıntılı cevaplar aracılığıyla (ki, elbette, bir kişinin isteyebileceği tüm ilgili ayrıntılarla son derece iyi cevaplar) - burada basit bir özet
G-Man

0

Modern tarayıcıları hedefleyebiliyorsanız, Object.defineProperty'den faydalanabilirsiniz .

Object.defineProperty () yöntemi, doğrudan bir nesne üzerinde yeni bir özellik tanımlar veya bir nesne üzerinde varolan bir özelliği değiştirir ve nesneyi döndürür. Kaynak: Mozilla

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

Masaüstü ve mobil uyumluluk listesini görmek için Mozilla'nın Tarayıcı Uyumluluk listesine bakın . Evet, IE9 + bunu Safari cep telefonunun yanı sıra destekliyor.


0

Bunu da deneyebilirsiniz

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();

0
Bana İyi Hizmet Eden Bir Desen
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

İlk olarak, kurucu prototypenesnesi yerine örneğe yöntem ekleme tercihinizi değiştirebilirsiniz . Neredeyse her zaman yapıcı içinde yöntemler beyan ederim çünkü Yapıcı Hijacking'i Miras ve Dekoratörler ile ilgili amaçlar için çok sık kullanıyorum .

Hangi bildirimlerin nerede yazılacağına nasıl karar vereceğim:

  • Asla doğrudan bağlam nesnesi ( this) üzerinde bir yöntem bildirme
  • Let varbeyanlar önceliklidir functionbildirimleri
  • İlkellerin nesneler ( {}ve []) üzerinde önceliğe sahip olmasına izin verin
  • Let publicbeyanlar önceliklidir privatebildirimleri
  • Tercih Function.prototype.bindüzerinde thus, self, vm,etc
  • Başka bir Sınıf içindeki Sınıfı beyan etmekten kaçının, aksi takdirde:
    • İkisinin ayrılmaz olduğu açık olmalı
    • İç sınıf Komut Deseni uygular
    • İç sınıf Singleton Paternini uygular
    • İç sınıf Devlet Paternini uygular
    • İç Sınıf bunu garanti eden başka bir Tasarım Deseni uygular
  • Her zaman Kapatma Alanının Lexical Scopethis içinden dönün .

İşte bu nedenler:

Oluşturucu Ele Geçirme
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
Model Tasarımı
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
Tasarım desenleri
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

Gördüğünüz gibi, tercihthus ettiğim (ya da ) tercihFunction.prototype.bind.call.applythus ettiğim için gerçekten ihtiyacım yok . Bizim de Singletonsınıfta, hatta ne gelirse yok thusçünkü INSTANCEaktarıyor fazla bilgi. Çünkü Model, içine verdiğimiz örneği döndürmek için thisYapıcı'yı çağırabilmemiz .calliçin geri dönüyoruz. Gereksiz olarak, updateddiğer senaryolarda yararlı olsa da, değişkene atadık.

Yanında, new{brackets} anahtar sözcüğünü kullanarak nesne değişmezleri oluşturmayı tercih ediyorum :

Tercihli
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
Tercih Edilmez
var klass = Super.apply({
    override: x
});

Gördüğünüz gibi, ikincisinin Üst Sınıfının "geçersiz kılma" özelliğini geçersiz kılma yeteneği yoktur.

Sınıfın prototypenesnesine yöntemler eklerseniz , newanahtar kelimeyi kullanarak veya kullanmadan bir nesne hazır bilgisi tercih ederim :

Tercihli
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
Tercih Edilmez
Klass.prototype.method = function m() {...};

0

Bir Nesne bildirmek için bir Başlık veya Dize kullanabileceğimizi belirtmek isterim .
Her türünü çağırmanın farklı yolları vardır. Aşağıya bakınız:

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));


-1

Temelde JS'de sınıf kavramı yoktur, bu nedenle mevcut tasarım desenleriyle ilgili bir sınıf yapıcısı olarak işlevi kullanıyoruz.

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

Şimdiye kadar JS'nin bir nesne oluşturmak istediğinize dair bir fikri yok, işte yeni anahtar kelime geliyor.

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

Referans: Web geliştiricileri için profesyonel JS - Nik Z


Downvote kabul edildi: geçerli bir nedenden dolayı daha bilgilendirici olurdu ve iyileştirme fırsatına sahip olacaktı.
Airwind711

Orada olan bir kavramı classkullanarak sizin pozisyonda belirtilen gibi JS, functionanahtar kelime. Bu var olmayan bir tasarım Desen ama dilin kasıtlı özelliği. Seni bu konuda küçümsemedim, ama soruya olan açıklık ve neredeyse ilgisizlik nedeniyle başkasının yaptığı gibi görünüyor. Umarım bu geribildirim yardımcı olur.
Cody
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.