JavaScript面向对象设计—对象继承

24 December 2015

面向对象 js 继承

接着上一篇对象的创建,这一篇主要写javaScript的继承方式。 许多语言的继承分为2种:接口继承和实现继承。 js没有函数签名,所以无法使用接口继承。js的实现继承是依靠原型链实现的。

1.原型链继承

基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。

  function SuperType{
    this.property = true
  }
  SuperType.prototype.getSuperValue = function(){
    return this.property
  }
  function SubType(){
    this.subproperty = false
  }
  SubType.prototype = new SuperType()
  SubType.prototype.getSubTypeValue = function(){
  	return this.subproperty
  }  
  var subtype = new SubType()
  console.log(subtype.getSuperValue()) //true

分析他们的关系:

  SubType[prototype] ---->
                         |--->SubType Prototype
  subtype[[prototype]]--->  [[prototype]]
                                |
                                |
                                v
  SuperType[prototype]------->SuperType Prototype
        ^                   [constructor]
        |                       |
        ------------------------|

SubType有一个原型对象SubType Prototype , 实例subtype也有一个指针指向原型对象SubType Prototype ;原型对象 SubType Prototype 又是 SuperType 的实例,所以又有一个指针指向 SuperType 的原型对象 SuperType Prototype , SuperType Prototype 的构造函数指针指向了SuperType.

总结:所有的函数的默认原型都是Object实例,因此默认的原型都包含了一个内部指针,指向Object.prototype. 通过原型链实现继承时,不能用对象字面量创建原型方法,这样会重新原型链。 存在的问题:引用类型值的原型属性会被所有实例所共享;创建子类型的实例时,不能向超类型的构造函数传递参数。

2.借用构造函数

基本思想:在子类构造函数的内部调用超类的构造函数。通过 apply() 和 call()实现。 两者的格式和参数定义:

  call( thisArg [,arg1,arg2, ] )// 参数列表,arg1,arg2,...
  apply(thisArg [,argArray] )// 参数数组,argArray

上面两个函数内部的this指针,都会被赋值为thisArg,这可实现将函数作为另外一个对象的方法运行的目的.

案例:

  function SuperType(){
    this.colors = ['red','blue','green']
  }
  function SubType(){
    SuerType.call(this)
  }
  var subtype1 = new SubType()
  subtype1.colors.push('black')
  console.log(subtype1.colors) // 'red,blue,green,black'
  
  var subtype2 = new SubType()
  console.log(subtype2.colors) // 'red,blue,green' 

2.1 传递参数

  function SuperType(name){
    this.name = name
  }
  function SubType(){
    SuerType.call(this ,'Jin')
    this.age = 24
  }
  var subtype = new SubType()
  console.log(subtype.name ,subtype.age) // 'Jin',24

总结:相对于原型链而言,借用构造函数最大的优势在于,子类构造函数可以向超类构造函数中传递参数; 不足:方法都必须在构造函数中定义,函数无法复用;超类中定义的方法对于子类型而已不可见,所有的子类型都只能使用构造函数模式。

3.组合继承

指的是将原型链和借用构造函数组合到一起,发挥二者之长的继承。 基本思想:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

  function SuperType(name){
    this.name = name
    this.colors = ['red','blue','green']
  }
  SuperType.prototype.getName = function(){
    return this.name
  }
  function SubType(name , age){
    SuperType.call(this,name)
    this.age = age
  }
  SubType.prototype = new SuperType()
  SubType.prototype.constructor = SubType
  SubType.prototype.getAge = function(){
    return this.age
  }
  
  var subtype1 = new SubType('Jin',24)
  subtype1.colors.push('black')
  console.log(subtype1.colors)//'red,blue,green,black'
  subtype1.getName() //'Jin'
  subtype1.getAge() //24
  
  var subtype2 = new SubType('Soul',24)
  console.log(subtype2.colors)//'red,blue,green'
  subtype2.getName() //'Soul'
  subtype2.getAge() //24

总结:避免了原型链和借用构造函数的缺陷,是JavaScript最常用的继承方式。 缺点:无论什么情况,都会调用2次超类的构造函数。一次是:创造子类型原型时,另一次是:在子类型构造函数内部。 instanceofisPrototypeOf()都有效。

instanceofisPrototypeOf() 都是用来确定对象之间是否存在关系。使用方式:

 obj //实例
 Object //构造函数
 obj instanceof Object
 Object.isPrototypeOf(obj)

4.原型式继承

基本思想:借助原型可以基于已有对象创建新对象,同时还不必创建自定义类型。 —道格拉斯.克罗克福德

函数如下:

  function object(o){
    function F(){}
    F.prototype = o
    return new F()
  }

在object()函数的内部创建一个临时的构造函数,然后将传人的对象作为这个构造函数的原型,最后返回这个临时类型的实例。 object()对传进来的对象执行一次浅复制。

4.1 浅复制与深复制

浅复制:将一个对象或数组赋予其他变量,那么我们只要更改其中的任何一个,然后其他的也会跟着改变。

  var a = {name:'Jin'}
  var b = a
  b.name = 'Soul'
  console.log(a.name,b.name) //'Soul','Soul'

这是因为他们引用的都是同一个对象。

深复制:就是把对象的属性遍历一遍,赋给一个新的对象。

  var a = {name:'Jin'}
  var b = {name : a.name}
  b.name = 'Soul'
  console.log(a.name,b.name) //'Jin','Soul'

总之:浅拷贝就是两个变量使用同一对象,深拷贝就是两个不同对象

回归正题,看案例:

  function object(o){
    function F(){}
    F.prototype = o
    return new F()
  }
  var person = {
    name : 'Jin',
    friends : ['aaa','bbb','ccc']
  }
  var aperson = object(person)
  aperson.name = 'Soul'
  aperson.friends.push('ddd')
  
  var bperson = object(person)
  bperson.name = 'Yim'
  bperson.friends.push('eee')
  
  console.log(person.friends)//'aaa,bbb,ccc,ddd,eee'

也可以使用 Object.create(person) 代替 object(person)

Object.create() 支持情况 : IE 9+ ,Firefox 4+ ,Safari 5+ , Opera 12+ , chrome

总结:在没有必要兴师动众的创建构造函数,而只是想让一个对象与另一个对象保持类似的情况下,就可以使用原型式继承。 缺点:包含共享属性。

5.寄生式继承

寄生式继承和原型式继承都是由道格拉斯-克罗克福德推广的。基本思想就是创建一个仅用于封装继承过程的函数, 该函数在内部以某种方式增强对象,最后再像真的是对它做了所有工作一样返回对象。

  function object(o){
    function F(){}
    F.prototype = o
    return new F()
  }
  function create(obj){
   var clone = object(obj)
   clone.sayHi = function(){
     console.log('Hi')
   }
   return clone 
  }
  var person = {
    name : 'Jin',
    friends : ['aaa','bbb','ccc']
  }
  var aperson = create(person)
  aperson.sayHi()//'Hi'

总结:在主要考虑对象而不是自定义类型和构造函数的情况下可以考虑使用。

6.寄生组合式继承

即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。基本思想: 不必为了指定子类型的原型而调用超类型的构造函数,我们需要的只是超类型原型的一个副本而已。 本质上,就是使用寄生式的继承来继承超类的原型,而后再将结果指定给予子类型的原型。

  function object(o){
    function F(){}
    F.prototype = o
    return new F()
  }
  function inheritPrototype(subtype,supertype){
    var prototype = object(supertype.prototype)
    prototype.constructor = subtype
    subtype.prototype = prototype
  }
  function SuperType(name){
    this.name = name
    this.colors = ['red','blue']
  }
  SuperType.prototype.getName = function(){
  	return this.name
  }
  function SubType(name,age){
    SuperType.call(this,name)
    this.age = age 
  }
  inheritPrototype(SubType , SuperType)
  SubType.prototype.getAge = function(){
    return this.age
  }

最理想的引用类型继承模式。