supce's blog

对象的创建与继承

再把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);

其实在继承过程中,不必为了指定子类型的原型而调用超类型的构造函数。子类型可以利用寄生式继承来继承超类型的原型。