JavaScript 面向对象的主次设计

面向对象(Object-oriented,OO)的言语来一个标明,那就是是其还有类的概念。而透过类似可以创建任意多只有同等属性和方法的目标。前面提到了,ECMAScript中从来不接近的概念,因此她的目标为与因类的言语中的对象有所不同。

ECMAScript-262将目标定义为:“无序属性的集纳,其性可以涵盖基本值、对象或函数。”严格来讲,这便相当给说对象是一律组没有特定顺序的价。对象的每个属性或艺术还发出一个名,而每个名字还照到一个值。正以这样(以及其他即将讨论的案由),我们得把ECMAScript的靶子想象变为散列表:无非就是是同样组名值对,其中价值好是数量或者函数。

每个对象还是冲一个援类型创建的,这个引用类型可以是前面议论的原生类型,也得以是开发人员定义的型。

1,创建对象

创建于定义对象极其简单易行的点子就是是创办一个Object的实例,然后又为其填补加属性和方法,如下所示:

var person = new Object();
person.name = 'Nicholas';
person.age = 29;
person.job = 'Software Engineer';

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

person.sayName(); //"Nicholas"

 上面的事例创建了一个名为吧person的目标,并也它们上加了三单特性(name、age和job)和一个主意(sayName())。其中,sayName()方法用于展示this.name(将于分析成person.name)的值。早期的JavaScript开发人员经常采取此模式来创造新对象。但这种艺术来个引人注目的弱项:使用及一个接口创建很多靶,会发大量的双重代码。为釜底抽薪这问题,人们开始用工厂模式之同种变体。

1.1 工厂模式

工厂模式是软件工程领域同样种广为人知的设计模式,这种模式抽象了创具体对象的长河(后面还谈讨论其他设计模式及其在JavaScript中之落实)。考虑到在ECMAScript中无法创建类,开发人员就发明了同等种植函数,用函数来封装以一定接口创建对象的细节,如下面的事例所示:

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');

person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"

 函数createPerson()能够冲接收的参数来创造一个包含有必要信息之Person对象。可以多软地调用这个函数,而每次她还见面回到一个蕴含三只属性一个艺术的目标。工厂模式则缓解了创多单一般对象的题目,但可未曾解决对象识别的问题(即如何理解一个目标的类)。随着JavaScript的向上,又一个新模式起了。

1.2 构造函数模式

ECMAScript中之构造函数可用来创造特定类型的目标。像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');

person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"

 在是事例中,Person()函数取代了createPerson()函数。我们注意到,Person()中的代码有了跟createPerson()中千篇一律的部分外,还在以下不同之处:

a,没有显式地创建对象;

b,直接以性能和方给给了this对象;

c,没有return语句。

除此以外,还应注意到函数名Person使用的凡死写字母P。按照规矩,构造函数始终都该以一个特别写字母开头,而休构造函数应该坐一个小写字母开头。这个做法借鉴于其他OO语言,主要是为区别于ECMAScript中的旁函数;因为构造函数本身吗是函数,只不过可以用来创建对象而已。

比方创造Person的初实例,必须用new操作符。以这种方法调用构造函数实际上会经历以下四独步骤:

01,创建一个初对象;

02,将构造函数的企图域赋给新对象(因此this就本着了此新对象);

03,执行构造函数中的代码(为这新目标上加属性和方式);

04,返回新目标。

在前例子的末尾,person1和person2分别保存在Person的一个差之实例。这简单单对象还出一个constructor(构造函数)属性,该属性指向Person,如下所示:

alert(person1.constructor === Person); //true
alert(person2.constructor === Person); //true

 对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是instanceof操作符更保险一些。我们当这个例子中创造的有所目标既是Object的实例,同时为是Person的实例。这或多或少由此instanceof操作符可以取得证明:

alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true

 创建于定义的构造函数意味着前得以用她的实例标识为同一栽特定的花色,而就多亏构造函数模式大了工厂模式之地方。在这个事例中,person1和person2之所以又是Object的实例,是坐兼具目标全连续自Object(详细内容稍晚讨论)。

(以这种艺术定义之构造函数是概念在Global对象(在浏览器被是window对象)中的,因此只有其他发征,instanceof操作符和constructor属性始终会使是于大局作用域中查询构造函数,后面会详细谈论浏览器对象模型BOM)。

1.2.1以构造函数当作函数

构造函数与另函数的唯一区别,就在调用它们的措施不同。不过,构造函数毕竟也是函数,不存定义构造函数的异语法。任何函数,只要通过new操作符来调用,那她就足以作为构造函数,而别函数,如果未通过new操作符来调用,那它和普通函数也非见面来什么不同。例如,前面例子中定义的Person()函数可以由此下列任何一样种艺术来调用:

//当构造函数调用
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"

这个事例中的前方片行代码展示了构造函数的卓越用法,即以new操作符来创造一个新对象。接下来的蝇头执行代码展示了在不以new操作符调用Person()会面世什么结果:属性与方法还叫补加被window对象了。当于全局作用域中调用一个函数时,this对象总是指为Global对象(在浏览器中就是window对象)。因此,在调用完函数下,可以经过window对象来调用sayName()方法,并且还回了”Greg”。最后,也得采用call()(或者apply())在某特殊目标的作用域中调用Person()函数。这里是以目标o的作用域中调用的,因此调用后o就拥有了独具属性与sayName()方法。

1.2.2构造函数的题材

构造函数模式则好用,但为无须无缺陷。使用构造函数的机要问题,就是每个方法还设当每个实例上再次创设同全副。在前的事例中,person1和person2且发一个sayName()的点子,但那片单主意不是和一个Function的实例。不要忘记了——ECMAScript中之函数是目标,因此各个定义一个函数,也即是实例化了一个目标。从逻辑角度讲,此时底构造函数也可以这样定义:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function('alert(this.name)'); //与声明函数在逻辑上是等价的
}

 从这个角度达来拘禁构造函数,更易理解每个Person实例都带有一个见仁见智之Function实例(以显示name属性)的实质。如前所述,这片只函数是未顶的,下面的代码可以作证当时一点:

alert(person1.sayName == person2.sayName); //false

但是,创建两单好同样任务之Function实例的确没必要,况且有this对象在,根本毫无在实行代码前即将函数绑定到特定对象方面。因此,大而是像下这样,通过将函数定义转移至构造函数外部来缓解此题材:

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');

person1.sayName();
person2.sayName();

alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true

alert(person1.sayName === person2.sayName); //true

 在这事例中,我们拿sayName()函数的定义转移至了构造函数外部。而当构造函数内部,我们将sayName属性设置成等于全局的sayName函数,这样一来,由于sayName包含的是一个对函数的指针,因此person1和person2对象就是共享了在大局作用域中定义之及一个sayName()函数,这样做真解决了少单函数做同码事之问题,可是新题材还要来了:在全局作用域中定义之函数实际上只能给某对象调用,这叫全局作用域有接触名不符实。而重新让人口束手无策接受之凡,如果目标要定义很多方式,那么即使假设定义很多单构造函数,于是我们这自定义的援类型就丝毫不曾封装性可言了。好当,这些题目得以运用原型模式来缓解。

1.3原型模式

俺们创建的各个一个函数都产生一个prototye(原型)属性,这个特性是一个指南针,指向一个目标,而者目标的用是富含可以由特定类型的持有实例共享的特性和方式。如果依字面意思来明,那么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

 在此,我们用sayName()方法与具备属性直接助长到了Person的prototype属性中,构造函数变成了空函数。即使如此,也照例可透过调用构造函数来创造新目标,而且新对象还见面有同样之习性与办法。但与构造函数模式不同之是,新对象的这些性与道是出于所有实例共享的。换句话说,person1和person2访问的且是一样组属性和及一个sayName()函数。要明原型模式之干活原理,必须先理解ECMAScript中原型对象的性质。

1.3.1懂原型对象

不管什么时候,只要创造了一个新函数,就会依据同样组特定的平整为该函数创造一个prototype属性,这个特性指向函数的原型对象。在默认情况下,所有原型对象还见面自动获得一个constructor(构造函数)属性,这个特性包含一个对准prototype属性所在函数的指针。就拿前面的例子来说,Person.prototype.constructor指向Person。而经此构造函数,我们尚只是连续为原型对象上加任何性能与方式。

始建了打定义的构造函数之后,其原型对象默认只会收获constructor属性,至于其他办法,则都是于Object继承而来之。当调用构造函数创建一个新实例后,该实例的中间以包含一个指针(内部属性),指向构造函数的原型对象。在众多落实着,这个里面属性的名是__proto__,而且经过脚本可以拜到,而于其它实现着,这个特性对剧本则是全不可见的。不过,要明显的真要之一点,就是此连续有为实例与构造函数的原型对象中,而不是存在于实例与构造函数之间。

为前面使用Person构造函数和Person.prototype创建实例的代码为条例,下图显示了逐条对象之间的涉及:

图片 1

 上图显示了Person构造函数、Person原型属性和Person现有的一定量只实例之间的干。在此,Person.prototype指于了原型对象,而Person.prototype.constructor又指回了Person。原型对象被除去含有constructor属性之外,还包后来长的另属性。Person的每个实例——person1和person2且含一个中间属性,该属性仅仅对了Person.prototype;换句话说,它们和构造函数没有一直的干。此外,要那个注意的是,虽然当时片单实例都非带有属性和方式,但咱倒是可调用person1.sayName()。这是经过搜索对象属性之经过来贯彻的。

虽说以少数实现中无法访问到中间的__proto__性,但当所有实现中还可以由此isPrototypeOf()方法来规定目标之间是否在这种关涉。从实质上称,如果目标的__proto__针对调用isPrototypeOf()方法的目标(Person.prototype),那么是方式就是回来true,如下所示:

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

 这里,我们之所以原型对象的isPrototypeOf()方法测试了person1和person2.盖其的其中都出一个针对Person.prototype的指针,因此都回来了true.

在代码读取某个对象的某部属性时,都见面尽同样软搜索,目标是有所给定名字的特性。搜索首先由目标实例本身开始,如果当实例中找到了具有给定名字的性质,则回该属性之价,如果无找到,则继续查找指针指向的原型对象,在原型对象中搜寻具有给定名字的性质。如果当原型对象中找到了此特性,则归该属性的价。也就是说,在咱们调用person1.sayName()的时,会次推行两软找,再提问:“person1的原型有sayName属性吗?”答:“有。”于是,它便读取那个保存在原型对象吃之函数。当我们调用person2.sayName()时,将会见重现相同之摸索过程,得到一致之结果。而这多亏多单对象实例共享原型所保存的特性和方式的基本原理。(前面提到过,原型最初只有含有constructor属性,而拖欠属性为是共享的,因此可通过对象实例访问)

虽然可以通过对象实例访问保存于原型中的值,但却未能够由此对象实例更写原型中之值。如果我们当实例中上加了一个性能,而该属性和实例原型中之一个性质同名,那咱们虽以实例中开创该属性,该属性将会见荫原型中的良属性,来拘禁下面的例子:

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();
var person2 = new Person();

person1.name = 'Greg';
alert(person1.name); //"Greg"——来自实例
alert(person2.name); //"Nicholas"——来自原型

 在此例子中,person1的name被一个新值给挡了。但不论是访问person1.name或看person2.name且能健康地回值,即分别是”Greg”(来自目标实例)和“Nicholas”(来自原型)。当以alert()中做客person1.name不时,需要读取它的价值,因此便会见当是实例上寻找一个称吧name的性质。这个特性确实在,于是就回了其的价值如果无需再寻找原型了。当以平等的艺术访person2.name时,并不曾以实例上发现该属性,因此即使见面连续搜寻原型,结果当那里找到了name属性。

当为对象实例添加一个性能时,这个特性就见面遮掩原型对象被保留的同名属性,换句话说,添加这个特性只见面拦我们走访原型中之十分属性,但不见面修改好属性。即使用此特性设置为null,也止会以实例中安装这个特性,而不会见东山再起其对原型的总是。不过,使用delete操作符则足以了除去实例属性,从而得以让咱们会更访问原型中的习性,如下所示:

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();
var person2 = new Person();

person1.name = 'Greg';
alert(person1.name); //"Greg"
alert(person2.name); //"Nicholas"

delete person1.name;
alert(person1.name); //"Nicholas"——来自原型

 在此修改后底例证中,我们以delete操作符删除了person1.name,之前它保存的”Greg”值屏蔽了同名的原型属性。把其去后,就恢复了对原型中name属性的连续。因此,接下再调用person1.name时不时,返回的即使是原型中之name属性的值了。

行使hasOwnProperty()方法可以检测一个属性是有让实例中,还是存在于原型中。这个法子(不要忘记了她是起Object继承来的)只当受定属性存在叫对象实例中,才见面回到true。来拘禁下面的例子:

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();
var person2 = new Person();

alert(person1.hasOwnProperty('name')); //false

person1.name = 'Greg';
alert(person1.name); //"Greg"——来自实例
alert(person1.hasOwnProperty('name')); //true

alert(person2.name); //"Nicholas"——来自原型
alert(person2.hasOwnProperty('name')); //false

delete person1.name;
alert(person1.name); //"Nicholas"——来自原型
alert(person1.hasOwnProperty('name')); //false

 通过采取hasOwnProperty()方法,什么时候访问的凡实例属性,什么时看的凡原型属性就清清楚楚了。调用person1.hasOwnProperty(‘name’)时,只有当person1重写name属性后才见面回true。因为只有这候name才是一个实例属性,而非原型属性。下图显示了面例子中以不同情况下之兑现与原型的涉及(为了简单期间,图中略了和Person构造函数的关系)

图片 2

1.3.2原型与in操作符

有一定量种植方法使用in操作符:单独行使与当for-in循环中运用。在单身使用时,in操作符会在经过对象会访问为定属性时返回true。无论该属性是叫实例中还是原型中。看同样看下的例子:

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();
var person2 = new Person();

alert(person1.hasOwnProperty('name')); //false
alert('name' in person1); //true

person1.name = 'Greg';
alert(person1.name); //"Greg"——来自实例
alert(person1.hasOwnProperty('name')); //true
alert('name' in person1); //true

alert(person2.name); //"Nicholas"——来自原型
alert(person2.hasOwnProperty('name')); //false
alert('name' in person2); //true

delete person1.name;
alert(person1.name); //"Nicholas"——来自原型
alert(person1.hasOwnProperty('name')); //false
alert('name' in person1); //true

 在以上代码执行的全方位经过被,
name属性要么是一直当靶及看到的,要么是通过原型访问到之。因此,调用”name”
in person始终犹回true。无论该属性在吃实例中或者有于原型中。同时用hasOwnProperty和in操作符,就得确定该属性到底是存为对象吃,还是在于原型中。如下所示:

function hasOwnProperty(object, name) {
    return !object.hasOwnProperty(name) && (name in object);
}

 由于in操作符只要经过对象会访问到性就回到true,hasOwnProperty()只以性质在被实例中才回到true,因此若in操作符返回true而hasOwnProperty()返回false,就足以确定属性是原型中之性。下面来拘禁同样看上面定义之函数hasOwnProperty()的用法:

function Person() {}

Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function() {
    alert(this.name);
};

function hasOwnProperty(object, name) {
    return !object.hasOwnProperty(name) && (name in object);
}

var person = new Person();
alert(hasOwnProperty(person, 'name')); //true

person.name = 'Greg';
alert(hasOwnProperty(person, 'name')); //false

 在此处,name属性先是在于原型中,因此hasOwnProperty()返回true,当在实例中重写name属性后,该属性就有让实例中了,因此hasOwnProperty()返回false。

于使用for-in循环时,返回的凡具能通过对象看的、可枚举的(enumerated)属性,其中既包括在被实例中之习性,也囊括有于原型中的特性,屏蔽了原型中不可枚举属性(即设置了[[DontEnum]]记的习性)的实例属性为会见在for-in循环中回到。因为根据规定,所有开发人员定义之性都是可枚举的——只有IE例外。

IE的JScript实现着是一个bug,即屏蔽不可枚举属性的实例属性不会见产出于for-in循环中。例如:

var o = {
    toString: function() {
        return 'My Object';
    }
}

for (var prop in o) {
    if (prop == 'toString') {
        alert('Found toString'); //在IE中不会显示
    }
}

当以上代码运行时,应该会显示一个警告框,表明找到了toString()方法。这里的靶子o定义了一个称为也toString()的计,该方法屏蔽了原型中(不可枚举)的toString()方法。在IE中,由于其促成认为原型的toString()方法被从及了[[DontEnum]]号就应当过了该属性,结果我们尽管未会见相警告框。该bug会影响默认不可枚举的所有属性和方法,包括:hasOwnProperty()、propertyIsEnumerable()、toLocaleString()、toString()和valueOf()。有的浏览器也也constructor和prototype属性打上了[[DontEnum]]号,但当时并无是具备浏览器联合之做法。

1.3.2 更简便的原型做法

读者大概注意到了,前面例子中各添加一个性和艺术就是设讹一全person.prototype,为减少非必要之输入,也以打视觉上更好地卷入原型的效用,更宽泛的做法是故一个富含有属性和措施的目标字面量来更写尽原型对象,如下面的事例所示:

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

 在面的代码中,我们用person.prototype设置为当一个坐目标字面量形式创建的新对象。最终结出同样,但有一个不等:constructor属性不再对Person了。前面介绍过,每创建一个函数,就会见又创造它的prototype对象,这个目标也会活动获取constructor属性。而我们于此以的语法,本质上完全重复写了默认的prototype对象,因此constructor属性也便成为了初对象的constructor属性(指向Object构造函数),不再对Person函数。此时,尽管instanceof操作符还能够回来正确的结果,但通过constructor已经力不从心确定目标的项目了,如下所示:

function Person() {}

Person.prototype = {
    name: 'Nicholas',
    age: 29,
    job: 'Software Engineer',
    sayName: function() {
        alert(this.name);
    }
};

var person = new Person();

alert(person instanceof Object); //true
alert(person instanceof Person); //true
alert(person.constructor === Object); //true
alert(person.constructor == Person); //false

 在这个,用instanceof操作符测试Object和Person仍然返回true,但constructor属性则等于Object而无顶Person了。如果constructor值真的死关键,可以像下这样特别用她装回适当的值:

function Person() {}

Person.prototype = {
    constructor: Person,
    name: 'Nicholas',
    age: 29,
    job: 'Software Engineer',
    sayName: function() {
        alert(this.name);
    }
};

var person = new Person();

alert(person instanceof Object); //true
alert(person instanceof Person); //true
alert(person.constructor == Object); //false
alert(person.constructor == Person); //true

 以上代码特意包含了一个constructor属性,并将她的价值设置也Person,从而确保了该属性能够访问到当的价值。

1.3.4 原型的动态性

由当原型中查找值的历程是相同次于搜索,因此我们对原型对象所召开的旁修改都能够即时从实例上体现出——即使是先行创造了实例后修改原型为是这般,请圈下面的事例:

function Person() {}

var person = new Person();

Person.prototype.sayHi = function() {
    alert('hi');
};

person.sayHi(); //"hi" (没有问题!)

 以上代码先创造了Person的一个实例,并将那保存于person中。然后,下一样漫长告句以Person.prototype中补充加了一个办法sayHi()。即使person实例是当加上新方式之前创建的,但它还可以拜是新点子。其故可以概括为实例与原型之间的麻痹大意连接关系。当我们调用person.sayHi()时,首先会见当实例中检索名也sayHi的性质,在从来不找到的状况下,会延续找原型,因为实例与原型之间的连天只不过是一个指南针,而未一个副本,因此便可以原型中找到新的sayHi属性并返保存于那边的函数。

尽管可以天天为原型添加属性和章程,并且修改能够立即以装有目标实例中体现出,但若是是再度写尽原型对象,那么情况便未等同了。我们掌握,调用构造函数时会为实例添加一个对最初原型的__proto__指南针,而将原型修改为任何一个对象就当隔绝了构造函数与前期原型之间的联系,请记住,实例中之指针仅对原型,而不针对构造函数,请圈下面2独对照的例子:

function Person() {}

Person.prototype = {
    constructor: Person,
    name: 'Nicholas',
    age: 29,
    job: 'Software Engineer',
    sayName: function() {
        alert(this.name);
    }
};

var person = new Person();

person.sayName(); //"Nicholas" 

function Person() {}

var friend = new Person();

Person.prototype = {
    constructor: Person,
    name: 'Nicholas',
    age: 29,
    job: 'Software Engineer',
    sayName: function() {
        alert(this.name);
    }
};

friend.sayName(); //VM319:15 Uncaught TypeError: person.sayName is not a function(…)

在次独例证中,我们先是创建了Person的一个实例,然后以重写了该原型对象,然后在调用Person.sayName()时起了错误,因为person指向的原型中无分包以化名字命名的性,下图展示了这过程的内幕:

图片 3

上图被可见见,重写原型对象切断了现有原型与外之前曾有的目标实例之间的联络。它们引用的依然是初期的原型。

1.3.5 原生对象的原型

原型模式之重要不仅体现在创建于定义类型方面,
就连有原生的援类型,都是以这种模式开创的。所有原生引用类型(Object、Array、String等)都以该构造函数的原型上定义了法子。例如,在Array.prototype中得以找到sort()方法,而当String.prototype中好找到subString()方法,如下所示:

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

经原生对象的原型,不仅可博得富有默认方法的援,而且为可定义新点子。可以像修改由定义对象的原型一样修改原生对象的原型,因此可天天添加方法。下面的代码就于核心包装档次String添加了一个称也startsWith()的主意:

String.prototype.startsWith = function(text) {
    return this.indexOf(text) == 0;
};

var msg = 'Hello world';
alert(msg.startsWith('Hello')); //true

此地新定义之starsWith()方法会在流传的文件位于一个字符串开始时回来true。既然方法被补充加给了String.prototype,那么当前环境被之兼具字符串就都好调用它。由于msg是字符串,而且后台会调用String基本包装函数创建是字符串,因此通过msg就好调用startsWith()方法。(尽管可以这么做,但是非引进以产品化的次序中修改原生对象的原型。如果因某实现中短有方法,就于原生对象的原型中添加这个方式,那么当以其他一个支持该方式的兑现着运作代码时,就可能会见招致命名冲突。而且,这样做为可能会见飞地再次写原生方法。)

1.3.6 原型对象的问题

原型模式吗不是没缺陷,首先,它大概了吗构造函数初始化参数就等同环,结果有实例在默认情况下都用收获一致的属于性值。虽然当时会以某种程度上带有勿便利,但尚免是原型的最为充分题目,原型模式之极致要命题材是由于其共享的个性所招的。

原型中所有属性是吃不少实例共享的,这种共享于函数非常适宜。对于那些含有基本值的性能倒也说之病逝,毕竟(如前的事例所示),通过当实例上添加一个暨名属性,可以隐藏原型中的对应属性,然而,对于富含引用类型值的性质来说,问题就是比较突出了,来拘禁下的例证:

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"

以是,Person.prototype对象来一个号称吧friends的性能,该属性包含一个字符串数组,然后创建了Person的星星点点单实例,接着,修改了person1.friends援的往往组,向数组中上加了一个字符串,由于friends数组存在于Person.prototype而非person1中,所以刚刚提到的改为会见通过person2.friends(与person1.friends指为同一个数组)反映出来。假如我们的初衷就是是比如说这么以具备实例中共享一个频组,那么对于这结果我们管言语可说,可是,实例一般都是要是产生属于自己的一切性能之,而之题目正是我们很少看到有人独使用原型模式的来由所在。

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

创于定义类型的极致广泛方式,就是成使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的特性,结果,每个实例都见面时有发生温馨的一模一样卖实例属性之副本,但还要又共享着对法的援,最特别限度地省了内存。另外,这种混成模式还支持于构造函数传递参数,可谓是汇两种模式的丰富,下面的代码重写了前方的例子:

function Person(name, age, job) {
    this.name = name;
    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,Court,Van"
alert(person2.friends); //"Shelby,Court"
alert(person1.friends == person2.friends); //false
alert(person1.sayName == person2.sayName); //true

 在斯事例中,实例属性都是以构造函数中定义的,而由于有实例共享的性constructor和措施sayName()则是以原型中定义的,而改了person1.friends(向其中添加了一个新字符串),并无会见影响到person2.friends,因为她各自引用了不同的数组。

这种构造函数与原型混成的模式,是时当ECMAScript中运用最广泛,认同度最高的自定义类型的措施。可以说,这是用来定义引用类型的一律栽默认模式。

1.5 动态原型模式

发出其他OO语言经验的开发人员在张独立的构造函数和原型时,很可能会见感觉特别纳闷。动态原型模式正是从为解决这个题材的一个方案,它将拥有消息还封闭装在了构造函数中,而经过当构造函数中初始化原型(仅以必要之状下),又保持了又以构造函数和原型的长。换句话说,可以经过检查有应该有的方是否可行,来控制是否如初始化原型。来拘禁一个例:

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 person = new Person('Nicholas', 29, 'Software Engineer');
person.sayName(); //"Nicholas"

留神构造函数中的法门下面的if语句,这里只在sayName()方法无有的事态下,才见面拿它们上加至原型中。这段代码只会于正调用构造函数时才见面履行。此后,原型已经完成初始化,不需要更举行什么修改了。不过假如切记,这里对原型所举行的修改,能够即时以装有实例中拿走体现。因此,这种办法真的可以说深全面。其中,if语句检查的可是初始化之后应该留存的另外性质与艺术——不必因此同格外堆if语句检查每个属性与办法,只要检查其中一个即可。对于下这种模式开创的对象,还足以应用instanceof操作符确定其的型。(使用动态原型模式时,不克利用对象字面量重写原型。前面已经说明了了,如果当都创办实例的状下又写原型,那么尽管会见切断现有实例与新原型之间的关系)

1.6 寄生构造函数模式

一般说来,在详谈的几乎种植模式都非适用的情下,可以运用寄生(parasitic)构造函数模式。这种模式之着力考虑是创建一个函数,该函数的图只是是包裹创建对象的代码,然后重新返回新创建的目标,但于表上看,这个函数又坏像是鹤立鸡群的构造函数,下面是一个例:

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 person = new Person('Nicholas', 29, 'Software Engineer');
person.sayName(); //"Nicholas"

于这事例中,Person函数创建了一个新对象,并因为相应的特性和方式初始化该目标,然后以回到了此目标。除了采用new操作符并拿下的包装对象叫做构造函数之外,这个模式和工厂模式其实是千篇一律摸一样的,构造函数在无归回值的状下,默认返回新对象实例。而经过当构造函数的终极添加一个return语句,可以重写调用构造函数时返回的价。

其一模式可以特种的动静下用来啊目标创建构造函数。假设我们怀念创立一个持有额外措施的独特数组,由于匪克一直修改Array构造函数,因此好利用此模式:

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"

alert(colors instanceof SpecialArray); //false
alert(colors instanceof Object); //true
alert(colors.constructor == SpecialArray); //false

每当这个事例中,我们创建了一个为SpecialArray的构造函数,在此函数内部,首先创建了一个屡组,然后push()方法(用构造函数接收及的装有参数)初始化了往往组的价。随后,又让数组实例添加了一个toPipedString()方法,该办法返回以竖线分隔的数组值。最后,将反复组因为函数值的样式返回。接着,我们调用了 SpecialArray构造函数,向内传入了用来初始化数组的值,此后而调用了toPipedString()方法。

关于寄生构造函数模式,有几许急需征:首先,返回的靶子同构造函数或者和构造函数的原型属性之间莫提到,也就是说,构造函数返回的对象以及当构造函数外部创建的目标没呀不同,为是,不能够依赖instanceof操作符来确定目标类型。由于有上述问题,我们建议在可以使用其它模式之景象下,不要采取这种模式。

1.7 稳妥构造函数模式

稳对象(durable
objects),指的凡没集体性质,而且该法呢不引用this的对象。稳妥对象极其适合在一部分康宁的条件中(这些条件面临见面禁止以this和new),或者以防止数据为另外应用程序(如Mashup程序)改动时行使。稳妥构造函数遵循以及寄生构造函数类似之模式,但来星星点点点不同:一是新创建目标的实例方法无引用this;二是无使new操作符调用构造函数。按照稳妥构造函数的渴求,可以以眼前的Person构造函数重写如下:

function Person(name, age, job) {

    //创建要返回的对象
    var o = new Object();

    //可以在这里定义私有变量和函数

    //添加方法
    o.sayName = function() {
        alert(name);
    };

    //返回对象
    return o;
}

var person = Person('Nicholas', 29, 'Software Engineer');
person.sayName(); //"Nicholas"

alert(person instanceof Person); //false
alert(person instanceof Object); //true
alert(person.constructor == Person); //false

专注,在因为这种模式创造的靶子中,除了使用sayName()方法外,没有任何方式看name的价。变量person中保存的是一个妥善对象,而除调用sayName()方法外,没有别的方法可以拜其他数据成员。即使有另外代码会让这个目标上加计还是数成员,但也非可能发别的艺术访问传入到构造函数中之老数据。稳妥构造函数模式提供的这种安全性,使得它非常适合在好几安全实施环境——例如,ADsafe(www.adsafe.org)和Caja(http://code.google.com/google-caja/)提供的环境下使用。(与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此instanceof操作符对这种对象也没有意义)

2,继承

承是OO语言中的一个太人津津乐道的概念。许多OO语言都支持少数种持续方式:接口继承和落实连续。接口继承只持续方法签名,而实现连续则持续实际的艺术。如前所述,由于函数没有签字,在ECMAScript中无法落实接口继承。ECMAScript只支持促成持续,而且那个落实连续主要是依赖原型链来实现的。

2.1 原型链

ECMAScript中讲述了原型链的定义,并以原先型链作为贯彻连续的要紧方式。其基本考虑是用原型为一个援类型继承另一个引用类型的属性与方法。简单回顾一下构造函数、原型和实例的涉嫌:每个构造函数都起一个原型对象,原型对象都含有一个针对构造函数的指针,而实例都饱含一个针对性原型对象的内部指针。那么,假如我们吃原型对象等另一个品类的实例,结果会怎么啊?显然,此时底原型对象将涵盖一个针对性任何一个原型的指针,相应地,另一个原型中为蕴藏在一个针对性任何一个构造函数的指针。假如另一个原型又是外一个型的实例,那么上述提到还是成立,如此罕见推进,就结成了实例与原型的链条。这即是所谓原型链的概念。

心想事成原型链有一样栽基本模式,其代码大致如下:

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

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

如上代码定义了个别独品种:SuperType和SubType 。每个品种分别发生一个特性和一个术。它们的根本区别是SubType继承了SuperType,而后续是通过创设SuperType的实例,并以该实例赋给SubType.prototype实现的,实现之本色是更写原型对象,代的为一个初类型的实例。换句话说,原来在于SuperType的实例中的装有属性和章程,现在也设有于SubType.prototype被了,在成立了后续关系下,我们于SubType.prototype添加了一个措施,这样就算当此起彼伏了SuperType的性与方式的底蕴及而补加了一个初措施。这个事例中之落实和构造函数与原型之间的干如下图所示:

图片 4

每当地方的代码中,我们尚无动SubType默认提供的原型,而是吃她换了一个新原型:这个新原型就是是SuperType的实例。于是,新原型不仅抱有作为一个SuperType的实例所怀有的全套性与道,而且那个中间还有一个指南针,指向了SuperType的原型。最终结果虽是这般的:instance指向SubType的原型,SubType的原型又指向SuperType的原型。getSuperValue()方法仍然还以SuperType.prototype中,但property则位于SubType.prototype中,这是盖property是一个实例属性,而getSuperValue()则是一个原型方法。既然SubType.prototype现在凡是SuperType的实例,那么property当然就在该实例中了。此外,需注意instance.constructor现在现对的凡SuperType,这是以原本的SubType.prototype中的constructor被再写了之因由(实际上,不是SubType的原型的constructor属性被再次写了,而是SubType的原型指向了别一个对象——SuperType的原型,而这原型对象的constructor属性指向的是SuperType。)

透过实现原型链,本质上扩大了面前介绍的原型搜索机制。当因为读取模式访问一个实例属性时,首先会见以实例中摸索该属性,如果没有找到该属性,则会延续找实例的原型。在经过原型链实现连续的情况下,搜索过程尽管可以沿着原型链继续上扬。就将点的例证来说,调用instance.getSuperValue()会更三只寻步骤:1)搜索实例;2)搜索SubType.prototype;3)搜索SuperType.prototype,最后一步才见面找到该措施。在寻不至性和艺术的情景下,搜素过程接连要一律围绕一围绕地前执行至原型链末端才会告一段落下来。

2.1.1 别忘记默认的原型

骨子里,前面例子中展示的原型链还不见一缠绕。我们知晓,所有援类型默认都累了Object,而以此累也是经原型链来实现之。大家要铭记,所有函数的默认原型都是Object的实例,因此默认原型都见面蕴藏一个中指针,指向Object.prototype.这吗多亏有由定义类型且见面继续toString()、valueOf()等默认方法的根本原因。所以,我们说地方例子展示的原型链中还该包括另外一个即便变成层次,下图为我们来得了拖欠例子中整的原型链。

图片 5

一律词话,SubType继承了SuperType,而SuperType继承了Object。当调用instance.toString()时,实际上调用的凡保存于Object.prototype中之充分方式。

2.1.2 确定原型与实例的关联

可以通过简单栽办法来规定原型和实例之间的关联。第一栽方法是运instanceof操作符,只要用是操作符来测试实例与原型链中出现了之构造函数,结果就是见面回true。以下几行代码就证明了这或多或少:

alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

出于原型链的涉嫌,我们可说instance是Object、 SuperType、SubType中另外一个列的实例,因此,测试就三只构造函数的结果还回了true.

次种办法是下isPrototypeOf()方法。同样,只要是原型链中出现过的原型,都可说凡是该原型链所派生的实例的原型。因此isPrototypeOf()方法为会见回true。如下所示:

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

2.1.3 谨慎地定义方法

子类型有时候需要更写超类型的某方法,或者需要添加超类型中莫存在的某部方法,但不管怎么样,给原型添加方法的代码一定要是在替换原型的口舌之后,来拘禁下的事例:

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

//重写超类型中的方法
SubType.prototype.getSuperValue = function() {
    return false;
};

var instance = new SubType();
alert(instance.getSuperValue()); //false

上述代码中,加入色情背景的片段是少数只方式的概念。第一独方法getSubValue()被上加至了SubType中,第二单点子getSuperValue()是原型链中已经有的一个方式,但再也写是方法以见面遮掩原来的充分方式。换句哈说,当通过SubType的实例调用getSuperValue()时,调用的即使是此还定义之艺术,但经SubType的实例调用getSuperValue()时,还会继续调用原来的挺方式。这里要格外留意的是,必须以就此SuperType的实例替换原型之后,再定义这半独方法。

还有一些急需提醒读者,即当通过原型链实现连续时,不克使用对象字面量创建原型方法,因为这样做就是会见更写原型链,如下面的事例所示:

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

    someOtherMethod: function() {
        return false;
    }
};

var instance = new SubType();
alert(instance.getSuperValue()); //VM627:1 Uncaught TypeError: instance.getSuperValue is not a function(…)

以上代码展示了刚刚把SuperType的实例赋值给原型,紧接着又拿原型替换成一个目标字面量而致的问题。由于本的原型包含的凡一个Object实例,而非SuperType的实例,因此我们着想中的原型链已经切断——SubType和SuperType之间都远非关系了。

2.1.4 原型链的题目

原型链虽然非常强劲,可以为此其来落实持续,但它也存在部分题目。其中,最要的问题来自包含引用类型值的原型。想必大家还记得,我们前面介绍了含引用类型值的原型属性会受抱有实例共享,而当时也多亏为何而于构造函数中,而休是当原型对象中定义属性的因由。在经过原型实现持续时,原型实际上会化为任何一个类的实例,于是,原先的实例属性也便天经地义地成了今之原型属性了。下面代码可以用来证实这个题材:

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"

此例子中之 SuperType构造函数定义了一个colors属性,该属性包含一个数组(引用类型值)。SuperType的每个实例都见面起独家包含自己数组的colors属性。当SubType通过原型链继承了SuperType之后,SubType.prototype就改为了SuperType的一个实例,因此它呢有所了一个其自己的colors属性——就跟专门创建了一个SubType.prototype.colors属性一样。但结果是呀也?结果是SubType的有所实例都见面共享这一个colors属性。而我们本着instance1.colors的修改能够透过instance2.colors体现出去,就曾经尽证实了就一点。

原型链的第二单问题是:在创建子类型的实例时,不克望超类型的构造函数中传递参数。实际上,应该就是没有法于匪影响有目标实例的景象下,给超类型的构造函数传递参数。有鉴于此,再长前面刚讨论过的原型中隐含引用类型值所带的题目,实践备受异常少会独自使用原型链。

2.2 使用构造函数

当缓解原型中含有引用类型值所带问题之过程遭到,开发人员开始以相同种植叫做借用构造函数(constructor
stealing)的技能(有时候为叫做伪造对象要藏延续)。这种技能的主干考虑相当简单,即于子类型构造函数的中间调用超类型构造函数。别忘了,函数只不过是在特定执行环境受到执代码的靶子,因此通过动用apply()和call()方法吧堪在(将来)新创办的对象上执行构造函数,如下所示:

function SuperType() {
    this.colors = ['red', 'blue', 'green'];
}

function SubType() {
    //继承了SuperType
    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"

代码中加背景的那么一行代码
“借调”了超类型的构造函数。通过动用call()方法(或apply()方法为得),我们其实是在(未来将要)新创办的SubType实例的条件下调用了SuperType构造函数。这样一来,就会于新SubType目标及推行SuperType()函数中定义的享有目标初始化代码。结果,SubType的每个实例就都见面具备自己的colors属性的副本了。

2.2.1 传递参数

对立于原型链而言,借用构造函数有一个挺特别之优势,即可以在子类型构造函数中朝超类型构造函数传递参数。请看下的例证:

function SuperType(name) {
    this.name = name;
}

function SubType() {
    //继承了SuperType,同时还传递了参数
    SuperType.call(this, 'Nicholas');

    //实例属性
    this.age = 29;
}

var instance = new SubType();
alert(instance.name); //"Nicholas"
alert(instance.age); //29

 以上代码中之SuperType只接受一个参数name,该参数会一直与给一个性。在SubType构造函数内部调用SuperType构造函数时,实际上是吧SubType的实例设置了name属性。为了保证SubType构造函数不见面再度写子类型的性,可用在调用超类型构造函数之后,再补偿加应该于子类型中定义的特性。

2.2.2 借用构造函数的题材

倘单独是假构造函数,那么为以无法避免构造函数模式在的题目——方法还以构造函数中定义,因此函数复用就不能谈起了。而且,在超类型的原型中定义的章程,对子类型而言也是不可见的,结果有所项目且不得不以构造函数模式,考虑到这些题目,借用构造函数的技能呢是死少单独使用的。

2.3 组合继承

组成继承(combination
inheritance),有时候为叫做伪经典延续,指的是拿本来型链和借构造函数的技巧整合及同样片,从而发挥要者的长之同等种持续模式。其背后的笔触是应用原型链实现对原型属性与办法的继承,而由此假构造函数来实现对实例属性的持续。这样,既通过以原型上定义方法实现了函数复用,又能管每个实例都发她和谐之属性。下面来拘禁一个例证:

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.sayAge = function() {
    alert(this.age);
};

var instance1 = new SubType('Nicholas', 29);
instance1.colors.push('black');
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas"
instance1.sayAge(); //29

var instance2 = new SubType('Greg', 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg"
instance2.sayAge(); //27

当此事例中,SuperType构造函数定义了片只属性,name和colors。SuperType的原型定义了一个道sayName()。SubType构造函数在调用SuperType构造函数时传入了name参数,紧接着又定义了其好之性age。然后,将SuperType的实例赋值给SubType的原型,然后以当该新原型上定义了章程sayAge(),这样一来,就可以吃简单只例外之SubType实例既分别持有好性——包括colors属性,又得以同一的方法了。

结缘继承避免了原型链和假构造函数的瑕疵,融合了他们的长,成为JavaScript中最为常用的延续模式。而且,instanceof和isPrototypeOf()也能用于识别基于组合继承创建的目标。

2.4 原型式继承

道格拉斯 克罗克福德在2006年描绘了同篇稿子,题吗Prototypal Inheritance in
JavaScript(JavaScript中之原型式继承)。在这首文章被,他牵线了一致种植实现连续的方,这种措施并无行使严格意义上之构造函数。他的想法是借助原型可以因已有的对象创建新对象,同时还不用为此创造于定义类型。为了上这目的,他被闹了如下函数:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

每当object()函数内部,先创造了一个少的构造函数,然后拿盛传的靶子作为之构造函数的原型,最后回来了这个临时类型的新实例。从实质上提,object()对传播其中的靶子实施了一致不良浅复制。来拘禁下的例子:

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';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barble');

alert(person.friends); //"Shelby,Court,Van,Rob,Barble"

克罗克福德主持的这种原型式继承,要求你不能不发一个目标足以用作其它一个靶的根基。如果生如此一个目标的说话,可以把它传递让object()函数,
然后还因现实要求对获的靶子加以修改即可。在斯事例中,可用作为其它一个函数基础的凡person对象,于是我们把它传播到object()函数中,然后该函数就见面返回一个初目标,这个新目标将person作为原型,所以其的原型中不怕富含一个为主类型值属性和一个引用类型值属性。这表示person.friends不仅属于person所有,而且也会受anotherPerson以及yetAnotherPerson共享。实际上,这虽一定给以创了person对象的一定量个副本。

在未曾必要兴师动众地开创构造函数,而就想叫一个目标及任何一个靶保障类似之状态下,原型式继承是了好胜任的。不过弯忘了,包含引用类型值的习性始终犹见面共享相应的值。就比如用原型模式一样。

2.5 寄生式继承

寄生式(parasitic)继承是与原型式继承紧密相关的等同栽思路,并且相同为是由于克罗克福德推而广之的。寄生式继承的笔触和构造函数和工厂模式类似,即创办一个一味用于封装继承过程的函数,该函数在其中以某种方式来提高对象,最后更譬如真正是它们做了有工作同样返回对象。以下代码示范了寄生式继承模式:

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

 在这例子中,createAnother()函数接收了一个参数,也便是快要当新对象基础之对象。然后,把这目标(original)传递给object()函数,将回到的结果赋值给clone,再为clone对象上加一个新点子sayHi(),最后回到clone对象。可用像下的例证来采取createAnother()函数:

var person = {
    name: 'Nicholas',
    friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

 这个事例中的代码基于person返回了一个新对象——anotherPerson。新目标不仅拥有person的保有属性和法,而且还有温馨的sayHi()方法。

当首要考虑对象要未是打定义类型以及构造函数的情形下,寄生式继承也是如出一辙种中之模式。前面示范继承模式时利用的object()函数不是必须的。任何能够回到新目标的函数都适用于斯模式。(使用寄生式继承来吗目标上加函数,会出于无可知成就函数复用而减低效率,这一点跟构造函数模式类似)

2.6 寄生组合式继承

面前说罢,组合继承是JavaScript最常用的接轨模式,不过,它吗时有发生友好之不足。组合继承的题目就是任什么动静下,都见面调用两糟糕超类型构造函数:一蹩脚是当创建子类型原型的上,另一样次等是于子类型构造函数内部。没错,子类型最终见面蕴藏超类型对象的总体实例属性,但我们只能以调用子类型构造函数时再度写这些性。再来拘禁一样拘禁下面组合继承的例证:

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); //第二次调用SuperType()

    this.age = age;
}

SubType.prototype = new SuperType(); //第一次调用SuperType()

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

 带黄色背景的行中是调用SuperType构造函数的代码。在首先不善调动用SuperType构造函数时,SubType.prototype会得到两只属性:name和colors;他们还是SuperType的实例属性,只不过现在居SubType的原型中。当调用SubType构造函数时,又见面调用一差SuperType构造函数,这同次而在初目标及开创了实例属性name和colors。于是,这半个特性就挡了原型中之一定量只跟名属性。下图显示了上述过程:

图片 6

倘若达到图所示,有星星点点组name和colors属性:一组在实例上,一组于SubType原型中。这即是调动用有限不成SuperType构造函数的结果。好当我们曾找到了缓解者题材的方法——寄生组合式继承。

所谓寄生组合式继承,即通过借用构造函数来延续属性,通过原型链的混成形式来继承方法。其偷的基本思路是:不必为负定子类型的原型而调用超类型的构造函数,我们所要之只有就是是超类型原型的一个副本而已。本质上,就是利用寄生式继承来延续超类型的原型,然后再以结果指定给子类型的原型。寄生组合式继承的基本模式如下所示:

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

夫示例中之inheritPrototype()函数实现了寄生组合式继承的极端简单易行款式。这个函数接收两单参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创办超类型原型的一个副本。第二步是吗创造的副本添加constructor属性,从而弥补因再也写原型而去的默认的constructor属性。最后一步,将新创造的目标(即副本)赋值给子类型的原型。这样,我们就好调用inheritPrototype()函数的语句,去替换前面例子中也子类型原型赋值的报句子了,例如:

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

inheritPrototype(SubType, SuperType);

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

 这个事例的过人效率体现于它只调用平等不成SuperType构造函数,并且用避免了当SubType.prototype上创办不必要之、多余的性能。于此同时,原型链还会保障无转移;因此还能够正常下instanceof和isPrototypeOf()。开发人员普遍认为寄生组合式继承是援类型最精彩之接续范式。(YUI的YAH.lang.extand()方法以了寄生组合继承,从而被这种模式首不行面世于了一个用广泛的JavaScript库中,要打听关于YUI的重新多信息,请看http://developer.yahoo.com/yui)

小结:

ECMAScript支持面向对象(OO)编程,但不使用类或者接口。对象足以当代码执行过程中创造及提高,因此有所动态性而未严加定义之实业。在并未看似的情事下,可以采用下列模式创建对象:

a,工厂模式,利用简易的函数创建对象,为目标上加属性和章程,然后回来对象。这个模式后来吃构造函数模式所替代。

b,构造函数模式,可以创造于定义引用类型,可以像创建内置对象实例一样采用new操作符,不过,构造函数模式吧发出欠缺,即其的每个成员还心有余而力不足赢得复用,包括函数。由于函数可以无囿于为任何对象(即和对象有松弛耦合的特征),因此未曾理由不以差不多个目标中共同享函数。

c,原型模式,使用构造函数的prototype属性来指定那些应该共享的属性与办法,组合使用构造函数模式以及原型模式时,使用构造函数定义实例属性,而以原型定义共享的特性和道。

JavaScript主要透过原型链实现持续,原型链的构建是由此将一个项目的实例赋值给其他一个构造函数的原型实现之。这样,子类型就会访问超类型的享有属性与办法,这一点以及主导型的连续很相似。原型链的题材是目标实例共享有继续的特性和措施,因此无对劲单独行使。解决之题目之技巧是假构造函数,即在子类型构造函数的中间调用超类型构造函数。这样便可以就每个实例都有着友好之性能,同时还能确保单独下构造函数模式来定义类型。使用最多之累模式是组成继承,这种模式采用原型链继承共享的性能和办法,而由此假构造函数继承实例属性。

另外,还在下列可供应选择的接续模式:

a,原型式继承,可用在不必预先定义构造函数的场面下促成持续,其庐山真面目是行对加对象的浅复制,而复制得到的副本还足以获更加改造。

b,寄生式继承,与原型继承非常相似,也是基于某个对象或少数信息创建一个目标,然后增强对象,最后回来对象。为了解决做继承模式由于频繁调用超类型构造函数而致的不如效率问题,可用将这个模式与组合继承并行使。

c,寄生组合式继承,集寄生式继承和重组继承的助益于寥寥,是实现冲项目继承的最实用方法。