JavaScript原型链与继承

最近学习了《Javascript高级程序设计》面向对象部分,结合书中的例子总结一下原型链和继承部分的内容。函数

建立对象


在Js当中没有类这个概念,当咱们想要建立具备相同属性的对象的时候,有以下解决方法:学习

  • 工厂模式
  • 构造函数模式
  • 原型模式

其中,原型模式在Js中应用更加普遍,下面逐一对上述模式进行介绍。this

工厂模式

在ECMAScript中,所谓的工厂模式其实就用一个函数进行封装,建立对象时传入相应的参数便可。prototype

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}

var person1 = createPerson("Nick", 20, "Teacher");
var person2 = createPerson("Nancy", 21, "Doctor");

工厂模式一目了然,但它的缺点是,咱们没法得知咱们建立的person1person2究竟属于什么类型,咱们只知道它们都是object, 但咱们但愿更加具体。由于它们有相同的行为,咱们但愿person1person2都属于一个叫Person的“类”。构造函数模式能够知足这个要求。设计

构造函数模式

直接上代码:指针

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    }
}

var person1 = new Person("Nick", 20, "Teacher");
var person2 = new Person("Nancy", 21, "Doctor");

下面解释一下对象建立的过程,code

  1. 首先,new新建一个对象
  2. this指向这个新对象
  3. 执行Person函数中的代码
  4. 返回这个新对象

如今,咱们能够看一下person1person2的类型,对象

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

用构造函数建立对象也很方便,但有个缺点,注意上面例子中this.sayName方法,这种建立方式意味着咱们每建立一个新的Person实例,该实例内部都会新建一个sayName方法。而实际上,这些方法的做用都相同,没有重复建立的必要。若是把这个函数放在构造函数以外,做为全局函数的话,能够解决重复的问题,但却牺牲了Person的封装性,所以咱们推荐下一种模式,原型模式。blog

原型模式

咱们须要一个“仓库”存储同一类型的对象的共有的属性和方法,在js里面,这个“仓库”是prototype指向的对象(即__原型对象__)。继承

咱们建立的每个函数都有一个prototype(原型)属性,这个属性指向“仓库”(prototype自己是一个指针)。只要把所需的函数、属性添加到“仓库”中,即可在该类型对象的实例中共用。

举例以下:

function Person() {
}

Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function() {
    alert(this.name);
}

var person1 = new Person();
person1.sayName();  //'Nicholas'

var person2 = new Person();
peron2.sayName();   //'Nicholas'

alert(person1.sayName() == peron2.sayName());   //true

原型对象

原型对象是一个很重要的概念,它就是咱们上面提到的“仓库”(可能比喻不是很恰当),先来理解一下它:
结合刚才的代码,有下图:

有:

  1. 构造函数Person的prototype属性指向Person prototype对象;Person Prototype的constructor又指向Person构造函数;
  2. 实例Person1和Person2的[[prototype]]指针也指向Person prototype对象;

在调用person1.sayName()的时候,解析器会先询问person1中是否有sayName方法,发现没有,就会查找person1的原型;在person1原型中发现有,就会使用原型中的sayName方法;

来看另一个例子:

function Person() {
}

Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function() {
    alert(this.name);
}

var person1 = new Person();
var person2 = new Person();

person1.name = "Gerg";

person1.sayName();  //'Gerg'
person2.sayName();   //'Nicholas'

在这里,能够看到设置了person1name属性后,该属性即存在于person1实例中,无需再从原型中查找,在person1中,至关于将原型的name属性覆盖。
person2不受影响。

语法简化
上面的代码有点冗长,能够用对象字面量来重写原型对象,代码以下:

function Person() {
}

Person.prototype = {
    constructor : Person, 
    name : "Nicholas",
    age : 29,
    job : "software Engineer",
    sayName : function() {
        alert(this.name);
    }
}

原型的动态性

  1. 对原型的修改会直接反映到每一个实例,由于实例的[[prototype]] 其实是指向原型对象的指针;
  2. 若是直接重写原型对象,原来的原型没有被直接覆盖,而是将构造函数的prototype指向新的原型对象。在此以前所建的实例的[[prototype]]不指向重写后的原型对象。上代码:
function Person() {
}

var friend = new Person();

Person.prototype = {
    constructor : Person, 
    name : "Nicholas",
    age : 29,
    job : "software Engineer",
    sayName : function() {
        alert(this.name);
    }
}
friend.sayName(); //error

从图中能够看出以前生成的实例的原型并无改变。

构造函数模式和原型模式结合

咱们经过构造函数的方式生成每一个实例独自享有的属性和方法,经过原型生成共享的属性和方法:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friend = [shelby', 'Court'];
}

Person.prototype = {
    constructor : Person,
    sayName : function() {
        alert(this.name);
    }
}

var person1 = new Person('Nicholas', 29, 'software engineer');
var person2 = new Person('Gerg', 27, 'doctor');

person1.friends.push('Van');
alert(person1.friends);     //'shelby, Count, Van'
alert(person2.friends);     //'shelby, Count'
alert(person1.friends === person2.friends); // false
alert(person1.sayName === person2.sayName); // true

继承

未完待续...

相关文章
相关标签/搜索