宝哥软件园

再谈javascript原型继承

编辑:宝哥软件园 来源:互联网 时间:2021-09-09

真正意义上,Javascript不是面向对象的语言,不提供传统的继承方式,而是提供原型继承的方式,利用自身提供的原型属性实现继承。

原型和原型链

在谈原型继承之前,首先要谈原型和原型链。毕竟这是原型继承的基础。在Javascript中,每个函数都有一个指向自己原型的原型属性prototype,这个函数创建的object也有一个_ _ prototype _ _属性指向这个原型,函数的原型就是一个对象,所以这个对象也有一个_ _ prototype _ _指向自己的原型,这个原型一层一层深入到Object对象的原型中,从而形成一个原型链。下图说明了Javascript中原型和原型链的关系。

每个函数都是由一个函数创建的对象,所以每个函数也有一个__proto__属性指向函数的原型。这里需要指出的是,真正形成原型链的是每个对象的__proto__属性,而不是函数的原型属性,这一点非常重要。

原型继承

基本方式

复制代码如下:VAR Parent=function () {this。name=' parent} ;parent . prototype . getname=function(){ return this . name;} ;parent . prototype . obj={ a : 1 };

var Child=function(){ this . name=' Child ';} ;child . prototype=new Parent();

var Parent=new Parent();var Child=new Child();

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

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

这种方法优点明显,实现非常简单,不需要特殊操作。同时,缺点也很明显。如果子类需要执行与父类构造函数相同的初始化操作,它必须在子类构造函数的父类中重复该操作:

复制代码如下: var Parent=function(name){ this。name=name | |“parent”;} ;parent . prototype . getname=function(){ return this . name;} ;parent . prototype . obj={ a : 1 };

var Child=function(name){ this . name=name | | ' Child ';} ;child . prototype=new Parent();

var parent=新父代(' my Parent ');var Child=new Child(' my Child ');

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

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

借用构造函数

复制代码如下: var Parent=function(name){ this。name=name | |“parent”;} ;parent . prototype . getname=function(){ return this . name;} ;parent . prototype . obj={ a : 1 };

var Child=function(name){ parent . apply(this,arguments);} ;child . prototype=new Parent();

var parent=新父代(' my Parent ');var Child=new Child(' my Child ');

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

在上面的方法中,通过在子类的构造函数中应用调用父类的构造函数来执行相同的初始化工作,这样无论父类做了多少初始化工作,子类都可以执行相同的初始化工作。然而,在上述实现中仍然存在问题。父类构造函数执行两次,一次在子类构造函数中,一次在赋值子类原型时,这是一个很大的盈余,所以我们需要做一个改进:

复制代码如下: var Parent=function(name){ this。name=name | |“parent”;} ;parent . prototype . getname=function(){ return this . name;} ;parent . prototype . obj={ a : 1 };

var Child=function(name){ parent . apply(this,arguments);} ;子原型=父原型;

var parent=新父代(' my Parent ');var Child=new Child(' my Child ');

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

这样我们只需要在子类的构造函数中执行一次父类的构造函数,同时可以继承父类原型中的属性,更符合原型的初衷,就是把可重用的内容放在原型中,我们只继承原型中的可重用内容。上述方法的原型图如下:

临时构造器模式(圣杯模式)

上面借用的构造函数模式的最后一个改进版本仍然有问题。它将父类的原型直接分配给子类的原型,这将导致一个问题。如果子类的原型被修改,修改也会影响父类的原型,然后是父类对象,这绝对不是大家想看到的。为了解决这个问题,有一个临时构造函数模式。

复制代码如下: var Parent=function(name){ this。name=name | |“parent”;} ;parent . prototype . getname=function(){ return this . name;} ;parent . prototype . obj={ a : 1 };

var Child=function(name){ parent . apply(this,arguments);} ;var F=新函数(){ };原型=父原型;child . prototype=new F();

var parent=新父代(' my Parent ');var Child=new Child(' my Child ');

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

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

很容易看出,通过在父原型和子类原型之间增加一个临时构造函数F,切断了子类原型和父原型之间的连接,这样在修改子类原型时父原型就不会受到影响。

我的方法

《Javascript模式》,圣杯模式结束了,但是无论上面哪种方法都有一个不容易发现的问题。如您所见,我在“父对象”的原型属性中添加了一个obj对象文字属性,但它从未起作用。让我们在圣杯模型的基础上看看下面的情况:

复制代码如下: var Parent=function(name){ this。name=name | |“parent”;} ;parent . prototype . getname=function(){ return this . name;} ;parent . prototype . obj={ a : 1 };

var Child=function(name){ parent . apply(this,arguments);} ;var F=新函数(){ };原型=父原型;child . prototype=new F();

var parent=新父代(' my Parent ');var Child=new Child(' my Child ');

console . log(child . obj . a);//1 console . log(parent . obj . a);//1 child . obj . a=2;console . log(child . obj . a);//2 con sole . log(parent . obj . a);//2

在上面的例子中,当我修改子对象obj.a时,父类原型中的obj.a也会被修改,这导致了和共享原型一样的问题。发生这种情况是因为当我们访问child.obj.a时,我们总是会沿着原型链找到父类的原型,然后找到obj属性,然后修改obj.a看看下面的情况:

复制代码如下: var Parent=function(name){ this。name=name | |“parent”;} ;parent . prototype . getname=function(){ return this . name;} ;parent . prototype . obj={ a : 1 };

var Child=function(name){ parent . apply(this,arguments);} ;var F=新函数(){ };原型=父原型;child . prototype=new F();

var parent=新父代(' my Parent ');var Child=new Child(' my Child ');

console . log(child . obj . a);//1 console . log(parent . obj . a);//1 child . obj . a=2;console . log(child . obj . a);//2 con sole . log(parent . obj . a);//2

这里有一个关键问题。当对象访问原型中的属性时,原型中的属性是只读的,这意味着子对象可以读取obj对象,但不能修改原型中的obj对象引用。因此,当孩子修改obj时,不会影响原型中的obj。它只是向自己的对象添加了一个obj属性,覆盖了父原型中的obj属性。而当子对象修改obj.a时,它首先读取原型中obj的引用。此时child.obj和Parent.prototype.obj指向同一个对象,所以子对象对obj.a的修改会影响Parent.prototype.obj.a的值,进而影响父类的对象。AngularJS中$scope的嵌套继承是通过Javasript模型中的原型继承实现的。根据上面的描述,只要子类对象中访问的原型和父原型是同一个对象,就会出现上面的情况,所以我们可以复制父原型,然后分配给子类原型,这样当子类修改原型中的属性时,只会修改父原型的一个副本,不会影响父原型。具体实现如下:

复制代码如下: vardeep clone=function(source,target){ source=source | | { };var toStr=object . prototype . tostring,arrStr='[对象数组]';for(source中的var I){ if(source . HasownProperty(I)){ var item=source[I];if(type of item==' object '){ target[I]=(ToStr . apply(item))。toLowerCase()===arrStr) : []?{} ;深度克隆(项目,目标[I]);}else{ deepClone(项目,目标[I]);} } }返回目标;} ;var Parent=function(name){ this . name=name | | ' Parent ';} ;parent . prototype . getname=function(){ return this . name;} ;parent . prototype . obj={ a : ' 1 ' };

var Child=function(name){ parent . apply(this,arguments);} ;child . prototype=deep clone(parent . prototype);

var Child=new Child(' Child ');var Parent=new Parent(' Parent ');

console . log(child . obj . a);//1 console . log(parent . obj . a);//1 child . obj . a=' 2 ';console . log(child . obj . a);//2 con sole . log(parent . obj . a);//1基于以上所有考虑,Javascript继承的具体实现如下。这里只考虑子函数和父函数都是函数的情况:

复制代码如下: vardeep clone=function(source,target){ source=source | | { };var toStr=object . prototype . tostring,arrStr='[对象数组]';for(source中的var I){ if(source . HasownProperty(I)){ var item=source[I];if(type of item==' object '){ target[I]=(ToStr . apply(item))。toLowerCase()===arrStr) : []?{} ;深度克隆(项目,目标[I]);}else{ deepClone(项目,目标[I]);} } }返回目标;} ;

var expand=function(Parent,Child){ Child=Child | | function(){ };if(Parent===未定义)返回Child//借用构造函数child=function () {parent。应用(这个,论点);} ;//继承原型子。原型=深度克隆(父。原型)通过深度复制;//重置构造函数属性child . prototype . constructor=child;} ;

摘要

话虽如此,其实Javascript中的继承是非常灵活多样的,没有最好的方法可以根据不同的需求用不同的方式实现继承。最重要的是理解Javascript中的继承原理,也就是原型和原型链的问题。只要你明白这些,你就可以很容易地自己实现继承。

更多资讯
游戏推荐
更多+