理解js中的原型和原型链

不记下来总是容易忘记掉,相当于备忘吧。。本文主要参考高程3,以及一点自己的想法,如有不对,还望指正

  • 为什么要使用原型模式?

我们创建对象最一开始用的工厂模式:

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

var person1 = createPeople('Nicolas', 29, 'SoftWare Engineer');
var person1 = createPeople('Greg', 27, 'Doctor');

该模式的缺点在于: 无法获知一个对象的类型

为了解决该问题,就有了构造函数模式:

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

var people1 = new Person('Nicholas', 29, 'Software Engineer');
var people2 = new Person('Greg', 27, 'Doctor');

new其实包含了一下4个步骤: 

创建一个空对象 -> 将构造函数的作用域赋给新对象(this指向该对象) -> 执行构造函数中的代码(为新对象添加属性) -> 返回新对象

 person1 和 person2 分别保存着Person对象的一个不同的实例。这两个对象都有一个constructor属性,指向Person。

该模式的缺点在于: 每个方法都要在每个实例上重新创建一遍。比如每个peson都有sayName这个方法,但是都指向不同的saynam实例,实际上并不需要这么多的sayname实例。那么一个解决方法是:

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

function sayName() {
    alert(this.name);
}

var people1 = new Person('Nicholas', 29, 'Software Engineer');
var people2 = new Person('Greg', 27, 'Doctor');

缺点: 实际上我们并不需要这样一个全局的sayName函数,并且如果对象有很多方法的话,全局函数就会变得非常多。

由此,我们就可以用原型模式来解决这个问题。

这里先介绍一下原型:

  • 什么是原型

当我们创建一个函数的时候,会给该函数创建一个prototype属性,该属性指向函数的原型对象。所有的原型对象有一个constructor属性,指向prototype属性所在的函数。即:

Person.prototype.constructor = Person;

创建实例之后,该实例中含有_proto_这个属性,指向构造函数的原型对象。找不到person的图了,凑合着看吧,这里_proto_画的有点不准确,指向的应该是构造函数的原型对象:

当代码读取对象的某个属性的时候,先查询实例是否有该属性,没有的话就搜索实例的原型对象。

回到原型模式,我们就可以这么构造一个对象:

function Person() {
} 

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

var people1 = new Person();
var people2 = new Person();

在这里无法为构造函数传参,为了解决这个问题可以组合使用构造函数和原型模式:

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

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

var people1 = new Person("Nicholas", 29, "SoftWare Engineer");
var people2 = new Person("Greg", 27, "Doctor");

这是目前比较通用的构造方法。。。

  •  下面介绍原型链

在js中,继承主要靠原型链来实现。实现原型链可以通过将构造函数的原型对象指向另一个类型的实例。

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

继承的本质上是重写原型对象,原来存在于SuperType的实例中的所有属性和方法也将存在于Subtype.prototype中。需要注意到的是:1.property这个属性位于SubType.property中,这一点可以这么理解:property是一个实例属性,而SubType.property是SuperType的一个实例。2.instance.constructor此时指向SuperType。

原型链实现继承有一个问题,就是包含引用类型的原型:

在构造函数里面定义属性不会与所有实例共享,而原型中定义的话就会与所有实例共享。

function SuperType() {
    this.color = ['red', 'blue'];
}

function SubType() {
      
}

SubType.protoType = new SuperType();
var instance1 = new SubType();
instance1.push('black');

var instance2 = new SubType();
alert(instance2.colors);  // 'red, blue,black'

这里SubType的原型是SuperType的实例,因此相当于在原型中定义了colors这个属性,而该属性是引用类型。

为了解决这样的问题,我们使用借用构造函数实现继承,实现只需要在子类型构造函数内调用超类类型结构函数。

function SuperType() {
    this.color = ['red', 'blue'];
}

function SubType() {
      SuperType.call(this);
}

var instance1 = new SubType();
instance1.push('black');

var instance2 = new SubType();
alert(instance2.colors);  // 'red, blue'

同时还解决了传参的问题:

function SuperType(name) {
    this.name= name;
}

function SubType() {
    SuperType.call(this ,'Nicholas');
}

var instance = new SubType();
alert(instance.name);  // 'Nicholas'

然而借用构造也有缺点:超类中属性包含方法的时候,如果将方法写在超类的prototype中,那么该方法对子类是不可见的,如果是写在构造函数里的话,那么就无法实现方法的复用,解决这个缺点可以采用组合继承的方式:

function SuperType(name) {
    this.name= name;
    this.color = ['red', 'green', 'blue'];
}

// 定义超类的方法
SuperType.prototype.sayName= function() {
    alert(this.name);
}

function subType(name, age) {
    // 继承属性
    SuperType.call(this, name);
    // 定义自己的实例属性
    this.age = age;
}

// 继承方法
SubType.prototype = new SuperType();
// 定义自己的方法
SubType.prototype.sayAge = function(){
    alert(this.age);
}

简单来说就是超类的属性用借构的方式继承,超类的方法用原型的方式来继承。。一般这种方式称为组合继承,也是使用最为广泛的继承模式。

原文地址:https://www.cnblogs.com/doudoujun/p/6420528.html