不记下来总是容易忘记掉,相当于备忘吧。。本文主要参考高程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); }
简单来说就是超类的属性用借构的方式继承,超类的方法用原型的方式来继承。。一般这种方式称为组合继承,也是使用最为广泛的继承模式。