ES5-对象建立与继承----《JavaScript高程3》

建立对象的几种方式

在逻辑上从低级到高级:工厂模式、构造函数模式、原型模式、组合模式。固然还有其余模式,可是这四者逻辑关系强,总结起来颇有感受。之因此和继承一块儿分析,也是由于逻辑关系很清晰:原型模式对应原型链继承,构造函数模式对应借用构造函数模式继承,组合模式对应组合继承。逻辑上按照“哪一个模式有什么缺点,为何有这个缺点,咱们怎么解决这个缺点”逐步分析,这样分析完后就会豁然开朗。javascript

1)工厂模式

使用函数建立对象,该函数来封装建立对象的细节。java

function createObject(para1,para2,para3){
                  var o = new Object();//显式建立对象
                  o.colors = [para1, para2, para3];
                  o.sayColors = function() {
                    console.log(this.colors[0]);
                  }
                  return o;//返回对象
                }
                var ob1 = createObject("red", "green", "blue");
                console.log(ob1);
                console.log(ob1.colors);
                ob1.sayColors();
                console.log(ob1 instanceof createObject);//false,没法判断对象的类型


缺点:没法判断对象类型。

2)构造函数模式

经过构造函数建立的实例被标识为一种特定的类型,能够经过instanceof 判断对象类型。数组

function createObject(para1, para2, para3) {
              //经过this完成函数属性定义,不用显示建立对象,所以也不用返回对象
              this.colors = [
                para1,
                para2,
                para3
              ];
              this.sayColors = function () {
                console.log(this.colors[0]);
              }
            }
            var ob1 = new createObject('red', 'green', 'blue');
            console.log(ob1);
            console.log(ob1.colors);
            ob1.sayColors();
            console.log(ob1 instanceof createObject); //true,能够判断对象的类型

缺点:经过构造函数建立的实例具备不一样的方法和属性,属性能够不一样,这对实例来讲是好的,咱们但愿实例属性独立。可是方法即函数,函数即对象,也就是说建立了太多重复的对象。函数

var ob1 = new createObject("red", "green", "blue");
  var ob2 = new createObject("red", "green", "blue");
  alert(ob1.sayColors == ob2.sayColors);//false,不一样实例具备不一样的方法

解决方式:把方法的定义放到构造函数外部,即在构造函数内部引用外部全局函数。这样就能够一次定义,屡次引用。可是当外部全局函数增多时,明显下降了封装性,《JavaScript精粹》上提到,全局对象是EScript的一大败笔。this

<script type="text/javascript">
      function createObject(para1,para2,para3){
       //经过this完成函数属性定义,不用显示建立对象,所以也不用返回对象
        this.colors = [para1, para2, para3];
        this.sayColors=sayColors;
      }
      function sayColors(){
          alert(this.colors[0]);
        }
      var ob1 = new createObject("red", "green", "blue");
      var ob2 = new createObject("red", "green", "blue");
      alert(ob1.sayColors == ob2.sayColors);//true
      alert(ob1.colors == ob2.colors);//false ,在构造函数中建立的引用类型属性是不一样的
</script>

值得一提的是,构造函数建立的实例中的引用类型属性是很特殊的,这一点会随后提到。prototype

3) 原型模式

:每个函数都有一个prototype属性,这个属性指向经过构造函数建立的实例对象的原型对象。原型对象的方法和属性能够被它的全部实例共享。所以,经过把属性和方法添加到实例的原型对象上,能够实现属性和方法共享。code

<script type="text/javascript">
      function createObject(){
  }
    createObject.prototype.colors = ["red", "green", "blue"];
    createObject.prototype.sayColors = function(){
    alert(this.colors[0]);
}

  var ob1 = new createObject();
  var ob2 = new createObject();
  alert(ob1.sayColors == ob2.sayColors);//true,经过原型建立的属性和方法共享
  alert(ob1.colors == ob2.colors);//true
</script>

缺点:“成也萧何,败也萧何”,原型模式的缺点就在于过强的共享能力,方法的共享能够减小多余的对象实例建立。可是属性共享致使实例难以拥有本身独特属性。固然,若是是一些不会修改的属性值,共享也就罢了;可是若是是须要修改的属性,而且该属性值是引用类型(基本类型属性值能够在实例中定义,会覆盖掉原型属性,可是不会修改原型属性,其余的实例访问该属性依旧对应原型属性),那么实例对这个属性值的修改就会在原型中反映出来,这其实就是修改了原型。糟糕的是其余实例中的该属性也同步变化,而后就会出现奇怪的问题。对象

<script type="text/javascript">
  function createObject(){
  }
    createObject.prototype.colors = ["red", "green", "blue"];
    createObject.prototype.sayColors = function(){
      alert(this.colors[0]);
    }

  var ob1 = new createObject();
  var ob2 = new createObject();
  ob1.colors.push("yellow");
  alert(ob1.colors);//red,green,blue,yellow
  alert(ob2.colors);//red,green,blue,yellow,经过ob1作的改变在ob2反映出来
</script>

4) 组合模式(最经常使用的一种对象建立方式,兼顾优势,避免缺点)

:使用构造函数模式定义各个实例属性,使用原型模式定义方法和共享的属性。继承

<script type="text/javascript">
  function createObject(){
            this.colors = ["red", "green", "blue"];
  }
    createObject.prototype.sayColors = function(){
      alert(this.colors[0]);
    }

  var ob1 = new createObject();
  var ob2 = new createObject();
  alert(ob1.sayColors == ob2.sayColors);//true,经过原型建立的方法共享
  alert(ob1.colors == ob2.colors);//flase,构造函数建立的引用类型属性不共享
</script>

补充关于对象的几个方法:
isPrototypeOf(): 肯定一个对象是不是另外一个对象的原型,只要是原型链中出现的原型,就返回true,使用方法:ip

alert(createObject.prototype.isPrototypeOf(ob1));//rue

instanceof操做符:检测是不是某一构造函数的实例,前面的参数是实例, 后面的的参数是构造函数名,只要是原型链中出现的构造函数就返回true。

alert(ob1 instanceof createObject);//true

hasOwnProperty(): 检测一个实例是否拥有某个属性,使用方法

alert(ob1.hasOwnProperty("colors"));//true

in操做符:单独使用时能够检查实例属性或者原型属性是否存在,in后跟的通常是实例,所以能够理解为检查实例以及实例对应的原型中是否有这个属性或非法。使用方法:

alert("sayColors" in ob1);//true
  alert("sayColors" in createObject);//false,直接对原型检查没意义

for in 的另外一种用法,相似于数组的forEach()方法,是一个循环,返回实例或原型中的可枚举的属性

<script type="text/javascript">
  function createObject(){
            this.colors = ["red", "green", "blue"];
  }
    createObject.prototype.sayColors = function(){
      alert(this.colors[0]);
    }

   var ob1 = new createObject();
    for (var prop in ob1) {
         alert(prop); //前后弹出colors   sayColors
       }   
</script>

继承的实现:

1.原型链

:把超类型的实例复制给子类型的原型,这样超类型的方法和属性就由子类型继承。

<script type="text/javascript">
  //组合模式定义超类型对象
  function SuperType(){
    this.property = true;
  }
  SuperType.prototype.getSuperValue = function(){
    return this.property;
  };
  function SubType(){
    this.subproperty = false;
  }
  //经过建立超类型对象实例而且赋值给子类型原型实现继承
  SubType.prototype = new SuperType();
  var instance = new SubType();
  //true,子类型访问超类型的方法,实现了继承
  alert(instance.getSuperValue()); 
</script>

问题:子类型原型会继承超类型实例的属性,若是这个属性值是引用类型,就会致使子类型的全部实例都共享了这个属性,致使不一样实例属性的耦合,这是原型模式建立对象的固有问题。而且,在建立子类型的实例时,没法向超类型的构造函数传递参数。所以,实际中不多单独使用原型链。

2 借用构造函数(经典继承)

在子类型构造函数的内部调用超类型构造函数。这个方法之因此被称为借用构造函数,我以为就是由于这种方法和前面介绍的经过构造函数建立实例的私有属性是同样的道理,只不过是在子类型构造函数内部调用了超类型构造函数。

<script type="text/javascript">
  function SuperType(){
    this.colors = ["red", "blue", "green"];
  }
  //借用构造函数,在子类型构造函数的内部调用超类型构造函数
  function SubType(){
    SuperType.call(this);
  }
  //每次调用产生的实例具备不一样的引用类型属性
  var instance1 = new SubType();
  alert(instance1.colors);//red,blue,green
  instance1.colors.push("yellow");
  alert(instance1.colors);//red,blue,green,yellow

  var instance2 = new SubType();
  alert(instance2.colors);//red,blue,green,从原型继承的实例属性是私有的
  alert(ob1.colors == ob2.colors);//false,属性私有
  alert(ob1.sayColors == ob2.sayColors);//false,方法也是私有
</script>

并且能够在建立子类型实例时向超类型传递参数,由于这就至关于调用了一个函数,函数固然是能够有参数的。
缺点是一样具备构造函数模式建立对象的固有弊端:构造函数中烦人方法(函数对象)重复建立。而且,只有在超类型构造函数中定义的属性和方法才会被继承,经过超类型原型定义的方法和属性对子类型不可见,这是由于只执行了构造函数内部的语句。所以实际中这个方法也不多单独使用。

<script type="text/javascript">
  function SuperType(){
    this.colors = ["red", "blue", "green"];

  }
  //超类型的原型上建立方法
  SuperType.prototype.sayColors = function(){
      alert(this.colors[0]);
      return 0;
    }
  //借用构造函数,在子类型构造函数的内部调用超类型构造函数
  function SubType(){
    SuperType.call(this);
  }

  var ob1 = new SubType();
 var ob2 = new SuperType();

  alert(ob1.sayColors);//undefined,子类型不具备这个方法
  ob2.sayColors();//red,超类型具备这个方法
</script>

3 组合继承(伪经典模式继承)

将原型链继承和借用构造函数模式组合到一块儿,使用原型链实现对原型属性和方法的继承(实现了方法复用),可是经过借用构造函数实现对实例属性的继承(实现了实例属性私有)。避免了缺陷,融合了优势,是最经常使用的继承模式,并且,instanceof操做符和isPrototypeOf()都适用于组合继承建立的对象。

<script type="text/javascript">
  function SuperType(){
    this.colors = ["red", "blue", "green"];

  }
  //超类型的原型上建立方法
  SuperType.prototype.sayColors = function(){
      alert(this.colors[0]);
      return 0;
    }
  //借用构造函数,在子类型构造函数的内部调用超类型构造函数,实现属性继承
  function SubType(){
    SuperType.call(this);
  }
  //原型链实现方法继承
  SubType.prototype = new SuperType();

  var ob1 = new SubType();
  ob1.sayColors();//red//子类型继承了超类型原型的方法
</script>
相关文章
相关标签/搜索