Javascript 对象的建立与继承

建立对象

建立对象的方法

理解原型对象:
不管何时,只要建立了新函数,就会根据一组特定的规则为该函数建立一个 prototype属性,这个属性指向函数的原型对象。在默认状况下,全部原型对象都会自动得到一个constructor属性,这个属性包含一个指向prototype属性的所在函数的指针。(重写原型属性会默认取消constructor属性)详细可见文章下图。
建立object实例的方式有三种:对象字面量表示法、new操做符跟object构造函数、(ECMAScript5中的)object.create()函数。下面主要讲的是最为复杂的new操做符跟object构造函数的建立对象实例的方法。函数

工厂模式

function createPerson(name,age,job){
        var o=new Object();//建立对象实例
            o.name=name;//为对象实例添加属性
            o.age=age;
            o.job=job;
            o.sayName=function(){
                alert(this.name);
            }
        return o;//返回刚建立的对象实例
    }
    var person1= createPerson("NIcho",29,"software engineer");

工厂模式的问题
工厂模式虽然解决了建立多个类似对象的问题,可是没有解决对象识别的的问题(即怎样知道一个对象的类型)。测试

构造函数模式

建立对象的自有属性的主要方法,每定义一个函数就实例化了一个对象。this

function Person(name,age,job){
        this.name=name;
        this.age=age;
        this.job=job;
        this.sayName=function(){
            alert(this.name);
        }
   }
    //将构造的函数Person实例化,传入的参数做为自有属性,而且继承自Person.prototype
    var person1=new Person(NIcho",29,"software engineer);
    var person2=new Person(Jhon",26,"software engineer);

Person函数代替了createPerson函数,还有如下不一样之处:spa

  1. 没有显式建立对象
  2. 直接将属性和方法赋给this对象(这里的this指的就是这个构造的函数)
  3. 没有return 语句

咱们在这个例子中建立的对象既是object的实例又是person的实例(全部对象均继承自object。object能够理解为对象的根原型,咱们所用到的全部对象方法的操做,如toString 、substring 、splice等都是继承自object)。prototype

alert(person1 instanceOf Object);//true
    alert(person1 instanceOf Person);//true

构造函数模式的问题:指针

使用构造函数的主要问题是,每一个方法都要在每一个实例上建立一次。person1和person2都有一个sayName的方法,但两个方法不是同一个Function实例(ECMAScript中的函数是对象,每定义一个函数就是实例化了一个对象)。

若是没有自定义向person原型中添加属性,实例化以后则获得Person自有属性的一个副本,而且继承object(注:person1与person2中sayName()引用位置不一样)。
图片描述code

经过把函数定义转移到构造函数外来解决:对象

//全局函数
    function sayName(){
        alert(this.name);
    }

对应的:继承

this.sayName:sayName;

原型模式

用于定义实例共享的属性和方法。索引

function Person(){
    }
    Person.prototype.name="Nicho";
    Person.prototype.age=29;
    Person.prototype.jod="software engineer";
    Person.prototype.sayName=function(){alert(this.name)};
    var person1=new Person();
    person1.sayName();//"Nicho" 继承Person.prototype
    var person2=new Person();
    person2.sayName();//"Nicho"继承Person.prototype
    alert(person1.sayName==person2.sayName);//true 引用位置相同

图片描述

(注:继承链接存在与实例与构造函数的原型之间而不是实例与构造函数之间)

关于原型上的一些函数:

  1. 检测obj是否为obj1的原型:obj.isPrototypeOf(obj1)//返回true/false
  2. 得到obj1的原型:object.getPrototypeOf(obj1)
  3. 检测一个属性是否存在于实例中(也就是自有属性)仍是原型中(来自于继承):
Obj1.hasOwnProperty();//若是obj1有自有属性则返回true,不然返回false

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

组合使用构造函数模式和原型模式既能够继承原型对象的属性,又能够设置自由属性。

function Person(name,age,job){
            this.name=name;
            this.age=age;
            this.job=job;
    }
    Person.prototype={
        constructor:Person,
        sayName:function(){
            alert("Hi");
        }
     }
    var person1=new Person(“Nicho”,29,”software engineer”);
    //person1自有属性:name aga job;原型属性(来自继承):constructor sayName

代码读取某个对象的某个属性时的搜索方法

搜索首先从对象实例开始,若是实例中找到了给定名字的属性,则返回该属性的值。若是没有找到,则继续搜索指针指向的原型对象,在原型对象中查找给定名字的属性。若是在原型对象中找到了该属性,则返回属性的值,不然一直向上查找给定名字的属性,直到object。若是没有找到则返回undefined。

这就须要咱们注意:虽然咱们能够经过对象实例访问原型中的值,但却不能经过对象实例(注意!!这里说的是经过对象实例而不是在原型中修改属性)来重写原型中的值。若是在实例中添加了一个与实例原型中同名的属性,该属性将会屏蔽原型中的那个属性。
来看下面的的例子:

function Person(){
    
    }
    Person.prototype.name="Nicho";
    Person.prototype.age=29;
    Person.prototype.jod="software engineer";
    Person.prototype.sayName=function(){alert(this.name)};
    var person1=new Person();
    var person2=new Person();
     person1.name="Greg";
    alert(person1.name);//Greg-来自实例
    alert(person2.name);//Nicho-来自原型;
    //person1对属性 name的修改并不能修改原型上的相应属性,所以person2继承自原型

图片描述

  1. person1=new Person()person3=person1是不一样的:前者是实例化一个对象,后者遵循复制函数(对象引用)的原理
  2. 对象总体进行操做(引用)和对对象中的某个属性(值)进行操做实现原理是不一样的
  • 整个对象的复制即引用相同
var person3=person1;//整个对象的复制即引用相同(指针指针指针!只是一个索引,实际存储都不在指针位置下)
    person3.name=”newName”;//修改属性名称的值
    alert(person1.name);//newName;//另外一个来自此处的引用属性的值会修改;即复制对象原理
    alert(person1.isPrototypeOf(person3));//false
    alert(Person.prototype.isPrototypeOf(person3));//true
  • 简单的属性的复制,并不是引用同一个属性值.
var person1=new Person("NIcho",29,"software engineer");
      var person2=new Person("HIcho",9,"Hardware engineer");
      person1.name=person2.name;//简单的属性的复制,并不是引用同一个属性值
      person2.name="NEW";//尝试赋新值
      alert(person1.name);//HIcho,说明是值的传递而并不是引用

继承

  1. ECMAScript只支持实现继承,并且实现继承主要是依靠原型链来实现的。
  2. 其基本思想是利用原型让一个引用类型继承另外一个引用类型的属性和方法(即:原型链的构建是经过将一个类型的实例赋值给另外一个构造函数的原型来实现的)。

原型链的继承

function superType(){
        this.property=true;
       }
        superType.prototype.getSuperValue=function(){
            return this.property;
        };
        function subType(){
            this.subProperty=false;
        }
        //继承superType
        subType.prototype = new superType();
        subType.prototype.getSubValue = function(){
            return this.subProperty;
        };
        var instance = new subType();
        alert((instance.getSuperValue)());//true  getSuperValue来自superType.prototype
        alert(instance.property);//true   property来自superType实例

实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于superType的实例的全部属性和方法(包括自有属性和继承属性),如今也存在于subType.prototype中了。
图片描述
(此时instance.constructor如今指向的是superType,由于subType的原型指向了另外一个对象superType的原型,而这个原型对象的constructor指向的是superType)

注意:

  1. 别忘记默认的对象Object.prototype: subType继承了superType,superType继承了Object.prototype
  2. 肯定原型和实例的关系:
  • instanceOf;测试实例与原型链中出现过的构造函数
alert(instance instanceOf  superType);//true
   alert(instance instanceOf  subType);//true
  • isPrototypeOf:只要原型链中出现过的原型均可以说是该原型链所派生出来的实例的原型
alert(superType.protoType.isPrototypeOf (instance));//true
    alert(subType.protoType.isPrototypeOf (instance));//true

原型链的问题:
在经过原型来实现继承时,原型实际上会变成另外一个类型的实例。因而,原先的实例属性也就瓜熟蒂落地变成了如今的原型属性了。

解决原型链继承带来的问题的方法

解决这个问题的技术是借用构造函数,使用最多的继承模式是组合继承。此外还有原型式继承,寄生式继承,寄生组合继承。这里主要讲借用构造函数和组合继承。

借用构造函数

在子类型构造函数的内部调用超类型的构造函数。

function superType(){
        this.colors=["red","blue","green"];
      }
      function subType(){
        //继承了superType
        superType.call(this);//在新建立的subType实例的环境下调用superType函数
        //这样一来,就会在新subType对象上执行superType函数中定义的全部初始化代码
        //结果subType的每个实例都会具备本身的color属性的副本了
      }
      var instance1=new subType();
      instance1.colors.push("black");
      alert(instance1.colors);//red,blue,green,black
      var instance2=new subType();
      alert(instance2.colors);//red,blue,green

组合继承

思路是使用原型链实现对属性和方法的继承,而经过借用函数来实现对实例属性的继承。

function superType(name){
  this.name = name;
  this.colors = ["red","blue","green"];
}

superType.prototype.sayName=function(){
  console.log(this.name);
}

function subType(name,age){
  superType.call(this,name);
  this.age = age;
}

subType.prototype = new superType();
subType.prototype.constructor = subType;
subType.prototype.sayAge = function(){
  console.log(this.age);
}

var instance1 = new subType("Nicho",29);
instance1.sayName();//”Nicho”
instance1.sayAge();//29

总结:

  1. subType的自有属性: this.age = age
  2. subType的继承属性:
superType.prptotype.sayName
subtype.prototype.sayAge
function superType(name){    
    this.name=name;
       this.colors=["red","blue","green"];
     }

组合继承改进

组合式继承的问题
组合式继承是js最经常使用的继承模式,但组合继承的超类型在使用过程当中会被调用两次;一次是建立子类型的时候,另外一次是在子类型构造函数的内部。咱们使用Object.create作一下改进:

function superType(name){
  this.name = name;
  this.colors = ["red","blue","green"];
}

superType.prototype.sayName=function(){
  console.log(this.name);
}

function subType(name,age){
  superType.call(this,name);
  this.age = age;
}

function inheritPrototype(sup,sub){
  const obj = Object.create(sup);
  obj.constructor = sub;
  sub.prototype = obj;
}
// 继承
inheritPrototype(superType,subType);

重写原型对象

下面让咱们看下一种状况:重写原型对象(彻底重写而不是简单的添加原型对象的属性)
咱们知道,在调用构造函数是会为实例添加一个prototype指针,而把这个原型修改成另外一个对象就等于切断了构造函数与最初原型之间的联系。请记住:实例中的指针仅指向原型而不指向构造函数。

function Person(){};
    var friend=new Person();
    Person.prototype={
    Constructor:Person,
    Name:“Nicholas”,
    Age:29,
    sayName=function(){
    alert(“this.name”);
    }
    }
    friend.sayName();//error

重写原型对象以前:

图片描述

重写原型对象以后:

图片描述

由图可见:重写原型对象切断了现有原型与任何以前已经存在的对象实例之间的联系,它们引用的还是最初的原型。