我遇到了这种相当有趣的方式来创建一个可以使用new
关键字实例化的JavaScript单例,例如var x = new SingletonClass()
。我对变量范围和闭包等有很好的把握,但是我很难理解为什么这段代码会像它那样工作。
// EDIT: DO NOT USE this code; see the answers below
function SingletonClass() {
this.instance = null;
var things = [];
function getInstance() {
if (!this.instance) {
this.instance = {
add: function(thing) {
things.push(thing);
},
list: function() {
console.log(things.toString());
}
};
}
return this.instance;
}
return getInstance();
}
var obj1 = new SingletonClass();
obj1.add("apple");
obj1.list(); //"apple"
var obj2 = new SingletonClass();
obj2.add("banana");
obj1.list(); //"apple,banana"
obj2.list(); //"apple,banana"
obj1.add("carrot");
obj1.list(); //"apple,banana,carrot"
obj2.list(); //"apple,banana,carrot"
我的直觉说每次实例化新的SingletonClass
时,this
都会引用那个新的新对象 - 但是由于构造函数返回了一个完全独立的对象,我想this
1}}会被丢弃。但它仍然存在。怎么样?为什么呢?
这里有一些小小的细节,我很想念。任何人都能发光吗?
编辑:原来这个代码很糟糕。它“神奇地”似乎持有对实例的引用的原因是因为它实际上是静默地将它存储在全局对象中。这充其量是不好的做法,毫无疑问容易出错。
答案 0 :(得分:3)
不要被函数this
内的getInstance
混淆,this
是全局对象window
,因此您正在创建一个对象并分配给该窗口对象,下次调用构造函数时,您正在检查window.instance
是否存在。
代码this.instance = null;
毫无意义,只会让您感到困惑。删除它不会改变任何东西。
以下内容来自MDN。
执行代码
new foo(...)
时,会发生以下情况:
- 创建一个新对象,继承自foo.prototype。
- 使用指定的参数调用构造函数foo 这绑定到新创建的对象。新foo相当于
new foo()
,即如果没有指定参数列表,则调用foo 没有参数。- 构造函数返回的对象成为结果 整个新表达式。如果构造函数没有 显式返回一个对象,使用在步骤1中创建的对象 代替。 (通常构造函数不返回值,但它们可以 如果他们想要覆盖正常的对象创建,请选择这样做 过程。)
醇>
注意步骤3,当你在构造函数中有return语句时,返回的结果将是新表达式的结果。
答案 1 :(得分:2)
当构造函数调用getInstance()
时,this
内的getInstance()
指针设置为window
,因此this.instance
内的window.instance
是全局变量{{1} }}。
此代码仅使用全局变量window.instance
来跟踪一个实例,并且可以比它更简单。
实现这一点的更简洁的方法就是这样。一个全局实例存储为函数本身的属性,而不是存储在顶级全局变量中。
function SingletonClass() {
var things = [];
// if there is a previous instance, return it
if (SingletonClass.inst) {
return SingletonClass.inst;
}
// if not called with 'new', force it
if (!this instanceof SingletonClass) {
return new SingletonClass();
}
// remember the first created instance
SingletonClass.inst = this;
// add methods that can see our private `things` variable
this.add = function(thing) {
things.push(thing);
}
this.list = function() {
console.log(things.toString());
}
}
答案 2 :(得分:1)
正在使用window
- getInstance
的{{1}}与this
不同:
SingletonClass
在JavaScript中实现Singleton模式的更好方法可能是:
function Singleton() {
console.log("This is:", this);
this.instance = null;
function _get() {
console.log("_get's this is:", this);
if (!this.instance) {
console.log("Instance is null");
this.instance = { test: 1 };
}
return this.instance;
}
return _get();
}
var x = new Singleton();
// Output is:
This is:
Singleton
_get's this is:
Window
Instance is null
这仍然受到限制。例如,如果有人用另一个对象替换function Singleton() {
// Handle not being called with `new`
if (!this instanceof Singleton) {
return new Singleton();
}
var instantiating = !!Singleton.instance,
things = [];
Singleton.instance = instantiating ? this : Singleton.instance;
if (instantiating) {
this.list = function() {
console.log(things.toString());
}
this.add = function(item) {
things.push(item);
}
}
return Singleton.instance;
}
,那么Singleton.instance
的不同实例之间的测试可能会中断 - 例如:
Singleton
答案 3 :(得分:1)
this
函数中的关键字getInstance
表示Window
个对象。在SingletonClass
函数中,它表示SingletonClass
对象。
对于词法范围,add
和list
函数始终引用相同的things
数组。
答案 4 :(得分:0)
这似乎是单身人士的一个很好的联系:
Simplest/Cleanest way to implement singleton in JavaScript?
以下是与上述内容有关的示例:
var myInstance = {
method1: function () {
return "Apple";
},
method2: function () {
return "Orange";
},
banana: "Banana"
};
myInstance.grape = "Grape";
console.log(myInstance.method1(), myInstance.method2(), myInstance.banana, myInstance.grape);
这是工作链接:
http://www.quirkscode.com/flat/forumPosts/singleton/singleton.html