再把JavaScript中对象的创建与继承复习下
对象的创建
工厂模式
console.log("-----工厂模式-----");
function CreatePerson(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
return name;
};
return o;
}
var person = CreatePerson("Nico",28);
console.log(person.sayName());
工厂模式虽然解决了对象的创建问题,但是并没有解决对象的识别问题。alert(person.constructor); 得到的并不是想要的结果。
构造函数模式
console.log("-----构造函数模式-----");
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
return this.name;
};
}
var person1 = new Person("Sora",28);
var person2 = new Person("Mimo",29);
console.log(person1.sayName());
console.log(person1.constructor==Person); //true
console.log(person1.sayName == person2.sayName); //false
构造函数模式虽然解决了类的识别问题,但是带来了另一个问题:构造函数中的每个方法都要在每个实例上重新创建一遍。虽然可以把函数的创建放在构造函数外部,通过设置属性等于外部函数来解决,但这样跟对象封装的初衷不相符,而且造成过多的全局函数。
原型模式
console.log("-----原型模式-----");
function Person(){
}
Person.prototype = {
constructor : Person,
name : "Nico",
age : "28",
friends : ["Mimo"],
sayName : function(){
return this.name;
}
};
var person1 = new Person();
var person2 = new Person();
person2.friends.push("Pile");
console.log(person1.sayName==person2.sayName); //true
console.log(person1.friends); //["Mimo", "Pile"]
使用原型模式会出现引用类型的数据共享问题,于是引入了组合使用构造函数与原型模式
组合模式
console.log("-----组合使用-----")
function Person(name,age){
this.name = name;
this.age = age;
this.friends = [];
};
Person.prototype={
constructor : Person,
sayName : function(){
return this.name;
}
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Mimo");
person2.friends.push("Pile");
console.log(person1.sayName==person2.sayName); //true
console.log(person1.friends); //["Mimo"]
利用组合模式解决了之前的几个问题。这种解决问题的方式特别像学习《操作系统》课程中问题的解决方式。
动态原型模式
动态原型模式主要是把对象的创建信息都封装于对象的构造函数中
console.log("-----动态原型-----");
function Person(name,age){
this.name = name;
this.age = age;
if(typeof sayName != 'function'){
Person.prototype.sayName = function(){
return this.name;
}
}
}
使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
寄生构造函数
function Person(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
return this.name;
}
return o;
}
当我们想基于原有数组创建一个更多方法的数组,但是又不想对原有数据类型进行修改,就可以使用这种模式
function MyArray(){
var array = new Array();
array.push.apply(array,arguments);
array.toMyString = function(){
return this.join("|");
}
return array;
}
var myarray = new MyArray("1","2","3");
console.log(myarray.toMyString()); //1|2|3
对象的继承
在JavaScript中,主要还是利用原型链来实现继承的。
原型链
console.log("-----原型链-----"); function SuperClass(){ this.superProperty = "super"; this.friends = ["nico"]; } SuperClass.prototype.getSuperProperty = function(){ return this.superProperty; } function SubClass(){ this.subProperty = "sub"; } SubClass.prototype = new SuperClass(); SubClass.prototype.getSubProperty = function(){ return this.subProperty; } var instance1 = new SubClass(); var instance2 = new SubClass(); console.log(instance1.getSuperProperty()); //super console.log(instance2.constructor); //superclass instance1.friends.push("mimo"); console.log(instance2.friends); //["nico", "mimo"]
使用原型会带来两个问题
- 父类中的属性如果是引用类型,会在子类中出现共享问题
- 在创建子类实例时,不能通过父类的构造函数来传递参数
借用构造函数
console.log("-----借用构造函数-----"); function SuperClass(name){ this.name = name; this.friends = ["nico"]; } SuperClass.prototype.getName = function(){ return this.name; } function SubClass(age){ SuperClass.call(this,"sora"); this.age = age; } var instance1 = new SubClass(21); var instance2 = new SubClass(22); instance1.friends.push("mimo"); console.log(instance2.friends); //["nico"] console.log(instance1.getName); //undefined
借用构造函数解决了引用类型的问题,但是带来了其他的问题,在超类型中原型定义的方法对于子类来说是不可见的,因此,也就不能函数复用了。
组合继承
console.log("-----组合继承-----");
function SuperClass(name,friends){
this.name = name;
this.friends = friends;
}
SuperClass.prototype.getName = function(){
return this.name;
}
function SubClass(name,friends,age){
SuperClass.call(this,name,friends);
this.age = age;
}
SubClass.prototype = new SuperClass();
SubClass.prototype.getAge = function(){
return this.age;
}
var instance1 = new SubClass("sora",["mimo"],28);
var instance2 = new SubClass("pile",["kussn"],27);
instance1.friends.push("emi");
console.log(instance1.friends); //["mimo", "emi"]
console.log(instance2.friends); //kussn
console.log(instance1.getName() + instance1.getAge()); //sora28
组合继承是将原型链与借用构造函数二者的长处结合起来,使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。
原型式继承
console.log("-----原型式继承-----");
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name:"sora",
friends:["mimo"]
}
var person1 = object(person);
person1.name = "pile";
person1.friends.push("emi");
var person2 = object(person);
person2.name = "kussn";
person2.friends.push("rippi");
console.log(person.friends); //["mimo", "emi", "rippi"]
console.log(person2.friends); //["mimo", "emi", "rippi"]
object其实是对传入的对象进行了一次浅复制 对于引用类型来说,跟原型链一样是共享的
寄生式继承
console.log("-------寄生式继承--------");
var person = {
name:"sora",
friends:["mimo"]
}
function createAnother(original){
var clone = Object.create(original);
clone.sayName = function(){
return this.name;
};
return clone;
}
var instance = createAnother(person);
console.log(instance.sayName());
寄生式继承其实与寄生构造函数和工厂模式类似,创建一个封装继承的函数,然后在函数中对传入的对象进行某种增强,最后返回被增强的对象
寄生组合式继承
组合继承会有一个问题,就是无论什么情况下,都会调用两次超类型构造函数。为了解决这个问题,引入了寄生组合式继承
function inheritPrototype(subType,superType){
var prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name,friends){
this.name = name;
this.friends = friends;
}
SuperType.prototype.getName = function(){
return this.name;
}
function SubType(name,friends,age){
SuperType.call(this,name,friends);
this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.getAge = function(){
return this.age;
}
var person1 = new SubType("Nico",["emi"],22)
var person2 = new SubType("Sora",["kussn"],33);
person1.friends.push("Mimo");
console.log(person1.getName() + person1.getAge() + person1.friends);
console.log(person2.getName() + person2.getAge() + person2.friends);
其实在继承过程中,不必为了指定子类型的原型而调用超类型的构造函数。子类型可以利用寄生式继承来继承超类型的原型。