7

我遇到了一种相当有趣的方法来创建一个可以用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实例化一个 new 时,都this指的是那个新的新对象——但是由于构造函数返回了一个完全独立的对象,我认为this它只会被丢弃。但它徘徊。如何?为什么?

这里有一些我遗漏的小细节。任何人都可以照亮它吗?

编辑:原来这段代码很糟糕。它“神奇地”似乎持有对实例的引用的原因是因为它实际上默默地将它存储在全局对象中。这充其量是不好的做法,而且无疑容易出错。

4

5 回答 5

3

不要被this函数内部弄糊涂了getInstance,那this就是全局对象window,所以你是在创建一个对象并赋值给窗口对象,下次调用构造函数时,你是在检查是否window.instance存在。

代码this.instance = null;毫无意义,只会让您感到困惑。删除它不会改变任何东西。

以下来自MDN

执行代码时new foo(...),会发生以下情况:

  1. 创建了一个新对象,继承自 foo.prototype。
  2. 构造函数 foo 使用指定的参数调用,并且 this 绑定到新创建的对象。new foo 等价于 new foo(),即如果没有指定参数列表,则调用不带参数的 foo。
  3. 构造函数返回的对象成为整个 new 表达式的结果。如果构造函数没有显式返回对象,则使用在步骤 1 中创建的对象。(通常构造函数不返回值,但如果他们想覆盖正常的对象创建过程,他们可以选择这样做。)

注意第3步,当你在构造函数中有return语句时,返回的结果将是new表达式的结果。

于 2012-10-02T02:27:17.760 回答
2

当构造函数调用getInstance()时,里面的this指针getInstance()被设置为window所以this.instance里面是一个全局变量window.instance

这段代码只是使用一个全局变量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());
        }
    }
于 2012-10-02T02:26:00.797 回答
1

它使用window-与getInstance' s 不同:thisSingletonClass

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

在 JavaScript 中实现单例模式的更好方法可能是:

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可能会中断 - 例如:

var x = new Singleton();
// Har, har, we be breaking things!
Singleton.instance = {"not": "the", "same": "at", "all": true};
var y = new Singleton();

console.log(x === y); // false
于 2012-10-02T02:26:50.583 回答
1

this函数中的关键字getInstance表示一个Window对象。在SingletonClass函数中,它表示一个SingletonClass对象。

对于词法作用域,addandlist函数总是引用同一个things数组。

于 2012-10-02T02:55:07.730 回答
0

这似乎是单身人士的一个很好的链接:

在 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

于 2012-10-02T03:12:17.493 回答