在自己研究javascript各种设计模式的过程中,偶然写出的一段代码让自己理解的更深刻了,之所以称之为伪单例模式,是因为这段代码造成的结果很想单例模式,但是实际上是活动对象捣乱所造成的误会。
代码很简单是这样的:
function Person(){ var money = 0; Person.prototype.getMoney = function (){ return money; } Person.prototype.addMoney = function (m){ money += m; } } var a = new Person(); var b = new Person(); a.addMoney(20); console.log(a.getMoney());//打印20 console.log(b.getMoney());//打印20
这里有些同学可能会感觉很奇怪了,a和b完全是两个不同的对象,每次new的时候都会把私有变量money初始化成0,为什么a对象使用addMoney()方法后,同时b对象的money也变成20了呢?
这个时候会造成一种假象,就是通过new实例化多个对象的结果都是产生了同一个对象,仿佛就像单例模式那样。
如果你对这个结果感到疑惑就接着看下吧,如果你一眼就看到其中的原因,那你就不用在这里浪费时间咯~
我们从这个构造函数说起,这里我我使用了var来创造一个私有变量money,并在这个构造函数中提供了两个匿名函数给构造函数的原型对象的getMoney和addMoney,即通过闭包的方式来提供对money变量的访问权限,如果你把原型对象的写在外面是访问不到money这个私有变量的。
事实上恰恰因为我这个行为导致了伪单例的发生,我们接下来用图来剖析一下当我们new对象的时候,函数的活动对象发生了怎样的变化。
如果对函数的活动对象、执行环境不太理解,给个传送门http://www.docin.com/p-509501990.html,讲解的十分清楚。
当我们var a = new Person()后,情况是这样的:
实例对象a通过原型链可以调用原型对象中的两个属性指向那两个匿名函数(迷之圆圈),他们在闭包的帮助下访问活动对象一的money属性。此时money属性初始化为0。
当我们var b = new Person()后,情况变成了这样:
现在可以很清楚的看到,真正改变的是原型对象中属性所指向的匿名函数,也就是说现在实例a和实例b通过原型中的访问所访问的money变量都是位于活动对象2中的,活动对象1已经被玩坏后抛弃了。
再看下一句的a.addMoney(20)实际上就是将活动对象2中的money变成了20,这就很明白了,最后他俩getMoney访问到的都是活动对象2里面的money,自然都会返回20了。则就造成了伪单例的效果。
总结要注意的几点:
1,要达成伪单例的效果,必须将构造函数中的变量设为私有变量而不是通过this设置为实力属性,因为那样访问的就不是通过闭包了。
2,给原型对象添加在构造函数内部才能访问私有变量,不明白?还不去好好看闭包!
3,最后所有的对象访问的都是最后new时,构造函数所形成的活动对象,对此有疑问的同学可以交换一下代码执行的顺序,比如改成这样:
var a = new Person(); a.addMoney(20);//写在前面 var b = new Person(); console.log(a.getMoney());//返回0 console.log(b.getMoney());//返回0
这是很自然的,因为被你你添加的20的那个money在活动对象一中,当你创造活动对象2时,实例对象a已经移情别恋了,a和b现在都指向活动对象2了,其中money被初始化成0,不懂得同学多看看图哟。