วิธีที่ง่ายที่สุด / Cleanest ในการใช้ซิงเกิลตันใน JavaScript?


300

วิธีที่ง่ายที่สุด / สะอาดที่สุดในการใช้รูปแบบซิงเกิลตันใน JavaScript คืออะไร?


15
Downvote สำหรับคำตอบที่ยอมรับไม่ใช่แบบซิงเกิลเลย มันเป็นเพียงตัวแปรทั่วโลก
mlibby

5
นี่เป็นข้อมูลจำนวนมาก แต่เป็นการบอกถึงความแตกต่างระหว่างรูปแบบการออกแบบ JS ที่แตกต่างกันจริงๆ มันช่วยฉันได้มาก: addyosmani.com/resources/essentialjsdesignpatterns/book
Justin

คำตอบ:


315

ฉันคิดว่าวิธีที่ง่ายที่สุดคือการประกาศวัตถุตามตัวอักษรอย่างง่าย:

var myInstance = {
  method1: function () {
    // ...
  },
  method2: function () {
    // ...
  }
};

หากคุณต้องการสมาชิกส่วนตัวในอินสแตนซ์เดี่ยวของคุณคุณสามารถทำสิ่งนี้:

var myInstance = (function() {
  var privateVar = '';

  function privateMethod () {
    // ...
  }

  return { // public interface
    publicMethod1: function () {
      // all private members are accessible here
    },
    publicMethod2: function () {
    }
  };
})();

นี้ได้รับการเรียกรูปแบบโมดูลนั้นโดยทั่วไปจะช่วยให้คุณแค็ปซูลส่วนตัวของสมาชิกบนวัตถุโดยการใช้ประโยชน์การใช้งานของการปิด

UPDATE:ฉันต้องการเพิ่มว่าถ้าคุณต้องการป้องกันการดัดแปลงวัตถุเดี่ยวคุณสามารถตรึงโดยใช้Object.freezeวิธีES5

ที่จะทำให้วัตถุไม่เปลี่ยนรูปป้องกันการเปลี่ยนแปลงใด ๆ กับโครงสร้างและค่าของมัน

นอกจากนี้ฉันอยากจะพูดถึงว่าถ้าคุณใช้ ES6 คุณสามารถเป็นตัวแทนของซิงเกิลที่ใช้ES Modulesได้อย่างง่ายดายและคุณยังสามารถเก็บสถานะส่วนตัวด้วยการประกาศตัวแปรที่ขอบเขตโมดูล :

// my-singleton.js
const somePrivateState = []

function privateFn () {
  // ...
}

export default {
  method1() {
    // ...
  },
  method2() {
    // ...
  }
}

จากนั้นคุณสามารถนำเข้าวัตถุ singleton เพื่อใช้งานได้:

import myInstance from './my-singleton.js'
// ...

46
+1 ไม่ใช่เรื่องแปลกที่จะมองหา "รูปแบบซิงเกิล" ในภาษาที่มีตัวแปรทั่วโลกใช่หรือไม่?
Victor

4
การใช้รูปแบบโมดูลสมาชิกสาธารณะจะเข้าถึงสมาชิกสาธารณะอื่นได้อย่างไร คือจะpublicMethod1โทรได้publicMethod2อย่างไร
typeof

4
@Tom ใช่รูปแบบที่เกิดในภาษา OOP ตามระดับ - ฉันจำการใช้งานจำนวนมากที่เกี่ยวข้องกับgetInstanceวิธีการคงที่และตัวสร้างส่วนตัว - แต่ IMO นี่เป็นวิธีที่ "ง่าย" ที่สุดในการสร้างวัตถุเดี่ยว ใน Javascript และท้ายที่สุดมันก็มีจุดประสงค์เดียวกันคือวัตถุเดียวที่คุณไม่สามารถเริ่มต้นใหม่ได้ (ไม่มีตัวสร้างมันเป็นเพียงวัตถุ) - เกี่ยวกับรหัสที่คุณเชื่อมโยงก็มีปัญหาบางอย่างสลับaและประกาศตัวแปรและการทดสอบb a === windowไชโย
CMS

15
@Victor - นี่ไม่ใช่เรื่องแปลกที่จะมองหา "รูปแบบซิงเกิล" ในภาษาดังกล่าว ภาษาเชิงวัตถุจำนวนมากใช้ตัวแปรทั่วโลกและยังใช้งานซิงเกิลตันอยู่ ซิงเกิลตันไม่เพียงรับประกันได้ว่าจะมีเพียงหนึ่งวัตถุของคลาสที่กำหนด Singleton มีคุณสมบัติเพิ่มเติมไม่กี่: 1) มันควรจะเริ่มต้นที่การใช้งานครั้งแรก (ซึ่งไม่เพียง แต่หมายถึงการเริ่มต้นล่าช้า แต่ยังรับประกันได้ว่าวัตถุพร้อมที่จะใช้จริง ๆ ) 2) มันควรจะเป็นเธรดที่ปลอดภัย รูปแบบโมดูลสามารถเปลี่ยนเป็นรูปแบบซิงเกิลตันได้ แต่เฉพาะในเบราว์เซอร์ (และไม่เสมอไป)
skalee

55
นี่ไม่ควรเป็นคำตอบที่ยอมรับได้ นี่ไม่ใช่ซิงเกิลเลย! นี่เป็นเพียงตัวแปรทั่วโลก มีโลกที่แตกต่างระหว่างสองสิ่งนี้
mlibby

172

ฉันคิดว่าวิธีที่สะอาดที่สุดคือ:

var SingletonFactory = (function(){
    function SingletonClass() {
        //do stuff
    }
    var instance;
    return {
        getInstance: function(){
            if (instance == null) {
                instance = new SingletonClass();
                // Hide the constructor so the returned object can't be new'd...
                instance.constructor = null;
            }
            return instance;
        }
   };
})();

หลังจากนั้นคุณสามารถเรียกใช้ฟังก์ชันเป็น

var test = SingletonFactory.getInstance();

4
หมายเหตุ: ตัวสร้างต้นฉบับสามารถอ่านได้อีกครั้งโดยใช้delete instance.constructor:x = SingletonClass.getInstance();delete x.constructor;new x.constructor;
Rob W

var test = SingletonClass.getInstance () - ดูไม่สะอาดมากหรือชอบ JS soultions อื่น ๆ ที่ลงท้ายด้วย a = new Foo (); b = new Foo (); a === b // true
Matthias

3
มีกลิ่นเหมือนโรงงานมากขึ้นสำหรับฉันด้วยส่วน "getInstance" ทั้งหมด
Lajos Meszaros

1
นี่ไม่ใช่ซิงเกิลเพราะคุณสามารถสร้างได้หลายอินสแตนซ์ด้วย Object.create
AndroidDev

ซอเสียแล้ว
HoffZ

103

ฉันไม่แน่ใจว่าฉันเห็นด้วยกับรูปแบบโมดูลที่ใช้แทนรูปแบบซิงเกิล ฉันเคยเห็น singletons ใช้และถูกทารุณกรรมในสถานที่ที่พวกเขาไม่จำเป็นทั้งหมดและฉันแน่ใจว่ารูปแบบโมดูลเติมช่องว่างมากมายที่โปรแกรมเมอร์จะใช้ singleton แต่รูปแบบโมดูลไม่ใช่แบบซิงเกิล

รูปแบบโมดูล:

var foo = (function () {
    "use strict";
    function aPrivateFunction() {}
    return { aPublicFunction: function () {...}, ... };
}());

ทุกอย่างเริ่มต้นในรูปแบบโมดูลเกิดขึ้นเมื่อFooมีการประกาศ นอกจากนี้รูปแบบโมดูลสามารถใช้ในการเริ่มต้นคอนสตรัคเตอร์ซึ่งสามารถสร้างอินสแตนซ์ได้หลายครั้ง ในขณะที่รูปแบบโมดูลเป็นเครื่องมือที่เหมาะสมสำหรับงานจำนวนมาก แต่ไม่เทียบเท่ากับซิงเกิลตัน

รูปแบบซิงเกิล:

แบบสั้น
var Foo = function () {
    "use strict";
    if (Foo._instance) {
        //this allows the constructor to be called multiple times
        //and refer to the same instance. Another option is to
        //throw an error.
        return Foo._instance;
    }
    Foo._instance = this;
    //Foo initialization code
};
Foo.getInstance = function () {
    "use strict";
    return Foo._instance || new Foo();
}
แบบยาวโดยใช้รูปแบบโมดูล
var Foo = (function () {
    "use strict";
    var instance; //prevent modification of "instance" variable
    function Singleton() {
        if (instance) {
            return instance;
        }
        instance = this;
        //Singleton initialization code
    }
    //instance accessor
    Singleton.getInstance = function () {
        return instance || new Singleton();
    }
    return Singleton;
}());

ในทั้งสองรูปแบบของรูปแบบซิงเกิลที่ฉันให้ไว้คอนสตรัคเองสามารถใช้เป็น accessor:

var a,
    b;
a = new Foo(); //constructor initialization happens here
b = new Foo();
console.log(a === b); //true

หากคุณรู้สึกไม่สบายใจกับการใช้ Constructor ด้วยวิธีนี้คุณสามารถโยนข้อผิดพลาดในif (instance)ข้อความสั่งและใช้รูปแบบยาว:

var a,
    b;
a = Foo.getInstance(); //constructor initialization happens here
b = Foo.getInstance();
console.log(a === b); //true

ฉันควรพูดถึงว่ารูปแบบซิงเกิลเหมาะกับรูปแบบฟังก์ชั่นคอนสตรัคของนัย:

function Foo() {
    if (Foo._instance) {
        return Foo._instance;
    }
    //if the function wasn't called as a constructor,
    //call it as a constructor and return the result
    if (!(this instanceof Foo)) {
        return new Foo();
    }
    Foo._instance = this;
}
var f = new Foo(); //calls Foo as a constructor
-or-
var f = Foo(); //also calls Foo as a constructor

4
ฉันไม่เคยพูดอะไรเกี่ยวกับซิงเกิลตันเป็นความคิดที่ดีหรือไม่ดี ฉันบอกว่าการใช้งานซิงเกิลตันของคุณซับซ้อนกว่าที่คิดเพราะคุณสับสนกับข้อ จำกัด ของ Java กับรูปแบบราวกับว่าคุณไม่เข้าใจเลย มันเหมือนกับการนำรูปแบบกลยุทธ์มาใช้โดยสร้างฟังก์ชั่นคอนสตรัคเตอร์และเมธอดเมื่อคุณสามารถใช้ฟังก์ชั่นนิรนามใน Javascript ได้
Esailija

11
@Eailija ฟังดูเหมือนว่าคุณจะไม่เข้าใจรูปแบบซิงเกิล รูปแบบซิงเกิลเป็นรูปแบบการออกแบบที่ จำกัด การสร้างอินสแตนซ์ของคลาสให้เป็นหนึ่งวัตถุ var singleton = {}ไม่พอดีกับคำจำกัดความที่
zzzzBov

9
ใช้กับภาษาอย่าง Java เท่านั้นเนื่องจากข้อ จำกัด var singleton = {}เป็นวิธีที่คุณใช้เดี่ยวใน Javascript
Esailija

2
@Esailija "ในภาษาการเขียนโปรแกรมต้นแบบโดยที่วัตถุ แต่ไม่ได้ใช้คลาส ... " JavaScript มีแนวคิดของคลาสดังนั้นจึงไม่มีผลบังคับใช้
zzzzBov

3
ยังไงซะ. Singleton เป็นรูปแบบการต่อต้านและไม่ควรใช้ในสภาพแวดล้อมแบบ sandboxed ที่มีการจัดการอย่างจาวาสคริปต์ misko.hevery.com/2008/08/17/singletons-are-pathological-liars - youtube.com/watch?v=G32acYVd9LY blogs.msdn.com/b/scottdensmore/archive/2004/05/25/140827.aspx - jalf.dk/blog/2010/03/ … - kore-nordmann.de/blog/0103_static_considered_harmful.html - phparch.com/2010/03/static-methods-vs-singletons-choose-neither
Benjamin Gruenbaum

18

ในes6:

class Singleton {
  constructor () {
    if (!Singleton.instance) {
      Singleton.instance = this
    }
    // Initialize object
    return Singleton.instance
  }
  // Properties & Methods
}

const instance = new Singleton()
Object.freeze(instance)

export default instance

1
การแช่แข็งน่าจะสมเหตุสมผลถ้า Singleton น่าจะเป็น wrapper รอบ ๆ คลาสอื่น ๆ และมี แต่instanceฟิลด์เท่านั้น เนื่องจากขณะนี้ ( instanceตั้งค่าเป็นthis) คลาสนี้อาจมีฟิลด์อื่นเช่นกันและการแช่แข็งไม่สมเหตุสมผล
thisismydesign

10

การทำงานต่อไปนี้ในโหนด v6

class Foo {
  constructor(msg) {

    if (Foo.singleton) {
      return Foo.singleton;
    }

    this.msg = msg;
    Foo.singleton = this;
    return Foo.singleton;
  }
}

เราทดสอบ:

const f = new Foo('blah');
const d = new Foo('nope');
console.log(f); // => Foo { msg: 'blah' }
console.log(d); // => Foo { msg: 'blah' }

8

ใน ES6 วิธีที่ถูกต้องคือ:

class MyClass {
  constructor() {
    if (MyClass._instance) {
      throw new Error("Singleton classes can't be instantiated more than once.")
    }
    MyClass._instance = this;

    // ... your rest of the constructor code goes after this
  }
}

var instanceOne = new MyClass() // Executes succesfully 
var instanceTwo = new MyClass() // Throws error

หรือหากคุณไม่ต้องการให้เกิดข้อผิดพลาดในการสร้างอินสแตนซ์ที่สองคุณสามารถส่งคืนอินสแตนซ์สุดท้ายได้เช่น:

class MyClass {
  constructor() {
    if (MyClass._instance) {
      return MyClass._instance
    }
    MyClass._instance = this;

    // ... your rest of the constructor code goes after this
  }
}

var instanceOne = new MyClass()
var instanceTwo = new MyClass()

console.log(instanceOne === instanceTwo) // logs "true"


สวัสดีคุณสามารถช่วยฉันให้ทราบความแตกต่างระหว่าง _instance และอินสแตนซ์เมื่อฉันใช้อินสแตนซ์และรหัสไม่ทำงาน
Abhinav bhardwaj

ไม่มีความแตกต่างทางเทคนิคในการเป็นและinstance _instanceมันเป็นเพียงแค่การตั้งชื่อในภาษาการเขียนโปรแกรมที่เราตั้งชื่อตัวแปรส่วนตัวที่มีเครื่องหมายขีดล่าง ฉันสงสัยว่าสาเหตุที่ทำให้รหัสของคุณไม่ทำงานคือคุณใช้this.instanceแทนMyClass.instance
UtkarshPramodGupta

7

มีมากกว่าหนึ่งวิธีในการสกินแมว :) ขึ้นอยู่กับรสนิยมหรือความต้องการเฉพาะของคุณคุณสามารถใช้วิธีแก้ปัญหาที่เสนอ ฉันจะไปหาทางออกแรกของ CMS เมื่อทำได้ (เมื่อคุณไม่ต้องการความเป็นส่วนตัว) เนื่องจากคำถามเกี่ยวกับสิ่งที่ง่ายและสะอาดที่สุดนั่นคือผู้ชนะ หรือแม้กระทั่ง:

var myInstance = {}; // done!

นี่ (อ้างจากบล็อกของฉัน) ...

var SingletonClass = new function() { 
    this.myFunction() { 
        //do stuff 
    } 
    this.instance = 1; 
}

ไม่สมเหตุสมผล (ตัวอย่างบล็อกของฉันไม่ได้เป็นอย่างใดอย่างหนึ่ง) เพราะมันไม่จำเป็นต้องมี vars ส่วนตัวดังนั้นมันจึงค่อนข้างเหมือนกับ:

var SingletonClass = { 
    myFunction: function () { 
        //do stuff 
    },
    instance: 1 
}

ข้อมูลโค้ด 2 มีข้อผิดพลาดทางไวยากรณ์ คุณไม่สามารถเขียนได้this.f(){}
xoxox

7

ฉันเลิกตอบคำถามแล้วดูคำตอบของฉันคนอื่น

โดยปกติรูปแบบโมดูล (ดูคำตอบ CMS ') ซึ่งไม่ใช่รูปแบบซิงเกิลดีพอ อย่างไรก็ตามหนึ่งในคุณสมบัติของซิงเกิลตันคือการเริ่มต้นนั้นจะล่าช้าจนกว่าวัตถุจะต้องการ รูปแบบโมดูลขาดคุณสมบัตินี้

ข้อเสนอของฉัน (CoffeeScript):

window.singleton = (initializer) ->
  instance = undefined
  () ->
    return instance unless instance is undefined
    instance = initializer()

ซึ่งรวบรวมไว้ใน JavaScript:

window.singleton = function(initializer) {
    var instance;
    instance = void 0;
    return function() {
        if (instance !== void 0) {
            return instance;
        }
        return instance = initializer();
    };
};

จากนั้นฉันสามารถทำสิ่งต่อไปนี้:

window.iAmSingleton = singleton(function() {
    /* This function should create and initialize singleton. */
    alert("creating");
    return {property1: 'value1', property2: 'value2'};
});


alert(window.iAmSingleton().property2); // "creating" will pop up; then "value2" will pop up
alert(window.iAmSingleton().property2); // "value2" will pop up but "creating" will not
window.iAmSingleton().property2 = 'new value';
alert(window.iAmSingleton().property2); // "new value" will pop up

ทำไมคุณจะโหลดโมดูลหากไม่จำเป็น? และเมื่อคุณต้องการโหลดโมดูลจากนั้นคุณโหลดโมดูลและเริ่มต้น
Esailija

6

คำตอบสั้น ๆ :

เนื่องจากลักษณะที่ไม่มีการปิดกั้นของ JavaScript, Singletons ใน JavaScript มีการใช้งานที่น่าเกลียดจริงๆ ตัวแปรทั่วโลกจะให้อินสแตนซ์เดียวแก่คุณผ่านแอปพลิเคชันทั้งหมดโดยไม่มีการเรียกกลับทั้งหมดรูปแบบโมดูลจะซ่อนภายในอินเทอร์เฟซ ดูคำตอบ @CMS

แต่เนื่องจากคุณต้องการซิงเกิล ...

var singleton = function(initializer) {

  var state = 'initial';
  var instance;
  var queue = [];

  var instanceReady = function(createdInstance) {
    state = 'ready';
    instance = createdInstance;
    while (callback = queue.shift()) {
      callback(instance);
    }
  };

  return function(callback) {
    if (state === 'initial') {
      state = 'waiting';
      queue.push(callback);
      initializer(instanceReady);
    } else if (state === 'waiting') {
      queue.push(callback);
    } else {
      callback(instance);
    }
  };

};

การใช้งาน:

var singletonInitializer = function(instanceReady) {
  var preparedObject = {property: 'value'};
  // calling instanceReady notifies singleton that instance is ready to use
  instanceReady(preparedObject);
}
var s = singleton(singletonInitializer);

// get instance and use it
s(function(instance) {
  instance.doSomething();
});

คำอธิบาย:

Singletons ให้คุณมากกว่าหนึ่งอินสแตนซ์ผ่านแอปพลิเคชันทั้งหมด: การกำหนดค่าเริ่มต้นของพวกเขาล่าช้าจนถึงการใช้ครั้งแรก นี่เป็นเรื่องใหญ่จริง ๆ เมื่อคุณจัดการกับวัตถุที่การเริ่มต้นมีราคาแพง โดยปกติแพงหมายถึง I / O และใน JavaScript I / O มักหมายถึงการโทรกลับ

อย่าเชื่อคำตอบที่ให้อินเทอร์เฟซกับinstance = singleton.getInstance()คุณพวกเขาทุกคนพลาดจุดสำคัญ

หากพวกเขาไม่ได้รับการเรียกกลับให้ทำงานเมื่ออินสแตนซ์พร้อมแล้วพวกเขาจะไม่ทำงานเมื่อ initializer ใช้ I / O

ใช่การเรียกกลับจะดูน่าเกลียดกว่าการเรียกใช้ฟังก์ชันซึ่งจะส่งคืนอินสแตนซ์ของวัตถุทันที แต่อีกครั้ง: เมื่อคุณทำ I / O การเรียกกลับเป็นสิ่งที่จำเป็น หากคุณไม่ต้องการทำ I / O ใด ๆ การสร้างอินสแตนซ์นั้นมีราคาถูกพอที่จะเริ่มต้นโปรแกรมได้

ตัวอย่างที่ 1, initializer ราคาถูก:

var simpleInitializer = function(instanceReady) {
  console.log("Initializer started");
  instanceReady({property: "initial value"});
}

var simple = singleton(simpleInitializer);

console.log("Tests started. Singleton instance should not be initalized yet.");

simple(function(inst) {
  console.log("Access 1");
  console.log("Current property value: " + inst.property);
  console.log("Let's reassign this property");
  inst.property = "new value";
});
simple(function(inst) {
  console.log("Access 2");
  console.log("Current property value: " + inst.property);
});

ตัวอย่างที่ 2 การกำหนดค่าเริ่มต้นด้วย I / O:

ในตัวอย่างนี้ทำให้setTimeoutการดำเนินการ I / O มีราคาแพง นี่แสดงให้เห็นว่าเหตุใดซิงเกิลใน JavaScript ต้องการการเรียกกลับ

var heavyInitializer = function(instanceReady) {
  console.log("Initializer started");
  var onTimeout = function() {
    console.log("Initializer did his heavy work");
    instanceReady({property: "initial value"});
  };
  setTimeout(onTimeout, 500);
};

var heavy = singleton(heavyInitializer);

console.log("In this example we will be trying");
console.log("to access singleton twice before it finishes initialization.");

heavy(function(inst) {
  console.log("Access 1");
  console.log("Current property value: " + inst.property);
  console.log("Let's reassign this property");
  inst.property = "new value";
});

heavy(function(inst) {
  console.log("Access 2. You can see callbacks order is preserved.");
  console.log("Current property value: " + inst.property);
});

console.log("We made it to the end of the file. Instance is not ready yet.");

ผ่านการทดลองและความยากลำบากพร้อมคำตอบแบบซิงเกิลอื่น ๆ ที่ไม่ได้ตัดออกไปฉันใช้รหัสผลลัพธ์ที่คล้ายกันอย่างน่าทึ่ง
mheyman

ไม่ว่าจะด้วยเหตุผลใดก็ตามนี่เป็นคำตอบเดียวที่สมเหตุสมผลสำหรับฉัน คำตอบอื่น ๆ ทั้งหมดทำให้ฉันนึกถึงตอนการแสดงของคนโง่ที่ชายสามคนพยายามปีนกำแพงสี่คนสูงโดยการปีนไหล่แต่ละข้างซ้ำ ๆ
ทิมโอกิลวี่

การซ้อนแคลด์เป็นสิ่งที่ฉันต้องการจริงๆ! ขอบคุณ !!
ทิมโอกิลวี่

วิธีการนี้ไม่เคยให้ซิงเกิลเดี่ยวกับคุณ: singleton (singletonInitializer)! == singleton (singletonInitializer) พวกเขาเป็นสองอินสแตนซ์ที่ต่างกัน ฟังก์ชันผลลัพธ์ที่คุณส่งคืนสามารถใช้เพื่อแนบการเรียกกลับมากขึ้นกับอินสแตนซ์ แต่ไม่ได้ระบุอย่างเคร่งครัดว่าสามารถสร้างเพียงหนึ่งอินสแตนซ์ของประเภทนี้ ซึ่งเป็นจุดรวมของซิงเกิล
โอเว่น

6

ฉันได้ตัวอย่างนี้จากรูปแบบ JavaScript สร้างแอปพลิเคชันที่ดีขึ้นด้วยการเข้ารหัสและรูปแบบการออกแบบโดยหนังสือของStoyan Stefanovในกรณีที่คุณต้องการคลาสการใช้งานอย่างง่ายเช่นวัตถุ singltone คุณสามารถใช้ฟังก์ชั่นได้ทันทีดังนี้

var ClassName;

(function() {
    var instance;
    ClassName = function ClassName() {
        //If private instance variable already initialized return reference
        if(instance) {
            return instance;   
        }
        //If instance does not created save pointer of original reference
        //to private instance variable. 
        instance = this;

        //All constructor initialization will be here
        // i.e.: 
        this.someProperty = 0;
        this.someMethod = function() {
            //Some action here
        };
    };
}());

และคุณสามารถตรวจสอบตัวอย่างนี้โดยกรณีทดสอบต่อไปนี้:

//Extending defined class like Singltone object using new prototype property
ClassName.prototype.nothing = true;
var obj_1 = new ClassName();
//Extending defined class like Singltone object using new prototype property
ClassName.prototype.everything = true; 
var obj_2 = new ClassName();

//Testing does this two object pointing to same instance
console.log(obj_1 === obj_2); //Result is true, it points to same instance object

//All prototype properites work
//no matter when they were defined
console.log(obj_1.nothing && obj_1.everything 
            && obj_2.nothing && obj_2.everything); //Result true


//Values of properties which is defined inside of constructor
console.log(obj_1.someProperty);// output 0
console.log(obj_2.someProperty);// output 0 
//Changing property value 
obj_1.someProperty = 1;

console.log(obj_1.someProperty);// output 1
console.log(obj_2.someProperty);// output 1

console.log(obj_1.constructor === ClassName); //Output true 

วิธีนี้ผ่านทุกกรณีทดสอบในขณะที่การใช้งานแบบสแตติกส่วนตัวจะล้มเหลวเมื่อมีการใช้ส่วนขยายต้นแบบ (สามารถแก้ไขได้ แต่จะไม่ง่าย) และการใช้งานสแตติกแบบสาธารณะแนะนำให้เลือกน้อยลงเนื่องจากมีการเปิดเผยต่อสาธารณะ

การสาธิต jsFiddly


5

ฉันคิดว่าฉันพบวิธีที่ดีที่สุดในการเขียนโปรแกรมใน JavaScript แต่คุณต้องมีจินตนาการ ฉันได้รับแนวคิดนี้จากเทคนิคการทำงานในหนังสือ "ส่วนที่ดีจาวาสคริปต์"

แทนที่จะใช้คำหลักใหม่คุณสามารถสร้างคลาสดังนี้:

function Class()
{
    var obj = {}; // Could also be used for inheritence if you don't start with an empty object.

    var privateVar;
    obj.publicVar;

    obj.publicMethod= publicMethod;
    function publicMethod(){} 

    function privateMethod(){} 

    return obj;
}

คุณสามารถยกตัวอย่างวัตถุด้านบนโดยพูดว่า:

var objInst = Class(); // !!! NO NEW KEYWORD

ในขณะนี้ด้วยวิธีการทำงานนี้ในใจคุณสามารถสร้างซิงเกิลตันแบบนี้:

ClassSingleton = function()
{
    var instance= null;

    function Class() // This is the class like the above one
    {
        var obj = {};
        return obj;
    }

    function getInstance()
    {
        if( !instance )
            instance = Class(); // Again no new keyword;

        return instance;
    }   

    return { getInstance : getInstance };

}();

ตอนนี้คุณสามารถรับอินสแตนซ์ของคุณโดยการโทร

var obj = ClassSingleton.getInstance();

ฉันคิดว่านี่เป็นวิธีที่เรียบร้อยที่สุดเพราะไม่สามารถเข้าถึง "คลาส" ที่สมบูรณ์ได้


แต่ด้วยเทคนิคนี้คุณสามารถมีได้มากกว่าหนึ่งตัวอย่าง นั่นไม่ถูกต้อง
nicolascolman

1
ฉันไม่คิดอย่างนั้นคุณไม่สามารถเข้าถึงชั้นเรียนได้โดยไม่ต้องผ่าน getInstance คุณสามารถทำอย่างละเอียด?
David

David Maes ขออภัย แต่ฉันไม่ได้สังเกตการตรวจสอบในตัวอย่างที่สอง ฉันขอโทษ.
nicolascolman

4

@CMS และ @zzzzBov ทั้งคู่ต่างให้คำตอบที่ยอดเยี่ยม แต่เพียงเพิ่มการตีความของตัวเองตามการย้ายไปสู่การพัฒนาต่อมน้ำอย่างหนักจาก PHP / Zend Framework ที่รูปแบบซิงเกิลทั่วไป

รหัสที่แสดงความคิดเห็นต่อไปนี้เป็นไปตามข้อกำหนดต่อไปนี้:

  • อินสแตนซ์หนึ่งและหนึ่งเดียวของวัตถุฟังก์ชันอาจถูกยกตัวอย่าง
  • ไม่สามารถใช้งานอินสแตนซ์ต่อสาธารณะได้และสามารถเข้าถึงได้ด้วยวิธีสาธารณะเท่านั้น
  • ตัวสร้างไม่พร้อมใช้งานแบบสาธารณะและอาจถูกสร้างอินสแตนซ์เท่านั้นถ้าไม่มีอินสแตนซ์พร้อมใช้งาน
  • การประกาศของนวกรรมิกจะต้องอนุญาตให้มีการดัดแปลงโซ่ต้นแบบ สิ่งนี้จะอนุญาตให้ตัวสร้างสืบทอดจากต้นแบบอื่น ๆ และเสนอวิธี "สาธารณะ" สำหรับอินสแตนซ์

รหัสของฉันคล้ายกับ @ zzzzBov มากยกเว้นว่าฉันได้เพิ่มเชนต้นแบบให้กับคอนสตรัคเตอร์และความคิดเห็นเพิ่มเติมที่จะช่วยให้ผู้ที่มาจาก PHP หรือภาษาที่คล้ายกันแปล OOP ดั้งเดิมเป็นธรรมชาติของจาวาสคริปต์ มันอาจไม่ใช่ "ง่ายที่สุด" แต่ฉันเชื่อว่าเหมาะสมที่สุด

// declare 'Singleton' as the returned value of a self-executing anonymous function
var Singleton = (function () {
    "use strict";
    // 'instance' and 'constructor' should not be availble in a "public" scope
    // here they are "private", thus available only within 
    // the scope of the self-executing anonymous function
    var _instance=null;
    var _constructor = function (name) {
        this.name = name || 'default';
    }

    // prototypes will be "public" methods available from the instance
    _constructor.prototype.getName = function () {
        return this.name;
    }

    // using the module pattern, return a static object
    // which essentially is a list of "public static" methods
    return {
        // because getInstance is defined within the same scope
        // it can access the "private" 'instance' and 'constructor' vars
        getInstance:function (name) {
            if (!_instance) {
                console.log('creating'); // this should only happen once
                _instance = new _constructor(name);
            }
            console.log('returning');
            return _instance;
        }
    }

})(); // self execute

// ensure 'instance' and 'constructor' are unavailable 
// outside the scope in which they were defined
// thus making them "private" and not "public"
console.log(typeof _instance); // undefined
console.log(typeof _constructor); // undefined

// assign instance to two different variables
var a = Singleton.getInstance('first');
var b = Singleton.getInstance('second'); // passing a name here does nothing because the single instance was already instantiated

// ensure 'a' and 'b' are truly equal
console.log(a === b); // true

console.log(a.getName()); // "first"
console.log(b.getName()); // also returns "first" because it's the same instance as 'a'

โปรดทราบว่าในทางเทคนิคแล้วฟังก์ชั่นที่ไม่เปิดเผยตัวตนของผู้ใช้งานจะเป็น Singleton ดังที่แสดงไว้ในรหัสที่ได้รับจาก @CMS สิ่งเดียวที่จับได้ที่นี่คือมันเป็นไปไม่ได้ที่จะแก้ไขต้นแบบลูกโซ่ของนวกรรมิกเมื่อนวกรรมิกเองไม่ระบุชื่อ

โปรดทราบว่าสำหรับ Javascript แนวคิดของ "สาธารณะ" และ "ส่วนตัว" ไม่สามารถใช้งานได้เช่นเดียวกับใน PHP หรือ Java แต่เราได้รับผลกระทบที่เหมือนกันโดยใช้ประโยชน์จากกฎความพร้อมใช้งานของขอบเขต Javascript


สามารถสร้างหลายอินสแตนซ์ได้จากรหัสของคุณ:var a = Singleton.getInstance('foo'); var b = new a.constructor('bar');
zzzzBov

@zzzzBov: ฉันเพิ่งได้รับข้อผิดพลาดพยายามที่ในซอของฉัน: jsfiddle.net/rxMu8
cincodenada

4

ไม่แน่ใจว่าทำไมไม่มีใครนำเรื่องนี้ขึ้นมา แต่คุณสามารถทำได้:

var singleton = new (function() {
  var bar = 123

  this.foo = function() {
    // whatever
  }
})()

นี่น่าจะเป็นวิธีที่เรียบร้อยในการข้ามเมธอด getInstance และหาวิธีแก้ไขที่ง่ายกว่า แต่โปรดจำไว้ว่าซิงเกิลตันจะดำเนินการทันทีที่มีการวิเคราะห์ไฟล์หมายความว่าผู้ฟัง DOM ต้องถูกห่อเป็น $ (เอกสาร) ฟังก์ชั่น.
Ready แล้ว

4

คำตอบที่ชัดเจนควรเป็นคำนี้จากหนังสือ Learning ลวดลายการออกแบบ JavaScript โดย Addy Osmani

var mySingleton = (function () {
 
  // Instance stores a reference to the Singleton
  var instance;
 
  function init() {
 
    // Singleton
 
    // Private methods and variables
    function privateMethod(){
        console.log( "I am private" );
    }
 
    var privateVariable = "Im also private";
 
    var privateRandomNumber = Math.random();
 
    return {
 
      // Public methods and variables
      publicMethod: function () {
        console.log( "The public can see me!" );
      },
 
      publicProperty: "I am also public",
 
      getRandomNumber: function() {
        return privateRandomNumber;
      }
 
    };
 
  };
 
  return {
 
    // Get the Singleton instance if one exists
    // or create one if it doesn't
    getInstance: function () {
 
      if ( !instance ) {
        instance = init();
      }
 
      return instance;
    }
 
  };
 
})();


3

ฉันเชื่อว่านี่เป็นวิธีที่ง่ายที่สุด / สะอาดที่สุดและใช้งานง่ายที่สุดแม้ว่าจะต้องใช้ ES7:

export default class Singleton {

  static instance;

  constructor(){
    if(instance){
      return instance;
    }

    this.state = "duke";
    this.instance = this;
  }

}

ซอร์สโค้ดมาจาก: adam-bien.com


สิ่งนี้ผิดอย่างสมบูรณ์และจะทำให้เกิดข้อผิดพลาดในการโทรnew Singleton()
UtkarshPramodGupta

2

เกิดอะไรขึ้นกับสิ่งนี้?

function Klass() {
   var instance = this;
   Klass = function () { return instance; }
}

Test = Klass; t1 = new Test(); t2 = new Test();- ไม่มีโอกาสเปลี่ยนชื่อคลาสหรือเลือกเนมสเปซอื่น
zzzzBov

2

ฉันสามารถใส่ 5 เหรียญของฉันได้ไหม ฉันมีฟังก์ชั่นตัวสร้างเช่น

var A = function(arg1){
  this.arg1 = arg1  
};

สิ่งที่ฉันต้องทำคือวัตถุทุกชิ้นที่สร้างโดย CF นี้จะเหมือนกัน

var X = function(){
  var instance = {};
  return function(){ return instance; }
}();

ทดสอบ

var x1 = new X();
var x2 = new X();
console.log(x1 === x2)

2

ฉันพบว่าสิ่งต่อไปนี้เป็นรูปแบบซิงเกิลที่ง่ายที่สุดเพราะการใช้ตัวดำเนินการใหม่ทำให้สิ่งนี้สามารถใช้ได้ทันทีภายในฟังก์ชั่นที่ไม่จำเป็นต้องส่งคืนวัตถุตัวอักษร:

var singleton = new (function () {

  var private = "A private value";
  
  this.printSomething = function() {
      console.log(private);
  }
})();

singleton.printSomething();


2

นี่เป็นตัวอย่างง่าย ๆ ในการอธิบายรูปแบบซิงเกิลใน javascript

 var Singleton=(function(){
      var instance;
      var init=function(){
           return {
             display:function(){
             alert("This is a Singleton patern demo");
              }
            };
           }; 
            return {
              getInstance:function(){
                   if(!instance){
                     alert("Singleton check");
                      instance=init();
                       }
               return instance;
             }
         };

    })();

   // In this call first display alert("Singleton check")
  // and then alert("This is a Singleton patern demo");
  // It means one object is created

    var inst=Singleton.getInstance();
    inst.display();

    // In this call only display alert("This is a Singleton patern demo")
   //  it means second time new object is not created, 
   //  it uses the already created object 

    var inst1=Singleton.getInstance();
    inst1.display();

1

ฉันต้องการซิงเกิลตันหลายอันด้วย:

  • การเริ่มต้นขี้เกียจ
  • พารามิเตอร์เริ่มต้น

และนี่คือสิ่งที่ฉันมาด้วย:

createSingleton ('a', 'add', [1, 2]);
console.log(a);

function createSingleton (name, construct, args) {
    window[name] = {};
    window[construct].apply(window[name], args);
    window[construct] = null;
}

function add (a, b) {
    this.a = a;
    this.b = b;
    this.sum = a + b;
}
  • args จะต้องเป็น Array เพื่อให้สิ่งนี้ทำงานได้ดังนั้นหากคุณมีตัวแปรว่างเปล่าให้ส่งค่าใน []

  • ฉันใช้วัตถุหน้าต่างในฟังก์ชัน แต่ฉันสามารถส่งผ่านพารามิเตอร์เพื่อสร้างขอบเขตของตัวเอง

  • พารามิเตอร์ name และ construct เป็นเพียง String สำหรับ window [] เพื่อให้ทำงานได้ แต่ด้วยการตรวจสอบประเภทอย่างง่าย window.name และ window.construct ก็สามารถทำได้เช่นกัน


1

วิธีการเกี่ยวกับวิธีนี้เพียงแค่ประกันชั้นไม่สามารถใหม่อีกครั้ง

โดยสิ่งนี้คุณสามารถใช้instanceofop ได้เช่นกันคุณสามารถใช้ต้นแบบลูกโซ่เพื่อสืบทอดคลาสมันเป็นคลาสปกติ แต่ไม่สามารถใหม่ได้ถ้า yuu ต้องการรับอินสแตนซ์เพียงแค่ใช้getInstance

function CA()
{
    if(CA.instance)
    {
        throw new Error('can not new this class');
    }else{
        CA.instance = this;
    }

}
/**
 * @protected
 * @static
 * @type {CA}
 */
CA.instance = null;
/** @static */
CA.getInstance = function()
{
    return CA.instance;
}

CA.prototype = 
/** @lends CA#*/
{
    func: function(){console.log('the func');}
}
// initilize the instance
new CA();

// test here
var c = CA.getInstance()
c.func();
console.assert(c instanceof CA)
// this will failed
var b = new CA();

หากคุณไม่ต้องการเปิดเผยinstanceสมาชิกเพียงแค่ปิดสมาชิก


1

ต่อไปนี้เป็นตัวอย่างข้อมูลจากการเดินผ่านของฉันเพื่อใช้รูปแบบซิงเกิล เรื่องนี้เกิดขึ้นกับฉันในระหว่างการสัมภาษณ์และฉันรู้สึกว่าฉันควรจะจับมันที่ไหนซักแห่ง

/*************************************************
   *     SINGLETON PATTERN IMPLEMENTATION          *
   *************************************************/

  //since there are no classes in javascript, every object is technically a singleton
  //if you don't inherit from it or copy from it.
  var single = {};
  //Singleton Implementations
  //Declaring as a Global Object...you are being judged!


  var Logger = function() {
    //global_log is/will be defined in GLOBAL scope here
    if(typeof global_log === 'undefined'){
      global_log = this;
    }
    return global_log;
  };


  //the below 'fix' solves the GLOABL variable problem but
  //the log_instance is publicly available and thus can be 

  //tampered with.
  function Logger() {
    if(typeof Logger.log_instance === 'undefined'){
      Logger.log_instance = this;
    }


    return Logger.log_instance;
   };


  //the correct way to do it to give it a closure!


  function logFactory() {
    var log_instance; //private instance
    var _initLog = function() { //private init method
      log_instance = 'initialized';
      console.log("logger initialized!")
    }
    return {
      getLog : function(){ //the 'privileged' method 
        if(typeof log_instance === 'undefined'){
          _initLog();
        }
        return log_instance;
      }
    };
  }

  /***** TEST CODE ************************************************
  //using the Logger singleton
  var logger = logFactory();//did i just gave LogFactory a closure?
  //create an instance of the logger
  var a = logger.getLog();
  //do some work
  //get another instance of the logger
  var b = logger.getLog();


  //check if the two logger instances are same?
  console.log(a === b); //true
  *******************************************************************/

สามารถพบได้ในหน้าสรุปสาระสำคัญของฉัน


1
function Unicode()
  {
  var i = 0, unicode = {}, zero_padding = "0000", max = 9999;
  //Loop through code points
  while (i < max) {
    //Convert decimal to hex value, find the character, then pad zeroes to the codepoint
    unicode[String.fromCharCode(parseInt(i, 16))] = ("u" + zero_padding + i).substr(-4);
    i = i + 1;
    }

  //Replace this function with the resulting lookup table
  Unicode = unicode;
  }

//Usage
Unicode();
//Lookup
Unicode["%"]; //returns 0025

1

นี่ไม่ใช่ซิงเกิลด้วยเหรอ?

function Singleton() {
    var i = 0;
    var self = this;

    this.doStuff = function () {
        i = i + 1;
        console.log( 'do stuff',i );
    };

    Singleton = function () { return self };
    return this;
}

s = Singleton();
s.doStuff();

1

คุณสามารถทำได้ด้วยการตกแต่งในตัวอย่างด้านล่างสำหรับ TypeScript:

class YourClass {

    @Singleton static singleton() {}

}

function Singleton(target, name, descriptor) {
    var instance;
    descriptor.value = () => {
        if(!instance) instance = new target;
        return instance;
    };
}

จากนั้นคุณใช้ซิงเกิลแบบนี้:

var myInstance = YourClass.singleton();

จากการเขียนนี้มัณฑนากรไม่พร้อมใช้งานในเครื่องมือ JavaScript คุณจะต้องแน่ใจว่า JavaScript runtime ของคุณเปิดใช้งานมัณฑนากรหรือใช้คอมไพเลอร์เช่น Babel และ TypeScript

โปรดทราบว่าอินสแตนซ์ซิงเกิลตันถูกสร้างขึ้น "สันหลังยาว" นั่นคือมันถูกสร้างขึ้นเฉพาะเมื่อคุณใช้เป็นครั้งแรก


1

รูปแบบโมดูล: ใน "สไตล์ที่อ่านได้มากขึ้น" คุณสามารถดูได้อย่างง่ายดายว่าวิธีใดเป็นสาธารณะและวิธีใดเป็นของเอกชน

var module = (function(_name){
   /*Local Methods & Values*/
   var _local = {
      name : _name,
      flags : {
        init : false
      }
   }

   function init(){
     _local.flags.init = true;
   }

   function imaprivatemethod(){
     alert("hi im a private method");
   }

   /*Public Methods & variables*/

   var $r = {}; //this object will hold all public methods.

   $r.methdo1 = function(){
       console.log("method1 call it");
   }

   $r.method2 = function(){
      imaprivatemethod(); //calling private method
   }

   $r.init = function(){
      inti(); //making init public in case you want to init manually and not automatically
   }

   init(); //automatically calling init method

   return $r; //returning all publics methods

})("module");

ตอนนี้คุณสามารถใช้วิธีสาธารณะเช่น

module.method2 (); // -> ฉันกำลังเรียกวิธีการส่วนตัวผ่านการแจ้งเตือนวิธีการสาธารณะ ("สวัสดีฉันเป็นวิธีการส่วนตัว")

http://jsfiddle.net/ncubica/xMwS9/


1

ซิงเกิล:

ตรวจสอบให้แน่ใจว่าคลาสมีเพียงอินสแตนซ์เดียวและให้จุดเข้าถึงส่วนกลางแก่คลาสนั้น

รูปแบบ Singleton จำกัด จำนวนอินสแตนซ์ของวัตถุเฉพาะให้เป็นหนึ่งเดียว อินสแตนซ์เดียวนี้เรียกว่าซิงเกิล

  • กำหนด getInstance () ซึ่งจะส่งกลับอินสแตนซ์ที่ไม่ซ้ำกัน
  • รับผิดชอบในการสร้างและจัดการวัตถุอินสแตนซ์

วัตถุ Singleton ถูกนำมาใช้เป็นฟังก์ชั่นที่ไม่ระบุชื่อทันที ฟังก์ชั่นจะดำเนินการทันทีโดยใส่ในวงเล็บแล้วตามด้วยวงเล็บสองอันเพิ่มเติม มันถูกเรียกว่าไม่ระบุชื่อเพราะไม่มีชื่อ

โปรแกรมตัวอย่าง

var Singleton = (function () {
    var instance;
 
    function createInstance() {
        var object = new Object("I am the instance");
        return object;
    }
 
    return {
        getInstance: function () {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();
 
function run() {
 
    var instance1 = Singleton.getInstance();
    var instance2 = Singleton.getInstance();
 
    alert("Same instance? " + (instance1 === instance2));  
}

run()


1

Simplest / Cleanest สำหรับฉันหมายถึงเพียงแค่เข้าใจและไม่มีเสียงระฆังและเสียงดังที่ถูกกล่าวถึงในการอภิปรายเวอร์ชัน Java:

วิธีที่มีประสิทธิภาพในการใช้รูปแบบซิงเกิลใน Java คืออะไร?

คำตอบที่ตรงกับที่ง่ายที่สุด / สะอาดที่สุดในมุมมองของฉันคือ:

https://stackoverflow.com/a/70824/1497139

และสามารถแปลเป็น JavaScript ได้เพียงบางส่วนเท่านั้น ความแตกต่างบางประการใน Javascript คือ:

  • ตัวสร้างไม่สามารถเป็นแบบส่วนตัวได้
  • คลาสไม่สามารถประกาศเขตข้อมูลได้

แต่ได้รับไวยากรณ์ ECMA ล่าสุดเป็นไปได้ที่จะเข้าใกล้:

รูปแบบซิงเกิลเป็นตัวอย่างคลาส JavaScript

 class Singleton {

  constructor(field1,field2) {
    this.field1=field1;
    this.field2=field2;
    Singleton.instance=this;
  }

  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance=new Singleton('DefaultField1','DefaultField2');
    }
    return Singleton.instance;
  }
}

ตัวอย่างการใช้งาน

console.log(Singleton.getInstance().field1);
console.log(Singleton.getInstance().field2);

ตัวอย่างผลลัพธ์

DefaultField1
DefaultField2

1
function Once() {
    return this.constructor.instance || (this.constructor.instance = this);
}

function Application(name) {
    let app = Once.call(this);

    app.name = name;

    return app;
}

หากคุณเข้าเรียน:

class Once {
    constructor() {
        return this.constructor.instance || (this.constructor.instance = this);
    }
}

class Application extends Once {
    constructor(name) {
        super();

        this.name = name;
    }
}

ทดสอบ:

console.log(new Once() === new Once());

let app1 = new Application('Foobar');
let app2 = new Application('Barfoo');

console.log(app1 === app2);
console.log(app1.name); // Barfoo

1

ถ้าคุณต้องการใช้คลาส:

class Singleton {
  constructor(name, age) {
    this.name = name;
    this.age = age;
    if(this.constructor.instance)
      return this.constructor.instance;
    this.constructor.instance = this;
  }
}
let x = new Singleton('s',1);
let y = new Singleton('k',2);

ผลลัพธ์สำหรับข้างต้นจะเป็น:

console.log(x.name,x.age,y.name,y.age) // s 1 s 1

อีกวิธีในการเขียน Singleton โดยใช้ฟังก์ชั่น

function AnotherSingleton (name,age) {
  this.name = name;
  this.age = age;
  if(this.constructor.instance)
    return this.constructor.instance;
  this.constructor.instance = this;
}

let a = new AnotherSingleton('s',1);
let b = new AnotherSingleton('k',2);

ผลลัพธ์สำหรับข้างต้นจะเป็น:

console.log(a.name,a.age,b.name,b.age)// s 1 s 1
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.