现在的位置: 首页 > 综合 > 正文

【深入JavaScript】3.JavaScript继承的实现总结

2013年05月06日 ⁄ 综合 ⁄ 共 6830字 ⁄ 字号 评论关闭

一、JavaScript对象

  ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”

  说白了就是一组无序名值对,其中值可以是数据或函数。

 

二、创建对象的方法总结

1.创建Object的实例

(1)方式:创建一个Object类型的实例,然后再为它添加属性和方法。

(2)例子:

var person = new Object();
person.name = "kwan";
person.age = 22;
person.job = "student";

person.sayName = function () {
    alert(this.name);
};

(3)优点:简单,容易理解。

(4)缺点:使用同一个接口创建很多对象,会产生大量重复代码,且无法识别对象类型。

(5)总结:这是创建自定义对象的最简单的方式。

 

2.工厂模式

(1)方式:在JS中定义一个工厂函数来实现这一模式。

(2)例子:

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;
}

 

(3)优点:减少重复使用的代码。

(4)缺点:无法识别对象的类型。

(5)总结:工厂模式抽象了创建具体对象的细节,工厂函数就像一个真正的工厂,只需为它传递参数,就可以流水线式产生相应的对象。

 

3.构造函数模式

(1)方式:创建自定义的构造函数,从而定义自定义对象类型的属性和方法。

(2)例子:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        alert(this.name);
    };
}

(3)优点:可将它的实例标识为一种特定的类型。

(4)缺点:每个方法都要在每个实例上重新创建一遍,但其实方法是可以让所有实例共享的。

       而如果简单地将函数定义转移到外部,让对象引用它,那么该模式又失去封装性。

(5)总结:  

  1.构造函数模式中:

           a.没有显式创建对象

           b.直接将属性和方法赋给了this对象

             c.也没有return语句

           d.且函数名首字母大写(惯例)

  2.使用new操作符调用构造函数创建新实例的机制是:

    a.创建一个新对象

    b.将构造函数的作用域赋给新对象(因此this指向这个新对象)

    c.执行构造函数中的代码

    d.返回新对象

  3.新实例具有一个constructor属性,该属性指向构造函数。

  4.构造函数与其他函数的唯一区别,就在于调用它们的方式不同。调用构造函数时需用new操作符。任何函数通过new操作符调用都会变成构造函数,而原本的构造函数若不使用new操作符而是直接调用,那和普通的函数没什么两样,此时的this指代调用该函数的作用域。

  

4.原型模式

(1)方式:通过函数的prototype属性,将可以共享的信息直接添加到原型对象中。

(2)例子:

function Person() {}

Person.prototype.name = "kwan";
Person.prototype.age = "22";
Person.prototype.job = "student";
Person.prototype.sayName = function () {
    alert(this.name);
};

(3)优点:可让所有对象实例共享其原型对象所包含的属性和方法。

(4)缺点:首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值;

       其次,由于其共享的本性,当属性值为引用类型时,会导致很大问题。

(5)总结:

    1.我们创建的每个函数都有一个prototype属性,该属性值为指针,指向一个对象,即原型对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。

    2.使用原型模式创建的对象都在使用同一组属性和函数,这便是该模式的共享特性。

    3.若实例属性与原型对象属性同名,则优先取实例属性值(屏蔽)

    4.实例的内部属性__proto__指向原型对象,这便是两者之间的连接。继承的实现,以及对象属性的查找,其实都是依赖于这个连接。

      5.读取对象属性的过程是:先找实例,后找原型。

    6.原型是动态的,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来。

 

5.组合使用构造函数模式和原型模式(优化方案)

(1)方式:构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。

(2)例子:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["kwan", "soyi"];
}

Person.prototype = {
    constructor : Person,
    sayName : function () {
        alert(this.name);
    }
}

(3)优点:每个实例都会有自己的一份实例属性的副本,同时又共享对方法和共享属性的引用,最大限度地节省了内存;另外,该模式支持向构造函数传递参数。

(4)缺点:封装性不足。

(5)总结:挺好用。

 

6.动态原型模式

(1)方式:把所有信息都封装在了构造函数中,通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

(2)例子:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["kwan", "soyi"];
    
    if (typeof this.sayName != "function") {//这个判断是必要的,且只需写一个条件
        Person.prototype.sayName = function () {
            alert(this.name);
        };
    }
}

(3)优点:仅在必要情况下通过在构造函数中初始化原型,保持同时使用构造函数和原型的优点,封装度更高。

(4)缺点:暂无。

(5)总结:

    通过if条件的判断,只有在第一个实例被new出来的时候才会为原型对象添加属性和方法,接下来的所有对象的实例化均不会调用添加操作,而且这样写可将所有信息封装在构造函数中。

 

7.寄生构造函数模式(了解)

(1)方式:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。

(2)例子:

function Person(name, age, job) {
    var o = new Ojbect();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        alert(this.name);
    };
    return o;
}

(3)优点:暂无。

(4)缺点:返回的对象与构造函数或构造函数的原型之间没有关系,为此不能依赖instanceof操作符来确定对象类型。

(5)总结:

    1.构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个return语句,可以重写返回值。

    2.这个模式的应用条件是:在特殊情况下用来为对象创建构造函数。尽量不要用这个模式。

 

8.稳妥构造函数模式(了解)

(1)方式:稳妥构造函数遵循与寄生构造函数类似的模式,但区别是:新创建对象的实例方法不引用this,并且不使用new操作符调用构造函数。

(2)例子:

function Person(name, age, job) {
    var o = new Ojbect();

    o.sayName = function () {
        alert(name);
    };
    
    return o;
}

(3)优点:暂无。

(4)缺点:返回的对象与构造函数或构造函数的原型之间没有关系,为此不能依赖instanceof操作符来确定对象类型。

(5)总结:

    1.所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。

    2.稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序(如Mashup)改动时使用。

 

三、继承总结

  在ECMAScript中无法实现接口继承,它只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

1.原型链继承

(1)什么是原型链?

  原型链的基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。

  我们让原型对象A等于另一个类型的实例,此时的原型A将包含一个指向另一个原型B的指针,相应地,原型B中也包含着一个指向另一个构造函数的指针。假如原型B又是另一个原型C的实例,那么上述关系依然成立,如此层层推进,就构成了实例与原型的链条(A->B->C)。这就是原型链的概念。

(2)例子:

function SuperType(){ //父类
    this.property=true;
}

//在父类原型中添加方法
SuperType.prototype.getSuperValue=function(){
    return this.property;
};

function SubType(){  //子类
    this.subproperty=false;
}

//实现继承的代码:
SubType.prototype=new SuperType();

//在子类的新原型中添加方法
SubType.prototype.getSubValue=function(){
    return this.subproperty;
};

(3)原型链的本质是什么?

  利用原型链实现继承的本质是重写原型对象,代之以一个新类型的实例。

  同时,通过实现原型链,本质上扩展了原型搜索机制。

  在通过原型链实现继承后,搜索属性或方法的过程就得以沿着原型链继续向上。

(4)或许来一个图,会更加清晰:

(5)原型链的问题:

  1.当存在包含引用类型值的原型时,某个实例对引用类型值的修改,会反映在其余所有实例上;

  2.在创建子类型的实例时,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

 

2.借用构造函数(伪造对象、经典继承)

(1)基本思想:在子类型构造函数的内部调用超类型构造函数。

(2)例子:

function SuperType() { //父类
    this.colors = ["red", "blue", "green"];
}

function SubType() {
    //继承SuperType
    SuperType.call(this);
}

(3)优点:可在子类型构造函数中向超类型构造函数传递参数。

(4)缺点:仅使用此技术实现继承,函数无法复用。而且在超类型的原型中定义的方法,对子类型而言也是不可见的。

(5)总结:

  仅仅借用构造函数实现继承,本质上是没有利用到原型链的。因此很少单独使用该模式。

 

3.组合继承(伪经典继承)

(1)方式:将原型链和借用构造函数的技术组合在一块。

       其基本思想是:使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。

(2)例子:

function SuperType(name) { //父类
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function () {
    alert(this.name);
};

function SubType(name, age) {
    //继承SuperType的实例属性
    SuperType.call(this, name); //第二次调用超类型构造函数
    
    this.age = age;
}

//继承方法
SubType.prototype = new SuperType(); //第一次调用超类型构造函数

SubType.prototype.sayAge = function () {
    alert(this.age);
};

(3)优点:既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

(4)缺点:无论在什么情况下,都会调用两次超类型构造函数,以至于重写超类型对象的实例属性,造成空间的浪费。(这个自己画图或看书理解)

(5)总结:最常用的继承模式。

 

4.寄生组合式继承

(1)方式:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

(2)基本思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

(3)例子:

  首先,寄生组合式继承的基本模式是:

function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype); //创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //指定对象
}

//原型式继承
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

  示例中,object函数首先生成一个空白构造函数F,再将传入的对象o指定为F的原型对象,最后返回一个F的实例。

  这样一来,返回的对象的原型就是o,而本身这个对象是空白的。

 

  而inheritPrototype函数的作用是:

    首先调用object函数,传入超类型的原型,从而得到一个F的空白实例,此时该实例的原型就是超类型的原型;

    然后,将这个实例的构造函数指针指定为subType,因此这个实例是用作subType的原型的;

    最后,将第一步所得的空白实例对象指定为subType的原型。

    至此,subType的原型不再是superType的实例,而是一个空白的实例,但这个实例原型又是suerType。

  下面给出示例2:

function SuperType(name) { //父类
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function () {
    alert(this.name);
};

function SubType(name, age) {
    //继承SuperType的实例属性
    SuperType.call(this, name); //仅这一次调用超类型构造函数
    
    this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function () {
    alert(this.age);
};

//继承方法
SubType.prototype = new SuperType(); //第一次调用超类型构造函数

SubType.prototype.sayAge = function () {
    alert(this.age);
};

(4)优点:这个例子的高效率体现在它只调用一次SuperType的构造函数,并且因此避免了在SubType.prototype上创建不必要的、多余的属性。与此同时,原型链还能保持不变。

 

5.原型式继承(了解)

(1)方式:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

(2)例子:

//原型式继承
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

(3)本质:object()对传入其中的对象执行了一次浅复制。

(4)应用条件:这种继承模式要求必须有一个对象可以作为另一个对象的基础。如果有这么一个对象的话,可以把它传递给object()函数,然后再根据具体需求对得到的对象加以修改即可。

 

6.寄生式继承(了解)

(1)思路:与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像是真的是它做了所有工作一样返回对象。

(2)例子:

function createAnother(original) {
    var clone = object(original); //通过调用函数创建一个新对象
    clone.sayHi = function () {
        alert("hi");
    };
    //以某种方式来增强这个对象
    return clone;
}

 

四、总结

这一篇总结写到我吐血...估计看的人也吐血了吧...已经尽可能简单化了,希望各位喜欢。

共勉。

 

抱歉!评论已关闭.