JS中“创建对象”及“通过原型创建对象”浅析

时间:2022-08-30 10:37:48
                                                   

JS中“创建对象”及“通过原型创建对象”浅析

这两天重新看着李炎恢的JavaScript视频学习了一遍,在看到第十五章 面向对象及原型 的时候,感觉必须要把所讲的知识点总结一下,以便今后查看:

(备注:查看该篇博客时,请结合视频及视频课件)

一、创建对象的几种方式


1、各自独立声明模式

[javascript] view plain copy
  1. var box1 = new Object();    //声明第一个对象并给各属性赋值  
  2. box1.name = 'Lee';  
  3. box1.age = 100;  
  4. box1.run = function () {  
  5.     return this.name + this.age + '运行中...';  
  6. };  
  7. alert(box.run());  
  8.   
  9. var box2 = new Object();    //声明第二个对象并给属性赋值  
  10. box2.name = 'Jack';  
  11. box2.age = 200;  
  12. box2.run = function () {  
  13.     return this.name + this.age + '运行中...';  
  14. };  
  15. alert(box2.run());  
此时box2和box1是相互独立的,不会混淆。
但是这样子每声明一个结构相同的对象,都得加一段代码,特别不方便,所以有了下面的工厂模式。

2、工厂模式

[javascript] view plain copy
  1. function createObject(name, age) { //集中实例化的函数  
  2.     var obj = new Object();  
  3.     obj.name = name;  
  4.     obj.age = age;  
  5.     obj.run = function () {  
  6.         return this.name + this.age + '运行中...';  
  7.     };  
  8.     return obj;  
  9. }  
这样子每次new对象时只需调用createObject()方法即可,同时,还可以传参初始化对象。
但由于new出来的对象都从属于Object,当我要使new出来的对象在本质做一些区分的时候,就不行了,此时就有了更简便的构造函数模式。

3、构造函数模式

[javascript] view plain copy
  1. function Box(name, age) {  
  2.     this.name = name;  
  3.     this.age = age;  
  4.     this.run = function () {  
  5.         return this.name + this.age + '运行中...';  
  6.     };  
  7. }  
  8. var box1 = new Box('Lee', 100);  
  9. var box2 = new Box('Jack', 200);  
  10. alert(box1.run());  
  11. alert(box1 instanceof Box); //很清晰的识别他从属于Box  
构造函数模式很大程度上解决了对象复用及传参初始化问题,是最常用的new对象的模式。
注意: 
此处box1.run != box2.run, 因为他们判断的是引用地址,由此可知,两个对象的方法分别存储在不同的地方,是两个不同的方法。
但 box1.run() == box2.run(),因为它们返回值一样。
当有了对象以后,自然而然就会涉及到对象属性及方法的共享问题(比如,在java中子类们共享父类的成员变量及方法)。
构造函数模式创建出的对象说白了还是相互独立的(比如上面的box1.run != box2.run),如果我要在两个对象之间共享某些属性的话,就必须要采用原型prototype了。

二、通过原型创建对象


1、原型模式

[javascript] view plain copy
  1. function Box() {} //声明一个构造函数  
  2. Box.prototype.name = 'Lee'//在原型里添加属性  
  3. Box.prototype.age = 100;  
  4. Box.prototype.arr = ['aaa','bbb'];  
  5. Box.prototype.run = function () { //在原型里添加方法  
  6.     return this.name + this.age + '运行中...';  
  7. };  
  8. var box1 = new Box();  
  9. var box2 = new Box();  
  10.   
  11. //修改普通属性,看下会不会影响prototype  
  12. alert(box1.name);   //Lee  
  13. box1.name = 'Rinima';  
  14. alert(box1.name);   //Rinima  
  15. alert(box2.name);   //Lee,修改box1的普通属性,相当于直接在box1中添加一个属性,是不会牵扯到原型中的属性的。  
  16.                     //这一点非常好。但是如果是引用对象(数组)的话,就有问题了。  
  17.   
  18. //修改引用属性,看下会不会影响prototype  
  19. alert(box1.arr);    //aaa,bbb  
  20. box1.arr.push('ccc');  
  21. alert(box1.arr);    //aaa,bbb,ccc  
  22. alert(box2.arr);    //aaa,bbb,ccc,修改box1的引用属性,却牵扯到了原型中的属性。  
  23.             //这是因为arr只是个引用地址,指向数组真实存储的位置,修改box1中的引用属性就是修改引用所指向的数组,所以原型也被修改了  
  24.             //这是我们不希望看到的!  
该模式的优点: 
1、所new出来的对象在prototype中的属性是共享的,此时box1.run == box2.run
2、能保证所new的对象实例的属性完全统一(和缺点2相对)
该模式的缺点: 
1、当含有引用类型的属性时,引用类型所指向的属性是存在prototype中,直接在对象中修改引用属性就把所有对象的引用属性都修改了
2、该种模式省略了初始化传参,使得new出来的所有属性都是一样的

1.1、原型字面量模式

(原型模式的另外一种模式,大体上区别不大,都属于原型模式)[javascript] view plain copy
  1. function Box() {};  
  2. Box.prototype = { //使用字面量的方式  
  3.     constructor : Box,  
  4.     name : 'Lee',  
  5.     age : 100,  
  6.     run : function () {  
  7.         return this.name + this.age + '运行中...';  
  8.     }  
  9. };  
该模式继承了原型模式的所有优缺点,但是也有自己特有的优缺点
优点: 更好的体现了封装性
缺点: constructor没有指向自身,必须手动强制指向

2、构造函数+原型模式

[javascript] view plain copy
  1. function Desk(name, age) { //不共享的使用构造函数  
  2.     this.name = name;  
  3.     this.age = age;  
  4.     this.family = ['aaa''bbb''ccc'];  
  5. };  
  6. Desk.prototype = { //共享的使用原型模式  
  7.     constructor : Desk,  
  8.     run : function () {  
  9.         return this.name + this.age + this.family;  
  10.     }  
  11. };  
  12.   
  13. var desk1 = new Desk('Lee',100);  
  14. var desk2 = new Desk('Jack',200);  
  15. alert(desk1.family);    //aaa,bbb,ccc  
  16. desk1.family.push('ddd');  
  17. alert(desk1.family);    //aaa,bbb,ccc,ddd  
  18. alert(desk2.family);    //aaa,bbb,ccc  
优点: 这种模式完美解决了共享属性与不共享属性的问题,而且可以精确控制,是非常不错的模式。
缺点: 但是此种办法,原型与构造函数相分离,让人感觉很怪异,体现不了封装。

3、动态原型模式

[javascript] view plain copy
  1. function Box(name ,age) { //将所有信息封装到函数体内  
  2.     this.name = name;       //不共享的属性  
  3.     this.age = age;  
  4.       
  5.     if (typeof this.arr != 'object') {      //共享的属性  
  6.         alert('在第一次调用的时候...arr');  
  7.         Box.prototype.arr = ['aaa','bbb'];  
  8.     }  
  9.     if (typeof this.run != 'function') {  
  10.         alert('在第一次调用的时候...run');  
  11.         Box.prototype.run = function () {  
  12.             return this.name + this.age + '运行中...';  
  13.         };  
  14.     }  
  15. }  
  16. var box1 = new Box();  
  17. var box2 = new Box();  
  18. alert( box1.arr );      //aaa,bbb  
  19. box1.arr.push('ccc');  
  20. alert( box1.arr );      //aaa,bbb,ccc  
  21. alert( box2.arr );      //aaa,bbb  

优点:这个模式继承了上一个模式(构造+原型)的所有优点,同时也完美解决了封装性问题
注意:此种模式书写原型时不可用字面量模式,会切断实例和新原型之间的联系的。???

基本到这里,以上两种模式已经能处理大多数问题,而且是比较好的模式了。
但是,还是有一些比较特定的需求,需要用到以下两种比较另类的模式。


4、寄生构造函数模式(工厂模式+构造函数模式)

[javascript] view plain copy
  1. function Box(name, age) {  
  2.     var obj = new Object();  
  3.     obj.name = name;  
  4.     obj.age = age;  
  5.     obj.run = function () {  
  6.         return this.name + this.age + '运行中...';  
  7.     };  
  8.     return obj;  
  9. }  


5、稳妥构造函数模式

[javascript] view plain copy
  1. function Box(name , age) {  
  2.     var obj = new Object();  
  3.     obj.run = function () {  
  4.         return name + age + '运行中...'//直接打印参数即可  
  5.     };  
  6.     return obj;  
  7. }  
稳妥构造函数模式,是当构造函数里不允许使用this,外部实例化时不准用new时,才需要用到的模式。
我也不知道为啥会出现这样那么奇葩的需求。不过这么多的创建对象模式,确实比Java眼花缭乱多了。