JS学习5(面向对象)

在ES里把对象定义为无序属性的集合,其属性可以包含基本值,对象和函数。

理解对象

创建对象最简单的方法就是创建一个Object的实例,再为他添加属性和方法。

var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
    alert(this.name);
};

然而更加直观且方便的方法是这样的:

var person = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function(){
        alert(this.name);
    } 
};

属性类型

在创建这些属性的时候都会带有特征值,JS通过这些特征值来定义属性的行为。
在ES中有2种属性:数据属性和访问器属性。
数据属性
该属性包含一个数据值的位置,这个位置可以读取或写入

  • [[Configurable]]:表示是否能通过delete删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改为访问器属性。像上面那样定义属性默认为true
  • [[Enumerable]]:表示能否通过for-in循环返回属性,默认为true
  • [[Writable]]:表示能否修改属性的值,默认为true
  • [[Value]]:包含这个属性的数据值,读取和写入都是这个,默认为undefined

要修改默认属性,只能使用ES5里的Object.defineProperty():

var person = {};
Object.defineProperty(person, "name", {
    writable: false,
    value: "Nicholas"
});
alert(person.name); //"Nicholas" 
person.name = "Greg"; 
alert(person.name); //"Nicholas"

只要你调用了Object.defineProperty(),如果不指定configurable,enumerable,writable他们会默认为false。
configurable比较特殊,因为一旦将其配置为false,就意味着你再也改不回来了,而且从此以后这个属性的enumerable,configurable不可修改,否则直接报错:

var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Nicholas"
});
alert(person.name); //"Nicholas" 
person.name = "Gerg";
delete person.name; 
alert(person.name); //"Nicholas"
//这里报错
Object.defineProperty(person, "name", {
    enumerable: true
});

访问器属性
访问器属性不包含数据值,但是包含一对getter和setter函数,在读取这个属性时会调用getter函数,写入时则会调用setter函数。访问器属性有如下4个特性:

  • [[Configurable]]:表示是否能通过delete删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改为数据属性。像上面那样定义属性默认为true
  • [[Enumerable]]:表示能否通过for-in循环返回属性,默认为true
  • [[Get]]:在读取属性时调用的函数,默认undefined
  • [[Set]]:在写入属性时调用的函数,默认undefined

与数据属性一样,访问器属性不能直接定义,需要通过 Object.defineProperty()来定义。

var book = {     
    _year: 2004,      
    edition: 1 
};  
Object.defineProperty(book, "year", {     
    get: function(){         
        return this._year;     
    },     
    set: function(newValue){  
        if (newValue > 2004) {             
            this._year = newValue;             
            this.edition += newValue - 2004;        
        }     
    } 
});  
book.year = 2005; 
alert(book.edition);  //2 

如果你只指定了一个,那就意味着这个属性只能读或只能写。

定义多个属性

有时要定义多个属性的特性,这时一个一个的调用Object.definePropertie就很麻烦。

var book = {};  
Object.defineProperties(book, {     
    _year: {         
        value: 2004     
    },          
    edition: {        
        value: 1     
    },  
    year: {         
        get: function(){ 
            return this._year;         
        },  
        set: function(newValue){             
            if (newValue > 2004) {                 
                this._year = newValue;                 
                this.edition += newValue - 2004;             
            }         
        }     
    } 
}); 

读取属性的特性

Object.getOwnPropertyDescriptor()方法。
上面那个例子:

var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); 
alert(descriptor.value);         //2004 alert(descriptor.configurable); //false 
alert(typeof descriptor.get);    //"undefined"  
var descriptor = Object.getOwnPropertyDescriptor(book, "year"); 
alert(descriptor.value);        //undefined alert(descriptor.enumerable);   //false 
alert(typeof descriptor.get);   //"function" 

创建对象

工厂模式

工厂模式为了解决创建同一类对象使用太多重复代码的问题,于是将创建并初始化同一类对象的代码封装起来,变成生产这种对象的工厂。

function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };
    return o; 
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
alert(person1 instanceof Object);     //true

但是这种模式并没有解决对象识别的问题,也就是说在你创建时并不好说你创建的事什么对象。于是出现了构造函数模式。

构造函数模式

像Object,Array这样的原生构造函数在运行时会自动出现在执行环境中,此外也可以创建自定义的构造函数。从而定义自己的对象类型属性和方法。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }; 
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
alert(person1 instanceof Person);  //true
alert(person1 instanceof Object);  //true

以这种方式创建一个对象会经历以下4个步骤:

  • 创建一个新对象
  • 将构造函数的作用域赋值给新对象,这时里面的this就指向了这个对象
  • 执行这个函数
  • 返回新对象

使用这种方法创建的对象会有一个constructor属性,并且在用instanceof检测对象类型时可以准确的检出。注意构造函数首字母最好大写来与其它函数区分。
构造函数就是普通的函数
构造函数唯一的特别之处就是他通过new操作符来调用,也就是说任何函数,只要通过new操作符来调用,就可以作为构造函数。构造函数不通过new来调用就是普通的函数,里面的this该指哪里就指哪里。

//构造函数
var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas"
//普通函数
Person("Greg", 27, "Doctor"); //  加 window 
window.sayName(); //"Greg"
//另一个对象作用域中调用 
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse"); 
o.sayName(); //"Kristen"

但是构造函数模式也是存在问题的
主要问题就是对于每一个实例,实例的方法都被重新创建了。以上的构造函数可以写成这样:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("alert(this.name)");
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
alert(person1.sayName == person2.sayName);  //false

但是我们都明白这样是不科学的。把函数定义在全局作用域,在对象里包含一个指向这个函数的指针确实可以解决这个问题:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
alert(person1.sayName == person2.sayName);  //true

但是,这样违背了我们定义对象的初衷。于是原型模式被创造了出来。

原型模式

我们创建的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象的用途是包涵可以由特定实例类型共享的所有属性和方法。这个对象就被称作原型对象。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
person1.sayName();   //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName);  //true

理解原型对象
上面的例子里各对象间的关系
默认情况下,原型对象会获得一个constructor属性,这个属性包涵了一个指向prototype属性所在函数的指针。
当调用构造函数创建一个新实例以后,该实例的内部将包涵一个指针[[Prototype]],指向构造函数的原型对象。
[[Prototype]]是不能直接访问的,但是可以通过isPrototypeOf()来确定原型与实例之间的关系。

alert(Person.prototype.isPrototypeOf(person1));  //true

ES5有个新方法Object.getPrototypeOf(),这个方法可以返回参数实例的[[Prototype]]值。

alert(Object.getPrototypeOf(person1) == Person.prototype); //true alert(Object.getPrototypeOf(person1).name); //"Nicholas"

当代码在读取某个对象的属性的时候,都会执行2次搜索,首先搜索本实例里面有没有这个对象,再搜索原型对象中有没有。
但是不可以通过对象实例重写原型中的值,如果我们在实例中给一个属性赋值,这个属性本不存在在实例中而存在于实例的原型对象中,那么就会在这个实例中创建该属性,原型对象中的同名属性保持不变,且通过这个实例访问不到了。通过delete可以删除这个属性从而重新访问到原型中的同名属性。可以通过hasOwnProperty()来检测该实例中是否含有某对象。

person1.name = "Greg";
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1);  //true
alert(person1.name); //"Greg"    
alert(person2.name); //"Nicholas"      
delete person1.name;
alert(person1.hasOwnProperty("name"));  //false
alert("name" in person1);  //true
alert(person1.name); //"Nicholas"

in操作符
in会在对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
使用for-in时会返回所有的可枚举属性,同样是无论该属性存在于实例中还是原型中。
Object.keys()
这个方法可以获得当前实例的所有可枚举的属性名组成的数组,原型对象的获得不到。但你可以直接把原型当参数传进去。
Object.getOwnPropertyNames()
这个方法可以获得当前实例的所有属性名组成的数组,原型对象的获得不到。但你可以直接把原型当参数传进去。
更简单的原型语法

function Person(){
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true

这里我们直接重写了函数的原型对象,这与之前的操作获得的效果是一样的,但是constructor属性在重写prototype的过程中也就被覆盖了,现在他指向Object构造函数。这并不影响instanceOf来判断对象类型。如果你真的很需要constructor属性,那么自己写咯。要想完全一样的话注意要改成不可枚举的哦。

function Person(){
}
Person.prototype = {
     name : "Nicholas",
     age : 29,
     job : "Software Engineer",
     sayName : function () {
         alert(this.name);
    }
};
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});   

原型的动态性
在原型中查找属性其实是一个动态的搜索过程,所以我们对原型对象的任何修改都能在实例中反应出来,即使是先创建了实例再修改原型也是一样。但是如果你直接重写这个原型对象就麻烦了:

function Person(){
}
var friend = new Person();
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
friend.sayName();   //error

在实例化friend时,friend的[[Prototype]]指针指向Person的prototype指向的对象。然后你把原型指向了另一个对象,这就切断了他们之间的联系。sayName()就访问不到了。
这里写图片描述
原生对象的原型
原生对象也是通过同样的机制创建的,所以我们可以访问到他们原型里的属性,可以添加原型属性:

alert(typeof Array.prototype.sort); //"function" 
alert(typeof String.prototype.substring); //"function"

String.prototype.startsWith = function (text) {
    return this.indexOf(text) == 0;
};
var msg = "Hello world!";
alert(msg.startsWith("Hello"));   //true

但是并不推荐这么做。
原型对象的问题
首先就是,这样创建实例省略了为构造函数传递参数这个过程,也就是说所有的实例在默认情况下都会得到同样的属性值。这个还不算最大的问题。最大的问题是如果原型的属性值是一个引用值,比如数组,那么在一个实例中更改了该值,其他实例中的也就被修改了。有时我们并不想这样。

function Person(){
}
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shelby", "Court"],
    sayName : function () {
        alert(this.name);
    } 
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);    //"Shelby,Court,Van"
alert(person2.friends);    //"Shelby,Court,Van"
alert(person1.friends === person2.friends);  //true

组合使用构造函数模式和原型模式

构造函数用于定义实例属性,原型用来定义方法和共享的属性。这是ES中使用最广泛,认同度最高的一种创建自定义类型的方法。

function Person(name, age, job){
    this.name = name; 3 this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
alert(person1.friends);    //"Shelby,Count,Van"
alert(person2.friends);    //"Shelby,Count"
alert(person1.friends === person2.friends);  //false
alert(person1.sayName === person2.sayName);  //true

动态原型模式

function Person(name, age, job){
    this.name = name; 
    this.age = age; 
    this.job = job;
    //  
    if (typeof this.sayName != "function"){
    //这里不能使用字面量的形式重写原型
        Person.prototype.sayName = function(){
            alert(this.name);
        }; 
    }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

这里if里面的代码只在初次调用构造函数的时候才会执行,此后原型已经完成了初始化,这个方法已经存在了,以后的实例就不会再执行这段代码了。而且if只用写一个,所有的原型属性和方法都写在这里面就可以了有一个不存在就说明都不存在。但是这里千万别用字面量形式重写原型。看下面的例子:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    //
    if (typeof this.sayName != "function"){
        Person.prototype = {
            sayName : function () {
                alert(this.name);
            }
        }
    }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
var friend1 = new Person("Nicholas", 29, "Software Engineer");
//friend.sayName();这句会报错,找不到sayName
alert(Person.prototype === Object.getPrototypeOf(friend));//false
alert(Person.prototype === Object.getPrototypeOf(friend1));//true

在这里实例化friend时,friend的[[prototype]]指向了Person的prototype指向的对象,这个对象里现在并没有sayName这个属性,所以执行了if里面的代码,现在Person的prototype指向的对象就变成了有sayName的对象,而friend的[[prototype]]指向的对象却还是原来没有sayName的那个原型对象,所以会报错。可以看到Person.prototype和Object.getPrototypeOf(friend)并不是同一个对象。
在实例化friend1时,friend1的[[prototype]]指向的Person的prototype指向的对象里有sayName这个属性,prototype不会再被重写,所以sayName可以被访问到,两个对象也是同一个对象。

寄生构造函数模式

其思想就是创建一个函数,这个函数的作用仅仅是把创建对象的代码封装起来,只是看起来像构造函数,其实一点构造函数的作用都没起,甚至使用instanceOf都判断不出对象的类型:

function Person(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };
    return o; 
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

但是可以这么用,比如像给某些已有的引用类型做改造,比如想给数组添个方法,但不能修改本身的构造函数:

function SpecialArray(){
    var values = new Array();
    values.push.apply(values, arguments);
    values.toPipedString = function(){
        return this.join("|");
    };
    return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString());//"red|blue|green"

稳妥构造函数模式

稳妥对象指的是没有公共属性,方法不引用this对象,防止数据被偷窥,创建时不使用new,在一些安全的环境中运行的对象。

function Person(name, age, job){
    var o = new Object();
    //这难道不是一个闭包????
    o.sayName = function(){
        alert(name);
    };
    return o; 
}
var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

除了sayName方法,别的方法永远访问不到name属性。
这里应该用到了闭包的特性????

继承

许多OO语言都支持两种继承,接口继承和实现继承。接口继承只继承方法签名,实现继承则继承实际的方法。由于ES里没有函数签名这个概念,所以ES只支持实现继承,实现继承是依靠原型链来实现的。

原型链

其基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象包含指向构造函数的指针,实例都包含一个指向原型对象的内部指针。那么如果我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个类型原型的内部指针。这样逐级上去,就构成了原型链。

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 

这里写图片描述
在上面的代码中,我们没有使用SubType默认提供的原型,而是给他换了一个新的原型,这个新的原型就是SuperType的实例。于是新原型不仅具有作为一个SuperType的实例所具有的全部属性和方法,而且其内部还有一个指针指向SuperType的原型。
最后的结果就是instance指向SubType的原型,SubType的原型又指向SuperType的原型。原型方法getSuperValue()还是存在于SuperType的原型中,实例属性property则位于SuperType的实例中,也就是SubType.prototype中。
还要注意的是,这时因为SubType.prototype被直接重写了,所以instance.constructor指不到subType了,SubType.prototype里找不到constructor这个属性,于是到SubType.prototype的[[Prototype]]指向的对象中找,最后只能指到SuperType。
原型链本质上拓展了原型搜索机制,在找一个属性时,会顺着原型链一直找。
默认的原型
实际上,所有引用类型都默认继承了Object,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针指向Object.prototype。toString()之类的方法都是在这个原型里。
原型和实例的关系
由于原型链的关系,对于一个实例来说,在其原型链中出现的原型都是它的原型。

alert(instance instanceof Object);         //true 
alert(instance instanceof SuperType);      //true 
alert(instance instanceof SubType);         //true 
alert(Object.prototype.isPrototypeOf(instance));         //true alert(SuperType.prototype.isPrototypeOf(instance));      //true alert(SubType.prototype.isPrototypeOf(instance));        //true 

定义方法时要注意:1、给原型添加或重写方法要放在本类继承超类的语句后,也就是将超类的实例赋值给本类的原型的语句之后。否则一赋值本类的原型就变成另一个对象,你之前添加的方法就灰飞烟灭了。 2、添加或定义方法时不能使用字面量重写原型对象。当然不能了。。。。。。要不继承的超类实例不就没了嘛!!!!!!!!!!!!!!!!!
原型链的问题
主要问题来自包含引用类型值的原型。这个属性会被所有的实例共享,所以我们不在原型中定义属性。但是通过原型来实现继承的过程中,这个问题又出现了。因为在继承的过程中我们将本类的原型指向了一个超类的实例,这也就意味着超类里的属性现在也在本类的原型里了。于是问题又出现了。。。。我们来看个栗子:

function SuperType(){   
    this.colors = ["red", "blue", "green"]; 
}  
function SubType(){
}  
SuperType SubType.prototype = new SuperType();  
var instance1 = new SubType(); 
instance1.colors.push("black"); 
alert(instance1.colors);    //"red,blue,green,black"  
var instance2 = new SubType(); 
alert(instance2.colors);      //"red,blue,green,black"

看。。。超类实例的属性变成了本类的原型属性被共享了。
还有一个问题就是在创建子类的实例的时候不能向超类的构造函数传递参数。

借用构造函数

基本思想就是在子类的构造函数中调用超类的构造函数。

function SuperType(){     
    this.colors = ["red", "blue", "green"]; 
}  
function SubType(){      
    SuperType.call(this); 
}  
var instance1 = new SubType(); 
instance1.colors.push("black"); 
alert(instance1.colors);    //"red,blue,green,black"  
var instance2 = new SubType(); 
alert(instance2.colors);    //"red,blue,green"

传递参数
这是它的优势之一:

function SuperType(name){
  this.name = name;
}
function SubType(name){      
  SuperType.call(this, name);       
  this.age = 29; 
}  
var instance = new SubType("sss"); 
alert(instance.name);    //"sss"; 
alert(instance.age);     //29

问题
问题很明显,这种继承只适用于属性,方法也这么继承的话是不科学的。

组合继承

组合继承使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

function SuperType(name){
  this.name = name;
  this.colors = ["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);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);      //"red,blue,green,black"
//这里可以清楚的看到,实例的属性和本类原型里的属性是不一样的
alert(Object.getPrototypeOf(instance1).colors);
instance1.sayName();          //"Nicholas";
instance1.sayAge();           //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors);      //"red,blue,green"
instance2.sayName();          //"Greg";
instance2.sayAge();           //27

原型式继承

这种继承方式的思想就是基于原来的对象创建新的对象:

function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
alert(anotherPerson.name);  //Greg
alert(Object.getPrototypeOf(anotherPerson).name); //Nicholas
Object.getPrototypeOf(anotherPerson).name = "qwe";
alert(Object.getPrototypeOf(anotherPerson).name); //qwe
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
alert(Object.getPrototypeOf(yetAnotherPerson).name); //qwe
yetAnotherPerson.friends.push("Barbie");
alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

实际上这就是又创建了两个以person为原型的实例然后又重写了同名的实例属性。可以看到这里所有实例里的friends属性没有被覆盖,所以它们还是共享一个。
ES5中,通过新增Object.create()方法来支持这种原型继承。这个方法接收两个参数:一个用作新对象原型的对象和一个为新对象定义额外属性的对象(可选)。
不指定第二个参数,和刚才的差不多:

var person = {     
    name: "Nicholas",     
    friends: ["Shelby", "Court", "Van"] 
};  
var anotherPerson = Object.create(person); 
anotherPerson.name = "Greg"; 
anotherPerson.friends.push("Rob");      
var yetAnotherPerson = Object.create(person); 
yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie");  
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

指定第二个参数时指定的属性会覆盖原型对象上的同名属性:

var person = {    
    name: "Nicholas",    
    friends: ["Shelby", "Court", "Van"] 
}; 
var anotherPerson = Object.create(person, {     
    name: {         
        value: "Greg"     
    } 
});     
alert(anotherPerson.name); //"Greg" 

寄生式继承

创建一个仅用于封装继承过程的函数,在该函数的内部以某种方式来增强对象,最后返回它。

function createAnother(original){     
    var clone = object(original);      
    clone.sayHi = function(){         
        alert("hi");     
    };     
    return clone;     
} 
var person = {     
    name: "Nicholas",     
    friends: ["Shelby", "Court", "Van"] 
};  
var anotherPerson = createAnother(person); 
anotherPerson.sayHi(); //"hi" 

这里基于person返回了新对象,这个新对象不仅有person的所有方法和属性,还有自己的方法。
这个用在主要考虑对象而不是自定义类型和构造函数的情况下,但是在这样的继承模式下方法还是不可复用的。

寄生组合式继承

组合继承确实可以达到我们自己最终想要的目的,但是还是有个问题:无论什么情况下都会调用两次超类的构造函数,一次是创建子类原型的时候,一次是在子类型构造函数内部。
解决这个的办法就是使用组合寄生继承。在指定子类的原型时我们只是需要超类的原型的一个副本而已,并不需要调用超类的构造函数,使用寄生继承的方式来获取超类的原型并赋给子类的原型就好了。

//用来复制超类原型的函数
function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}
function inheritPrototype(subType, superType){  
    //复制超类原型   
    var prototype = object(superType.prototype);  
    //增强超类原型,指定我们想要的     
    prototype.constructor = subType;   
    //赋给子类原型           
    subType.prototype = prototype;               
} 
function SuperType(name){     
    this.name = name;     
    this.colors = ["red", "blue", "green"]; 
}  
SuperType.prototype.sayName = function(){     
    alert(this.name); 
};  
function SubType(name, age){       
    SuperType.call(this, name);          
    this.age = age; 
} 
//这里就不用再调用超类的构造函数了,超类的原型方法获取到了,实例属性也不存在于子类的原型中了,perfect 
inheritPrototype(SubType, SuperType);  
SubType.prototype.sayAge = function(){     
    alert(this.age); 
};

这样的继承方式高效科学,超类的原型方法获取到了,实例属性也不存在于子类的原型中了,超类的构造函数只调用了一次。还可以使用instanceOf和isPrototypeOf()。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值