Una

接下来的日子,陆续把未总结的问题补充起来......

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

一、为什么要用原型模式。

    早期采用工厂模式或构造函数模式的缺点:

     1.工厂模式:函数creatPerson根据接受的参数来构建一个包含所有必要信息的person对象,这个函数可以被无数次的调用,工厂模式尽管解决了创建多个相似对象的问题,却没有解决对象识别的问题(返回的是自定义的一个对象o,不明确对象o的类型)。

 

     2. 构造函数模式:构造函数的调用和其他oo语言一样,用new操作符来构建一个Person的实例;javascript中的构造函数也是函数(所以也可以直接像普通函数那样直接调用方法名)只不过可以用来创建对象,这是和其他oo语言不一样的地方(其他oo语言的构造函数不是函数,不能直接调用方法名,必须用new操作符来创建对象才可以),相同点是构造函数首字母都要大写,非构造函数首字母都是小写。

      构造函数模式解决了创建多个相似对象的问题和对象识别的问题,但是不足的地方是,采用这种模式会创建多个完成同样任务的Function实例。

     拿上面的例子来分析内存:

    此时person1.isName===person2.isName 是为false的

   不同实例上的同名函数是不相等的,我们希望有一种方法,即可以解决创建多个相似对象的问题和对象识别的问题,还能让每个实例共享相同的方法。

 

   上面的例子可以做改进将方法提到构造函数的外面,单独写成一个全局函数,this.isName指向一个指针:

    不同的实例是共享了方法,但是如果有很多个方法,有几个我们就要定义几个全局函数,这样我们自定义的引用类型就失去了封装性,为了解决这一系列的问题,所以才有了原型模式。

 

二、理解原型模式。

   1.理解原型模式先理解原型对象:  

       我们创建的每一个函数(javascript中函数也是一个对象)都有一个原型属性(prototype),原型属性实质上是一个指针,它指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法(通俗的说就是这个特定类型的所有实例都可以共享原型对象包含的属性和方法)。

 

  2.原型对象的两种赋值方法:

   第一种:

此时实例对象person1和person2的属性和方法都是用的原型对象共享的,所以上面的例子输出的结果为:

(可以看到person1和person2共享一个sayName方法,它们的方法是相同的)

 

 第二种:

      和创建一个引用对象一样,也可以采用字面量的形式给原型对象赋值。最终结果和上面的结果相同,但是一个例外就是constructor属性不再指向Person了,因为字面量的方式重写了原型对象,此时的contructor指向的是Object对象(这里不懂看后面的内存分析了解原理)。

 

      但是我们可以指明constructor指向Person:

 

3.原型对象的内存分析:

    引用了一下《javascript高级程序第三版》中的内存分析图。

    Person构造函数、Person原型对象、Person现有的两个实例之间的关系:

   

      每一个函树都会有一个原型属性(prototype),它是一个指针,指向原型对象;默认情况下,原型对象会包含一个constructor(构造函数)属性(原型最初只包含constructor属性),这个属性包含一个指向prototype属性的指针;拿上面的图做例子,Person.prototype.constructor指向Person,通过这个构造函数就可以继续为原型对象添加其他的属性和方法。

     当调用构造函数创建一个新实例后,该实例内部会包含一个内部属性(指针),它指向构造函数的原型对象;这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间;也就是说这个内部属性和构造函数没有直接的关系。

 

4.原型对象中的值不能被对象实例重写:

   实例:

   

 

 测试结果:

 

      可以看到原型对象中的name没有被改变,person1.name来自实例,person2.name来自原型。

     在原型模式中当通过person1.name读取属性值时,首先会去实例上查找有没有名称为name的属性,有的话就不会再去原型对象上查找;如果实例上没有,则就会去原型上搜索。

     也就是说当我们给实例上添加了一个属性,这个属性就会阻止我们去原型上访问这个同名属性,但是不会修改那个属性。

 

     内存分析,图中省略了与Person构造函数之间的关系:

     

        通过delete操作符可以直接删除实例中的属性:delete person1.name。

 

 5.原型的动态性:

       第4点说了不能通过对象实例来修改(重写)原型对象中的值,不是说原型对象中的值不能被修改,通过如下方式仍然可以修改:

 

 

测试结果如下:

 

     由于在原型中查找值的过程是一次搜索,因此在对原型对象做的任何修改都会立即从实例上反映出来,即使是先创建实例后修改原型也如此。

实例:

测试结果:

 

内存分析:

     

      ( friend改为person5,function中省略了内容)

 

     但是如果在先创建实例后修改原型的情况下,用字面量赋值的方式来重写原型对象,这就会切断现有原型与任何之前存在的对象实例之间的联系(不是先创建实例后修改原型的情况下仍然可以用这种方式重写原型对象)。

  实例:

 

 测试结果:

 

 

内存分析:

 

      (图引用《JavaScript高级程序设计第3版》,图中值未改,原理一样)

 

 6.原生对象的模型:

     所有原生引用类型(Object、Array、String等)都在其构造函数的原型上定义了方法。例如:Array.prototype.sort(),String.prototype.substring等,通过原生对象的模型不仅可以读取到所有的默认方法的引用,还可以像修改自定义对象的原型一样修改原生对象的原型,所以也可以给原生对象添加方法,但是在实际运用中我们不要这样去修改原生对象的模型(命名冲突,重写原生方法等等问题都是我们在实际开发中所不希望遇到的)。

 

7.关于原型对象的几种操作:

 1) isPrototypeOf检测实例对象是否包含指向某个原型对象的指针,包含则返回true,否则false。

 

2) Object.getPrototypeOf(实例对象) ,返回的就是这个对象的原型,下面的结果为true。

Object.getPrototypeOf(person1).name通过这种方式可以访问到原型对象中的name值。

 

3)hasOwnProperty(从object中继承而来)检测一个属性是否存在于实例中,是则返回true,否则返回false。

   

 用in也可以检测属性值,只不过用这种方式检测的无论属性是在实例中还是原型中都会返回true。关于检测属性我的另一篇文章中有:Javascript之数据检测

 

4)Object.keys(原型对象)返回一个包含所有可枚举属性的字符串数组:

测试结果:

 

8.原型对象的缺点:

       原型对象的好处是原型中的所有属性和方法可以被很多实例共享,缺点是当原型中包含引用类型的值的属性时,一个实例对象对这个引用类型的属性做了修改,在其他实例对象中也可以体现出来。

      

 

9.好的实践:

   1)组合使用构造函数模式和原型模式:

2)动态原型模式:

 

   除了上面两种模式以外还有寄生构造函数模式和稳妥构造函数模式这里就不总结了,没有用到过没有什么体会,以上只是我看《JavaScript高级程序第三版》的学习笔记,用自己的理解的方式归纳整理了一下。

 

posted on 2016-08-09 21:34  youyi2016  阅读(1675)  评论(0编辑  收藏  举报