JS笔记——面向对象的程序设计(JS笔记系列)
1.创建对象
虽然object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用一个接口创建很多对象 ,会产生大量的重复代码。
1.1工厂模式
这种模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节。
function createPerson(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name)'
}
return o;
}
var person1 = new createPerson("fiona","20");
var person2 = new createPerson("superMan","30");
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎么知道一个对象的类型)。
1.2构造函数模式
创建自定义的构造函数意味着将来可以将它的实例表示为一种特定的类型。而这正是构造函数模式胜过工厂模式的地方。
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
}
}
以这种方式调用构造函数实际上会经历以下4个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
使用构造函数的主要问题:每个方法都要在每个实例上重新创建一遍。以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建Function新实例的机制仍然是相同的。创建两个完成同样任务的Function实例的确没有必要;况且有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
alert(this.name)
}
此方案将sayName属性设置为全局的sayName函数。这样确实解决了两个函数做同一件事情的问题。但是如果存在很多的全局函数都是为了让某个对象调用,不仅让全局作用域有点名不符其实,而且让我们自定义的引用类型没有封装性可言。
1.3原型模式
我们创建的每个函数都有一个prototype属性,这个属性是一个指针, 指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例所共享的属性和方法。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
function Person(){}
Person.prototype.name="fiona";
Person.prototype.age="20";
Person.prototype.sayName = function(){
alert(this.name);
}
原型中所有的属性是被很多实例共享的,这种共享对于函数非常合适,但是对于包含引用类型值的属性来说,问题就比较突出了。
1.4组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。这样,每个实例都会有自己的一份实例属性的副本,但同时又共享者对方法的引用,最大限度地节省了内存。
function Person(name,age){
this.name = name;
this.age = age;
}
Person.Prototype = {
sayName:function(){
alert(this.name);
}
};
1.5动态原型模式
它把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。
function Person(name,age){
this.name = name;
this.age = age;
if(typeof this.sayName != 'function'){
alert(this.name);
}
}
其中,if语句检查的可以是初始化之后应该存在的任何属性或方法——不必一大堆if语句检查每个属性和方法,只要检查其中一个即可。
2.继承
2.1原型链
其基本的思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = true;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
实现的本质是重写原型对象,代之以一个新类型的实例。
但是原型链存在两个问题:
- 最主要的问题来自包含引用类型值的原型
- 其次是在创建子类型的实例时,不能向超类型的构造函数中传递参数。
2.2借用构造函数
基本思想:在子类型构造函数的内部调用超类型构造函数。
function SuperType(name){
this.name = name;
this.color = ["red","blue","green"];
}
function SubType(){
SuperType.call(this,name);
}
存在的问题:方法都在构造函数中定义,函数复用无从谈起。并且在超类型的原型中定义的方法,对子类型而言也是不可见的。
2.3组合继承(最常用)
有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
function SuperType(name){
this.name = name;
this.color = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(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(){
alert(this.age);
}
2.4原型继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
function object(o){
function F(){}
F.prototype = o;
return new F();
}
2.5寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有的工作一样返回对象。
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
alert("hi");
}
return clone;
}
2.6寄生组合式继承
组合继承最大的问题就是无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了制定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。
function inheritPrototype(subType,superType){
var prototype = Object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}