从实际意义上说,Javascript不是面向对象语言。它不提供传统的继承方式,但它提供了一种原型继承的方法,并使用自己提供的原型属性来实现继承。

原型与原型链

在原型继承之前,应该先说明原型和原型链,毕竟这是实现原型继承的基础。

在Javascript中,每个函数都有一个原型属性的原型是指它的原型,并通过该函数创建的对象也有原型和函数原型的__proto__属性是一个对象,这样的对象会有一个__proto__其原型,这层深度到对象的原型对象,从而形成原型链。下图为原型,在Javascript原型链之间的关系的一个很好的解释。



每个函数的功能是创建一个对象,所以每个函数也有一个__proto__属性指向的函数的函数原型,指出真正的原型链的每个对象的__proto__属性重要,不是函数的原型属性。

原型继承

基本模式

复制代码代码如下所示:

var父=函数(){()

this.name = 'parent;

};

parent.prototype.getname =函数(){

返回this.name;

};

parent.prototype.obj = { 1 };

var子函数(){()

this.name =';

};

child.prototype =新的母公司();

var父=新父();

新子();

console.log(parent.getname()); / /母

console.log(child.getname()); / /孩子



这是实现原型继承的最简单的方法。它直接将父类的对象分配给子类构造函数的原型,这样子类的对象就可以访问父类和父类构造函数的原型中的属性:



这种方法的优点是显而易见的,方法是很简单的,不需要任何特殊的操作;同时,缺点也很明显,如果子类需要在初始化基类构造函数一样,所以在子类的构造函数重复父类中的操作:

复制代码代码如下所示:

var父=函数(名称){

this.name = name'parent| |;

};

parent.prototype.getname =函数(){

返回this.name;

};

parent.prototype.obj = { 1 };

var子=函数(名称){

this.name = name'child| |;

};

child.prototype =新的母公司();

VaR母=新的母('myparent);

VaR的孩子=新的儿童('mychild);

console.log(parent.getname()); / /我父母所养

console.log(child.getname()); / /我的孩子



上述情况只需要初始化name属性,如果初始化工作正在增加,这是非常不方便的,因此有改进的方法。

借用构造函数

复制代码代码如下所示:

var父=函数(名称){

this.name = name'parent| |;

};

parent.prototype.getname =函数(){

返回this.name;

};

parent.prototype.obj = { 1 };

var子=函数(名称){

Parent.apply(这个参数);

};

child.prototype =新的母公司();

VaR母=新的母('myparent);

VaR的孩子=新的儿童('mychild);

console.log(parent.getname()); / /我父母所养

console.log(child.getname()); / /我的孩子



上面的方法通过调用父类的构造函数来执行子类构造函数中相同的初始化,因此不管父类中进行了多少初始化,子类都可以执行相同的初始化。父类构造函数执行两次,一次在子类构造函数中,每次分配子类原型时都是多余的,所以我们仍需要改进。

复制代码代码如下所示:

var父=函数(名称){

this.name = name'parent| |;

};

parent.prototype.getname =函数(){

返回this.name;

};

parent.prototype.obj = { 1 };

var子=函数(名称){

Parent.apply(这个参数);

};

child.prototype = parent.prototype;

VaR母=新的母('myparent);

VaR的孩子=新的儿童('mychild);

console.log(parent.getname()); / /我父母所养

console.log(child.getname()); / /我的孩子



所以我们只需要在子类的构造函数执行父类的构造函数,可以继承父类的属性的原型,它也与心灵的原型,是需要重用内容的原型,我们只是继承了可重用内容的原型。这种方法的原型如下:



临时构造函数模式(圣杯模式)

上述模型改进的构造函数版本借仍然是一个问题,它的父类的原型直接分配到子类的原型,这将导致一个问题,如果子类原型的改变,那么改变也会影响到父类的原型,从而影响父对象,这肯定不是我们想要的看到的。为了解决这个问题,有一个临时的构造函数模式。

复制代码代码如下所示:

var父=函数(名称){

this.name = name'parent| |;

};

parent.prototype.getname =函数(){

返回this.name;

};

parent.prototype.obj = { 1 };

var子=函数(名称){

Parent.apply(这个参数);

};

新的(函数){ };

f.prototype = parent.prototype;

child.prototype =新的f();

VaR母=新的母('myparent);

VaR的孩子=新的儿童('mychild);

console.log(parent.getname()); / /我父母所养

console.log(child.getname()); / /我的孩子



该方法的原型继承图如下所示:



这是很容易看到,父类的原型样机,进入一个暂时的F构造函数子类之间,子类和父类的原型样机之间的切割,如原型进行修改不会影响到父类的原型。

我的方法

Javascript的方式结束的圣杯,但无论哪种方法,有一个问题,是不容易找到的,你可以看到我的原型属性of'parent增加了一个obj对象文字的属性,但它从来没有被使用。我们看看圣杯图案的底部。

复制代码代码如下所示:

var父=函数(名称){

this.name = name'parent| |;

};

parent.prototype.getname =函数(){

返回this.name;

};

parent.prototype.obj = { 1 };

var子=函数(名称){

Parent.apply(这个参数);

};

新的(函数){ };

f.prototype = parent.prototype;

child.prototype =新的f();

VaR母=新的母('myparent);

VaR的孩子=新的儿童('mychild);

console.log(孩子。obj,); / / 1

console.log(母。obj,); / / 1

孩子。obj = 2;

console.log(孩子。obj,); / / 2

console.log(母。obj,); / / 2



在这种情况下,当我修改子对象,同时对象,在父类的原型也将被修改,这将发生一起分享共同的原型同样的问题。这是因为我们看孩子的时候。obj,我们会发现沿着原型链的父类的原型,然后找到对象的属性,然后修改在以下的obj.a.and看:

复制代码代码如下所示:

var父=函数(名称){

this.name = name'parent| |;

};

parent.prototype.getname =函数(){

返回this.name;

};

parent.prototype.obj = { 1 };

var子=函数(名称){

Parent.apply(这个参数);

};

新的(函数){ };

f.prototype = parent.prototype;

child.prototype =新的f();

VaR母=新的母('myparent);

VaR的孩子=新的儿童('mychild);

console.log(孩子。obj,); / / 1

console.log(母。obj,); / / 1

孩子。obj = 2;

console.log(孩子。obj,); / / 2

console.log(母。obj,); / / 2



这是一个关键的问题,当属性访问对象的原型,原型的属性是只读的对象,也就是说孩子可以读取obj对象,但是你不能在原型参考修改obj对象,所以当孩子修饰对象不影响对象的原型。只有在添加一个对象属性的对象,对象属性覆盖父类的原型。当孩子对象修改对象,它首先读取obj的参考原型。当child.obj和parent.prototype.obj指向同一对象,孩子的修改对象,会影响家长的价值。原型。obj,然后影响到父类的对象。AngularJS的嵌套范围遗传是由模型中的javascript原型继承实现。

根据以上的描述,只要进入与父类的子类对象的原型,原型是相同的对象,那么就会出现上面的情况,所以我们可以复制父类的原型,然后分配给子类的原型,这样的孩子是修改原型的性质是修改父类的原型。副本不会影响父类原型。具体实现如下:

复制代码代码如下所示:

无功deepclone =功能(源、目标){

源=源| | { };

无功tostr = object.prototype.tostring,

arrstr ={ }对象数组;

对于(源代码中的var i){

如果(source.hasownproperty(I)){

var =源代码{ };

如果(typeof项目= 'object){

目标{我} =(tostr.apply(项目),ToLowerCase(arrstr)= = = { } { });

DeepClone(项目,目标{我});

其他{ }

DeepClone(项目,目标{我});

}

}

}

将目标;

};

var父=函数(名称){

this.name = name'parent| |;

};

parent.prototype.getname =函数(){

返回this.name;

};

parent.prototype.obj = {一}:1;

var子=函数(名称){

Parent.apply(这个参数);

};

child.prototype = deepclone(母。原型);

VaR的孩子=新的儿童(儿童的);

VaR母=新的母('parent);

console.log(孩子。obj,); / / 1

console.log(母。obj,); / / 1

孩子。obj = 2;

console.log(孩子。obj,); / / 2

console.log(母。obj,); / / 1



考虑到以上考虑,Javascript继承的具体实现如下。这里只有孩子和父母被视为功能:

复制代码代码如下所示:

无功deepclone =功能(源、目标){

源=源| | { };

无功tostr = object.prototype.tostring,

arrstr ={ }对象数组;

对于(源代码中的var i){

如果(source.hasownproperty(I)){

var =源代码{ };

如果(typeof项目= 'object){

目标{我} =(tostr.apply(项目),ToLowerCase(arrstr)= = = { } { });

DeepClone(项目,目标{我});

其他{ }

DeepClone(项目,目标{我});

}

}

}

将目标;

};

函数(父,子){

孩子=孩子(功能){ } | |;

如果(父=未定义)

返回的孩子;

借/父类的构造函数

子=函数(){

Parent.apply(这个说法);

};

通过一个深拷贝继承父原型

child.prototype = deepclone(母。原型);

重置构造函数属性

child.prototype.constructor =孩子;

};



总结

说这么多,其实,Javascript实现继承是非常灵活的,没有一个实现继承的最好方法根据不同的要求,最重要的是要了解继承在Javascript实现的原理,它是原型链,只要理解了这样,你可以很容易地实现自己的继承。