Bagaimana cara "benar" membuat objek khusus dalam JavaScript?

471

Saya bertanya-tanya tentang apa cara terbaik untuk membuat objek JavaScript yang memiliki properti dan metode.

Saya telah melihat contoh di mana orang tersebut digunakan var self = thisdan kemudian digunakan self.di semua fungsi untuk memastikan ruang lingkup selalu benar.

Lalu saya telah melihat contoh menggunakan .prototypeuntuk menambahkan properti, sementara yang lain melakukannya inline.

Dapatkah seseorang memberi saya contoh yang tepat dari objek JavaScript dengan beberapa properti dan metode?

Michael Stum
sumber
13
Tidak ada cara "terbaik".
Triptych
Bukan selfkata yang khusus? Jika tidak, seharusnya; karena selfmerupakan variabel yang ditentukan sebelumnya yang merujuk ke jendela saat ini. self === window
Shaz
2
@Shaz: ini bukan kata yang dilindungi undang-undang seperti properti lain windowdi dalam Browser Object Model seperti documentatau frames; Anda tentu dapat menggunakan kembali pengenal sebagai nama variabel. Meskipun, ya, secara gaya saya lebih suka var that= thismenghindari kemungkinan kebingungan. Meskipun window.selfpada akhirnya tidak ada gunanya sehingga jarang ada alasan untuk menyentuhnya.
bobince
7
Ketika JS diperkecil, menugaskan thiske variabel lokal (misalnya self) mengurangi ukuran file.
Patrick Fisher
Tautan baru Classjs: github.com/divio/classjs
Nikola

Jawaban:

889

Ada dua model untuk mengimplementasikan kelas dan instance dalam JavaScript: cara prototyping, dan cara penutupan. Keduanya memiliki kelebihan dan kekurangan, dan ada banyak variasi tambahan. Banyak pemrogram dan perpustakaan memiliki pendekatan dan fungsi utilitas penanganan kelas yang berbeda untuk meliput beberapa bagian bahasa yang lebih buruk.

Hasilnya adalah bahwa di perusahaan campuran Anda akan memiliki mishmash of metaclasses, semua berperilaku sedikit berbeda. Yang lebih buruk, sebagian besar materi tutorial JavaScript mengerikan dan menyajikan semacam kompromi di sela-sela untuk mencakup semua pangkalan, membuat Anda sangat bingung. (Mungkin penulis juga bingung. Model objek JavaScript sangat berbeda dengan kebanyakan bahasa pemrograman, dan di banyak tempat langsung dirancang dengan buruk.)

Mari kita mulai dengan cara prototipe . Ini adalah JavaScript-asli yang paling banyak Anda dapatkan: ada minimum kode overhead dan instanceof akan bekerja dengan instance objek semacam ini.

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

Kita bisa menambahkan metode ke instance yang dibuat new Shapedengan menulisnya ke prototypepencarian fungsi konstruktor ini:

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

Sekarang untuk subkelasnya, sebanyak yang Anda bisa sebut apa yang JavaScript lakukan subklasifikasi. Kami melakukannya dengan sepenuhnya mengganti prototypeproperti sihir aneh itu :

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

sebelum menambahkan metode ke dalamnya:

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

Contoh ini akan berfungsi dan Anda akan melihat kode seperti itu di banyak tutorial. Tapi man, itu new Shape()jelek: kami membuat instance kelas dasar meskipun tidak ada Shape yang sebenarnya yang harus dibuat. Itu terjadi untuk bekerja dalam kasus sederhana ini karena JavaScript sangat ceroboh: ini memungkinkan nol argumen untuk diteruskan, dalam hal ini xdan ymenjadi undefineddan ditugaskan ke prototipe this.xdan this.y. Jika fungsi konstruktor melakukan sesuatu yang lebih rumit, itu akan jatuh datar di wajahnya.

Jadi yang perlu kita lakukan adalah menemukan cara untuk membuat objek prototipe yang berisi metode dan anggota lain yang kita inginkan di tingkat kelas, tanpa memanggil fungsi konstruktor kelas dasar. Untuk melakukan ini, kita harus mulai menulis kode pembantu. Ini adalah pendekatan paling sederhana yang saya tahu:

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

Ini mentransfer anggota kelas dasar dalam prototipe ke fungsi konstruktor baru yang tidak melakukan apa-apa, kemudian menggunakan konstruktor itu. Sekarang kita dapat menulis secara sederhana:

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

bukannya new Shape()kesalahan. Kami sekarang memiliki satu set primitif yang dapat diterima untuk membangun kelas.

Ada beberapa penyempurnaan dan ekstensi yang dapat kita pertimbangkan dalam model ini. Sebagai contoh di sini adalah versi sintaksis-gula:

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

Versi mana pun memiliki kelemahan bahwa fungsi konstruktor tidak dapat diwarisi, seperti dalam banyak bahasa. Jadi, bahkan jika subkelas Anda tidak menambahkan apa pun pada proses konstruksi, ia harus ingat untuk memanggil konstruktor dasar dengan argumen apa pun yang diinginkan dasar. Ini bisa sedikit otomatis menggunakan apply, tetapi Anda masih harus menulis:

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

Jadi ekstensi yang umum adalah untuk membagi hal-hal inisialisasi ke dalam fungsinya sendiri daripada konstruktor itu sendiri. Fungsi ini kemudian dapat mewarisi dari basis dengan baik:

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!

Sekarang kita baru saja mendapatkan fungsi konstruktor boilerplate yang sama untuk setiap kelas. Mungkin kita bisa memindahkannya ke dalam fungsi penolongnya sendiri sehingga kita tidak harus terus mengetiknya, misalnya alih-alih Function.prototype.subclass, memutarnya dan membiarkan Function kelas dasar mengeluarkan subclass:

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

... yang mulai sedikit lebih mirip dengan bahasa lain, walaupun dengan sintaksis yang sedikit klumsier. Anda dapat menaburkan beberapa fitur tambahan jika suka. Mungkin Anda ingin makeSubclassmengambil dan mengingat nama kelas dan memberikan default toStringmenggunakannya. Mungkin Anda ingin membuat konstruktor mendeteksi ketika secara tidak sengaja dipanggil tanpa newoperator (yang sebaliknya akan sering mengakibatkan debugging yang sangat menjengkelkan):

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

Mungkin Anda ingin memasukkan semua anggota baru dan makeSubclassmenambahkannya ke prototipe, agar Anda tidak perlu menulis Class.prototype...terlalu banyak. Banyak sistem kelas melakukan itu, misalnya:

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

Ada banyak fitur potensial yang mungkin Anda anggap diinginkan dalam sistem objek dan tidak ada yang benar-benar setuju pada satu formula tertentu.


Cara penutupan , kalau begitu. Ini menghindari masalah warisan berbasis prototipe JavaScript, dengan tidak menggunakan warisan sama sekali. Sebagai gantinya:

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

Sekarang setiap instance Shapeakan memiliki salinan toStringmetode sendiri (dan metode lain atau anggota kelas lain yang kami tambahkan).

Hal buruk dari setiap instance memiliki salinannya sendiri dari setiap anggota kelas adalah bahwa itu kurang efisien. Jika Anda berurusan dengan banyak contoh subclass, pewarisan prototipikal dapat membantu Anda dengan lebih baik. Juga memanggil metode kelas dasar sedikit mengganggu seperti yang Anda lihat: kita harus ingat apa metode itu sebelum konstruktor subkelas menimpa, atau hilang.

[Juga karena tidak ada warisan di sini, instanceofoperator tidak akan bekerja; Anda harus menyediakan mekanisme Anda sendiri untuk mengendus kelas jika Anda membutuhkannya. Sementara Anda bisa mengutak-atik objek prototipe dengan cara yang sama seperti dengan warisan prototipe, ini agak rumit dan tidak terlalu layak hanya untuk instanceofbekerja.]

Hal yang baik tentang setiap instance yang memiliki metode sendiri adalah bahwa metode tersebut kemudian dapat terikat pada instance spesifik yang memilikinya. Ini berguna karena cara aneh JavaScript thisdalam mengikat pemanggilan metode, yang memiliki kesimpulan bahwa jika Anda melepaskan metode dari pemiliknya:

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

kemudian thisdi dalam metode tidak akan menjadi contoh Circle seperti yang diharapkan (itu sebenarnya akan menjadi windowobjek global , menyebabkan celaka debugging luas). Pada kenyataannya ini biasanya terjadi ketika suatu metode diambil dan ditugaskan ke setTimeout, onclickatau EventListenersecara umum.

Dengan cara prototipe, Anda harus menyertakan penutupan untuk setiap tugas seperti:

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

atau, di masa depan (atau sekarang jika Anda meretas Function.prototype) Anda juga dapat melakukannya dengan function.bind():

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

jika instans Anda dilakukan dengan cara penutupan, pengikatan dilakukan secara gratis oleh penutupan atas variabel instan (biasanya disebut thatatau self, meskipun secara pribadi saya akan menyarankan agar yang terakhir tidak selfmemiliki arti lain yang berbeda dalam JavaScript). Anda tidak mendapatkan argumen 1, 1dalam cuplikan di atas secara gratis, jadi Anda masih perlu penutupan lain atau bind()jika Anda perlu melakukannya.

Ada banyak varian pada metode penutupan juga. Anda mungkin lebih suka menghilangkan thissepenuhnya, membuat yang baru thatdan mengembalikannya daripada menggunakan newoperator:

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

Jalan mana yang “tepat”? Kedua. Mana yang "terbaik"? Itu tergantung situasi Anda. FWIW Saya cenderung membuat prototipe untuk warisan JavaScript nyata ketika saya melakukan banyak hal OO, dan penutupan untuk efek halaman sekali pakai yang sederhana.

Namun kedua cara tersebut cukup kontra-intuitif bagi kebanyakan programmer. Keduanya memiliki banyak variasi yang berpotensi berantakan. Anda akan bertemu keduanya (dan juga banyak skema di antara dan umumnya rusak) jika Anda menggunakan kode / pustaka orang lain. Tidak ada satu jawaban yang diterima secara umum. Selamat datang di dunia objek JavaScript yang luar biasa.

[Ini telah menjadi bagian 94 dari Mengapa JavaScript Bukan Bahasa Pemrograman Favorit Saya.]

bobince
sumber
13
Langkah-langkah bertahap yang sangat bagus dari def "class" ke instantiation objek. Dan sentuhan yang bagus untuk memintas new.
Crescent Fresh
8
Sepertinya JavaScript bukan bahasa favorit Anda karena Anda ingin menggunakannya seolah-olah memiliki kelas.
Jonathan Feinberg
59
Tentu saja saya lakukan, begitu juga semua orang: model kelas-dan-contoh adalah yang lebih alami untuk sebagian besar masalah umum yang dihadapi programmer saat ini. Saya setuju bahwa, berdasarkan teori, pewarisan berbasis prototipe berpotensi menawarkan cara kerja yang lebih fleksibel, tetapi JavaScript sama sekali tidak memenuhi janji itu. Sistem fungsi konstruktor yang kikuk memberi kita yang terburuk dari kedua dunia, membuat warisan kelas-seperti sulit sementara tidak memberikan fleksibilitas atau kesederhanaan prototipe yang bisa ditawarkan. Singkatnya, itu kotoran.
bobince
4
Bob Saya pikir ini adalah jawaban yang luar biasa - saya telah bergulat dengan dua pola ini untuk sementara waktu dan saya pikir Anda telah membuat kode sesuatu yang lebih ringkas daripada Resig dan menjelaskan dengan lebih banyak wawasan daripada Crockford. Tidak ada pujian yang lebih tinggi yang bisa saya pikirkan ....
James Westgate
4
Bagi saya selalu tampak bahwa grafik paradigma warisan klasik ke bahasa prototipikal seperti javascript adalah pasak persegi dan lubang bundar. Apakah ada saat-saat ini benar-benar perlu atau ini hanya cara bagi orang-orang untuk memahami bahasa sebagai cara yang mereka inginkan daripada hanya menggunakan bahasa untuk apa itu?
slf
90

Saya sering menggunakan pola ini - saya menemukan bahwa ini memberi saya fleksibilitas yang cukup besar ketika saya membutuhkannya. Dalam penggunaannya agak mirip dengan kelas gaya Jawa.

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

Ini menggunakan fungsi anonim yang dipanggil pada penciptaan, mengembalikan fungsi konstruktor baru. Karena fungsi anonim dipanggil hanya sekali, Anda dapat membuat variabel statis pribadi di dalamnya (mereka berada di dalam penutupan, dapat dilihat oleh anggota kelas lainnya). Fungsi konstruktor pada dasarnya adalah objek Javascript standar - Anda mendefinisikan atribut pribadi di dalamnya, dan atribut publik dilampirkan ke thisvariabel.

Pada dasarnya, pendekatan ini menggabungkan pendekatan Crockfordian dengan objek Javascript standar untuk membuat kelas yang lebih kuat.

Anda dapat menggunakannya seperti halnya pada objek Javascript lainnya:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method
ShZ
sumber
4
Itu terlihat menarik, karena agak dekat dengan "rumah-rumput" saya yang merupakan C #. Saya juga berpikir saya mulai memahami mengapa privateStaticVariable benar-benar pribadi (seperti itu didefinisikan dalam lingkup fungsi dan terus hidup selama ada referensi untuk itu?)
Michael Stum
Karena tidak menggunakan, thisapakah masih perlu dipakai new?
Jordan Parmer
Sebenarnya, this tidak digunakan dalam constructorfungsi, yang menjadi Foodalam contoh.
ShZ
4
Masalahnya di sini adalah bahwa setiap objek mendapatkan salinan sendiri dari semua fungsi pribadi dan publik.
virtualnobi
2
@virtualnobi: Pola ini tidak mencegah Anda dari menulis metode protytpe: constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d'Ennequin
25

Douglas Crockford membahas topik itu secara luas di The Good Parts . Dia merekomendasikan untuk menghindari operator baru untuk membuat objek baru. Alih-alih ia mengusulkan untuk membuat konstruktor yang disesuaikan. Contohnya:

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

Dalam Javascript, fungsi adalah objek, dan dapat digunakan untuk membuat objek bersama-sama dengan operator baru . Dengan konvensi, fungsi yang dimaksudkan untuk digunakan sebagai konstruktor dimulai dengan huruf kapital. Anda sering melihat hal-hal seperti:

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

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

Jika Anda lupa menggunakan operator baru saat membuat objek baru, apa yang Anda dapatkan adalah panggilan fungsi biasa, dan ini terikat ke objek global, bukan ke objek baru.

Diego Pino
sumber
5
Apakah ini saya atau saya pikir Crockford sama sekali tidak masuk akal dengan bashing terhadap operator baru?
meder omuraliev
3
@meder: Bukan hanya kamu. Setidaknya, saya pikir tidak ada yang salah dengan operator baru. Dan ada sebuah implisit newdi var that = {};pula.
Tim Down
17
Crockford adalah seorang lelaki tua yang ngambek dan saya sangat tidak setuju dengannya, tapi dia setidaknya mempromosikan pandangan kritis pada JavaScript dan layak mendengarkan apa yang dia katakan.
bobince
2
@ bobince: Setuju. Tulisannya pada penutupan membuka mata saya untuk banyak hal sekitar 5 tahun yang lalu, dan dia mendorong pendekatan yang bijaksana.
Tim Down
20
Saya setuju dengan Crockford. Masalah dengan operator baru adalah bahwa JavaScript akan membuat konteks "ini" sangat berbeda daripada ketika memanggil fungsi. Terlepas dari konvensi kasus yang tepat, ada masalah yang muncul pada basis kode yang lebih besar karena pengembang lupa untuk menggunakan yang baru, lupa untuk menggunakan huruf kapital, dll. Agar pragmatis, Anda dapat melakukan semua yang perlu Anda lakukan tanpa kata kunci baru - jadi mengapa menggunakannya dan memperkenalkan lebih banyak poin kegagalan dalam kode? JS adalah bahasa prototipe, bukan berbasis kelas. Jadi mengapa kita ingin bertindak seperti bahasa yang diketik secara statis? Tentu saja tidak.
Joshua Ramirez
13

Untuk melanjutkan jawaban bobince

Di ES6 Anda sekarang dapat benar-benar membuat class

Jadi sekarang Anda bisa melakukannya:

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

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

Jadi perluas ke lingkaran (seperti pada jawaban lain) yang dapat Anda lakukan:

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

Berakhir sedikit lebih bersih di es6 dan sedikit lebih mudah dibaca.


Berikut adalah contoh yang baik dari itu dalam aksi:

Naftali alias Neal
sumber
6

Anda juga dapat melakukannya dengan menggunakan struktur:

function createCounter () {
    var count = 0;

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

Kemudian :

var counter1 = createCounter();
counter1.increaseBy(4);
Eino Gourdin
sumber
6
Saya tidak suka cara itu karena spasi putih itu penting. Keriting setelah kembali harus pada baris yang sama untuk kompatibilitas lintas-browser.
geowa4
5

Cara lain adalah http://jsfiddle.net/nnUY4/ (saya tidak tahu apakah penanganan objek semacam ini dan fungsi pengungkapannya mengikuti pola tertentu)

// 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"
Fluchtpunkt
sumber
4

Ketika seseorang menggunakan trik menutup "ini" selama doa konstruktor, itu untuk menulis fungsi yang dapat digunakan sebagai panggilan balik oleh beberapa objek lain yang tidak ingin memanggil metode pada objek. Ini tidak terkait dengan "membuat ruang lingkup menjadi benar".

Berikut ini adalah objek JavaScript vanilla:

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

Anda mungkin bisa keluar banyak dari membaca apa yang dikatakan Douglas Crockford tentang JavaScript. John Resig juga brilian. Semoga berhasil!

Jonathan Feinberg
sumber
1
Eh, penutupan ada thishubungannya dengan "membuat ruang lingkup yang benar".
Roatin Marth
3
Jonathan benar. Lingkup fungsi js adalah apa pun yang Anda rancang. Self = trik ini adalah salah satu cara untuk mengikatnya ke instance tertentu sehingga tidak berubah ketika dipanggil dalam konteks lain. Tetapi kadang-kadang itulah yang sebenarnya Anda inginkan. Tergantung pada konteksnya.
Marco
Saya pikir Anda semua mengatakan hal yang sama sebenarnya. self=thismeskipun tidak memaksa thisuntuk bertahan, dengan mudah memungkinkan pelingkupan "benar" melalui penutupan.
Crescent Fresh
2
Alasan Anda melakukan itu = ini adalah untuk memberikan fungsi fungsi bersarang ke lingkup ini seperti yang ada di fungsi konstruktor. Ketika fungsi bersarang berada di dalam fungsi konstruktor, lingkup "ini" mereka kembali ke lingkup global.
Joshua Ramirez
4

Closureserbaguna. bobince telah meringkas baik prototipe vs pendekatan penutupan saat membuat objek. Namun Anda dapat meniru beberapa aspek OOPmenggunakan penutupan dengan cara pemrograman fungsional. Ingat fungsi adalah objek dalam JavaScript ; jadi gunakan fungsi sebagai objek dengan cara yang berbeda.

Berikut adalah contoh penutupannya:

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

Beberapa waktu yang lalu saya menemukan artikel Mozilla tentang Penutupan. Inilah yang melompat di mata saya: "Penutupan memungkinkan Anda mengaitkan beberapa data (lingkungan) dengan fungsi yang beroperasi pada data itu. Ini memiliki paralel yang jelas dengan pemrograman berorientasi objek, di mana objek memungkinkan kita untuk mengaitkan beberapa data (properti objek ) dengan satu atau lebih metode ". Itu adalah pertama kalinya saya membaca paralelisme antara penutupan dan OOP klasik tanpa referensi prototipe.

Bagaimana?

Misalkan Anda ingin menghitung PPN beberapa item. PPN kemungkinan akan tetap stabil selama masa aplikasi. Salah satu cara untuk melakukannya dalam OOP (kode semu):

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

Pada dasarnya Anda memasukkan nilai PPN ke konstruktor dan metode penghitungan Anda dapat beroperasi dengannya melalui penutupan . Sekarang alih-alih menggunakan kelas / konstruktor, berikan PPN sebagai argumen ke fungsi. Karena satu-satunya hal yang Anda minati adalah perhitungan itu sendiri, mengembalikan fungsi baru, yang merupakan metode penghitungan:

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

Dalam proyek Anda, identifikasi nilai-nilai tingkat atas yang merupakan kandidat yang baik untuk perhitungan PPN. Sebagai aturan praktis setiap kali Anda menyampaikan argumen yang sama terus-menerus, ada cara untuk memperbaikinya menggunakan penutupan. Tidak perlu membuat objek tradisional.

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

roland
sumber
3

Membuat objek

Cara termudah untuk membuat objek dalam JavaScript adalah dengan menggunakan sintaks berikut:

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

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

Ini berfungsi baik untuk menyimpan data dengan cara yang terstruktur.

Namun, untuk kasus penggunaan yang lebih kompleks, sering kali lebih baik membuat instance fungsi:

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

Ini memungkinkan Anda untuk membuat beberapa objek yang memiliki "cetak biru" yang sama, mirip dengan cara Anda menggunakan kelas misalnya. Jawa.

Ini masih bisa dilakukan dengan lebih efisien, dengan menggunakan prototipe.

Setiap kali contoh fungsi yang berbeda berbagi metode atau properti yang sama, Anda bisa memindahkannya ke prototipe objek itu. Dengan begitu, setiap instance dari fungsi memiliki akses ke metode atau properti itu, tetapi itu tidak perlu diduplikasi untuk setiap instance.

Dalam kasus kami, masuk akal untuk memindahkan metode fke prototipe:

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

Warisan

Cara sederhana namun efektif untuk melakukan pewarisan dalam JavaScript, adalah dengan menggunakan dua-baris berikut:

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

Itu mirip dengan melakukan ini:

B.prototype = new A();

Perbedaan utama antara keduanya adalah bahwa konstruktor Atidak berjalan saat menggunakan Object.create, yang lebih intuitif dan lebih mirip dengan warisan berbasis kelas.

Anda selalu dapat memilih untuk menjalankan konstruktor opsional Asaat membuat instance baru Bdengan menambahkannya ke konstruktor B:

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

Jika Anda ingin meneruskan semua argumen Buntuk A, Anda juga dapat menggunakan Function.prototype.apply():

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

Jika Anda ingin mencampur objek lain ke dalam rantai konstruktor B, Anda dapat menggabungkan Object.createdengan Object.assign:

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

Demo

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


Catatan

Object.createdapat digunakan dengan aman di setiap browser modern, termasuk IE9 +. Object.assigntidak berfungsi di versi IE apa pun atau beberapa peramban seluler. Disarankan untuk mengisi polyfill Object.create dan / atau Object.assignjika Anda ingin menggunakannya dan mendukung browser yang tidak mengimplementasikannya.

Anda dapat menemukan polyfill untuk di Object.create sini dan satu untuk di Object.assign sini .

John Slegers
sumber
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);
adeola olanrewaju
sumber
4
Apa yang menambah banyak jawaban luas yang sudah ditawarkan?
Blm
Saya suka jawaban ini karena singkat dan menunjukkan tiga bagian dari implementasi: 1) Tentukan objek, 2) Instantiate instance dari objek, 3) Gunakan instance - itu menunjukkan semuanya dalam sekejap daripada memiliki parse melalui semua jawaban verbal di atas (yang, tentu saja, semua jawaban yang sangat bagus dengan semua perincian terkait yang diinginkan seseorang) - semacam ringkasan sederhana di sini
G-Man
0

Selain jawaban yang diterima dari 2009. Jika Anda dapat menargetkan browser modern, Anda dapat menggunakan Object.defineProperty .

Metode Object.defineProperty () mendefinisikan properti baru secara langsung pada objek, atau memodifikasi properti yang ada pada objek, dan mengembalikan objek. Sumber: 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();

Untuk melihat daftar kompatibilitas desktop dan seluler, lihat daftar Kompatibilitas Browser Mozilla . Ya, IE9 + mendukungnya serta Safari mobile.

Alex Nolasco
sumber
0

Anda juga dapat mencoba ini

    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();
G Naga Subrahmanyam
sumber
0
Pola Yang Melayani Saya Dengan Baik
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;
};

Pertama, Anda dapat mengubah preferensi Anda menambahkan metode untuk contoh bukan konstruktor prototypeobjek . Saya hampir selalu mendeklarasikan metode di dalam konstruktor karena saya sering menggunakan Constructor Hijacking untuk keperluan mengenai Warisan & Dekorator.

Inilah cara saya memutuskan di mana deklarasi ditulis:

  • Jangan pernah mendeklarasikan metode secara langsung pada objek konteks ( this)
  • Biarkan vardeklarasi didahulukan dari functiondeklarasi
  • Biarkan primitif lebih diutamakan daripada objek ( {}dan [])
  • Biarkan publicdeklarasi didahulukan dari privatedeklarasi
  • Lebih Function.prototype.bindlebih thus, self, vm,etc
  • Hindari mendeklarasikan Kelas dalam Kelas lain, kecuali:
    • Harus jelas bahwa keduanya tidak dapat dipisahkan
    • Kelas dalam mengimplementasikan Pola Perintah
    • Kelas dalam mengimplementasikan Pola Singleton
    • Kelas dalam mengimplementasikan Pola Negara
    • Kelas Dalam menerapkan Pola Desain lain yang menjamin ini
  • Selalu kembali thisdari dalam Lingkup Leksikal Ruang Penutupan.

Inilah sebabnya mengapa ini membantu:

Pembajakan konstruktor
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
Desain Model
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
Pola desain
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

Seperti yang Anda lihat, saya benar-benar tidak perlu thuskarena saya lebih suka Function.prototype.bind( .callatau .apply)thus . Di Singletonkelas kami , kami bahkan tidak menyebutkannya thuskarena INSTANCEmenyampaikan lebih banyak informasi. Karena Model, kami kembali thissehingga kami dapat memanggil Constructor menggunakan .calluntuk mengembalikan contoh yang kami lewati. Secara berlebihan, kami menugaskannya ke variabel updated, meskipun berguna dalam skenario lain.

Di samping itu, saya lebih suka membangun objek-literal menggunakan newkata kunci di atas {kurung}:

Lebih disukai
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
Tidak disukai
var klass = Super.apply({
    override: x
});

Seperti yang Anda lihat, yang terakhir tidak memiliki kemampuan untuk menimpa properti "menimpa" Superclass.

Jika saya menambahkan metode ke objek Class prototype, saya lebih suka objek literal - dengan atau tanpa menggunakan newkata kunci:

Lebih disukai
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() {...}
};
Tidak disukai
Klass.prototype.method = function m() {...};
Cody
sumber
0

Saya ingin menyebutkan bahwa kita dapat menggunakan Judul atau String untuk mendeklarasikan Objek.
Ada berbagai cara memanggil masing-masing jenis. Lihat di bawah:

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

Chetabahana
sumber
-1

Pada dasarnya tidak ada konsep kelas di JS jadi kami menggunakan fungsi sebagai konstruktor kelas yang relevan dengan pola desain yang ada.

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

Sampai sekarang JS tidak memiliki petunjuk bahwa Anda ingin membuat objek jadi inilah kata kunci baru.

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

Ref: JS profesional untuk pengembang web - Nik Z

Airwind711
sumber
Downvote diterima: dengan alasan yang valid akan lebih informatif dan akan memberikan kesempatan untuk meningkatkan.
Airwind711
Ada adalah konsep classdi JS, seperti yang Anda sebutkan di pos Anda menggunakan functionkata kunci. Ini bukan Pola Desain tetapi fitur bahasa yang disengaja. Saya tidak meremehkan Anda dalam hal ini, tetapi sepertinya orang lain melakukannya karena kesederhanaan dan hampir tidak ada hubungannya dengan pertanyaan. Semoga umpan balik ini membantu.
Cody