用JavaScript实现单例模式的最简单/最干净的方法是什么?
当前回答
简单的例子
class Settings {
constructor() {
if (Settings.instance instanceof Settings) {
return Settings.instance;
}
this.settings = {
id: Math.floor(Math.random() * 4000),
name: "background",
};
Object.freeze(this.settings);
Object.freeze(this);
Settings.instance = this;
}
}
var o1 = new Settings();
var o2 = new Settings();
console.dir(o1);
console.dir(o2);
if (o1 === o2) {
console.log("Matched");
}
其他回答
我认为最干净的方法是:
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;
}
};
})();
之后,可以调用函数as
var test = SingletonFactory.getInstance();
简短的回答:
由于JavaScript的非阻塞特性,JavaScript中的单例在使用时非常难看。全局变量将在整个应用程序中为您提供一个实例,而不需要所有这些回调,模块模式将内部隐藏在接口后面。参见Christian C. Salvadó的回答。
但是,既然你想要一个单胞胎……
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 the singleton that the instance is ready to use
instanceReady(preparedObject);
}
var s = singleton(singletonInitializer);
// Get the instance and use it
s(function(instance) {
instance.doSomething();
});
解释:
单例在整个应用程序中提供了不止一个实例:它们的初始化延迟到第一次使用。当您处理初始化开销很大的对象时,这确实是一个大问题。昂贵通常意味着I/O,在JavaScript中I/O总是意味着回调。
不要相信那些给出instance = singleton.getInstance()这样接口的答案,它们都没有抓住重点。
如果它们没有在实例就绪时运行回调,那么当初始化器执行I/O时,它们将不起作用。
是的,回调看起来总是比函数调用更丑,函数调用会立即返回一个对象实例。但是同样:当你执行I/O时,回调是必须的。如果你不想做任何I/O,那么在程序启动时实例化就足够便宜了。
例1,简单的初始化器:
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.");
我从JavaScript模式中得到了这个例子 用编码和设计模式构建更好的应用程序一书(Stoyan Stefanov著)。如果你需要一些简单的实现类,比如单例对象,你可以使用一个直接函数,如下所示:
var ClassName;
(function() {
var instance;
ClassName = function ClassName() {
// If the private instance variable is already initialized, return a reference
if(instance) {
return instance;
}
// If the instance is not created, save a pointer of the original reference
// to the 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 singleton object using the new prototype property
ClassName.prototype.nothing = true;
var obj_1 = new ClassName();
// Extending the defined class like a singleton object using the new prototype property
ClassName.prototype.everything = true;
var obj_2 = new ClassName();
// Testing makes these two objects point to the same instance
console.log(obj_1 === obj_2); // Result is true, and it points to the same instance object
// All prototype properties 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 are defined inside of the constructor
console.log(obj_1.someProperty); // Outputs 0
console.log(obj_2.someProperty); // Outputs 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 demo.
我认为最简单的方法是声明一个简单的对象文字:
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 () {
}
};
})();
这被称为模块模式,它基本上允许您通过使用闭包来封装对象上的私有成员。
如果你想阻止对单例对象的修改,你可以使用ES5 object .freeze方法冻结它。
这将使对象不可变,防止对其结构和值进行任何修改。
如果你正在使用ES6,你可以很容易地使用ES模块来表示一个单例,你甚至可以通过在模块范围内声明变量来保持私有状态:
// my-singleton.js
const somePrivateState = []
function privateFn () {
// ...
}
export default {
method1() {
// ...
},
method2() {
// ...
}
}
然后你可以简单地导入单例对象来使用它:
import myInstance from './my-singleton.js'
// ...
在ES6中正确的方法是:
MyClass { 构造函数(){ if (MyClass._instance) { 抛出新的错误("单例类不能实例化超过一次。") } MyClass。_instance = this; / /……其余的构造函数代码在此之后 } } var instanceOne = new MyClass() //执行成功 var instanceTwo = new MyClass() //抛出错误
或者,如果你不想在创建第二个实例时抛出错误,你可以只返回最后一个实例,如下所示:
MyClass { 构造函数(){ if (MyClass._instance) { 返回MyClass._instance } MyClass。_instance = this; / /……其余的构造函数代码在此之后 } } var instanceOne = new MyClass() var instanceTwo = new MyClass() console.log(instanceOne === instanceTwo) //日志为true
推荐文章
- Babel 6改变了它导出默认值的方式
- 如何配置历史记录?
- ES6模板文字可以在运行时被替换(或重用)吗?
- [Vue警告]:找不到元素
- 可以在setInterval()内部调用clearInterval()吗?
- AngularJS控制器的生命周期是什么?
- 无法读取未定义的属性“msie”- jQuery工具
- 我的蛋蛋怎么不见了?
- JavaScript中的排列?
- JavaScript中有睡眠/暂停/等待功能吗?
- 如何禁用文本选择使用jQuery?
- 如何停止事件冒泡复选框点击
- 如何在PHP中截断字符串最接近于一定数量的字符?
- 向对象数组添加属性
- 如何在Redux应用程序中动态加载代码分割的减速器?