当前位置: 首页 > Web前端 > HTML

本文厘清原型链与继承

时间:2023-04-03 00:58:55 HTML

描述了ECMAscript中原型链的概念,并以原型链作为实现继承的主要方式。基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。构造函数与原型和实例的关系:每个构造函数都有一个原型对象(prototype),原型对象包含一个指向构造函数(constructor)的指针,实例包含一个指向原型对象(proto)的内部指针。来一张手残党画的图。实际上,每个Function都是Object基类的一个实例,所以每个Function都有一个指向Object.prototype的__proto__。在搜索实例的属性时,它会首先查找实例的自定义属性。如果不是,则使用__proto__查找实例所属类的原型。如果没有,那就用原型(原型也是对象,只要是对象就有__proto__属性)逐层找Object的原型上的__proto__,如果没有就是undefined。可以说,引用类型之间的继承是通过原型链机制实现的。这里我们可以看一下第一种继承方式。原型继承。原型继承将父类的私有+公共属性和方法视为子类的公共属性。核心:而不是克隆父类的private+public属性,让子类的public属性完全一样;它通过__proto__与子类建立原型链,当子类的实例需要使用父类的属性时,可以通过__proto__来使用;函数Parent(){this.x=199;this.y=299;}Parent.prototype.say=function(){console.log('say')}functionChild(){this.g=90;}//把父类的实例挂在原型上子类的Child.prototype=newParent();varp=newParent();varc=newChild();的本质实现;console.dir(c)是重写原型对象。通过将子类的原型指向父类的实例,子类的实例可以通过__proto__访问Child.prototype,即Parent的实例,这样就可以访问父类的私有方法,然后通过__proto__指向父类的原型,得到父类原型上的方法。这样,父类的私有和公有方法和属性都被视为子类的公共属性。这就通过原型链实现了继承。但是不要忘了默认的原型,因为所有的引用类型都继承了Object,所有的子类也可以访问Object上的方法,比如toString(),valueOf()等,这时候我们可以通过instanceof查看,console.log(cinstanceofObject)//trueconsole.log(cinstanceofParent)//trueconsole.log(cinstanceofChild)//true但是需要注意的是有时候我们需要在创建一个新的方法或者重写父类的方法,记得放在替换原型的语句之后。函数Parent(){this.x=199;this.y=299;}Parent.prototype.say=function(){console.log('say')}functionChild(){this.g=90;}/*写原型方法和属性没用这里的子类,因为会改变原型的指针,所以应该放在重新设计之后Child.prototype.Bs=function(){console.log('Bs')}*/Child.prototype=newParent();Child.prototype.constructor=Child//由于重新修改Child的原型,导致默认原型上的构造函数丢失,所以需要添加Child.prototype.Bs=function(){console.log('Bs')}Child.prototype.say=function(){console.log('稍后更改')}varp=newParent();varc=newChild();console.dir(c)c.Bs()//bsc.say()//后来改成p.say()//say不影响父类的父类实例访问方法原型继承问题:子类继承父类的属性和方法是使用private属性和public父类的方法作为它自己的公共属性和方法。有一点我们需要明确的是,当我们操作基本数据类型时,我们操作的是值,当我们操作应用数据类型时,我们操作的是地址。如果引用类型的属性在父类的private属性中被提及,那么在被子类继承时会作为public属性使用,这样子类1操作这个属性时,就会影响到子类2.创建子类的实例时,不能将参数传递给超类型的构造函数。应该说没有办法在不影响所有对象实例的情况下向父类的构造函数传递参数。因此,在实践中很少单独使用原型继承。第二种继承是调用继承。我相信每个人都应该使用调用方法。很熟悉,改变方法的this点,同时执行方法。在子类构造函数中,父类.call(this)可以将父类的private变成子类的private函数Parent(){this.x=100;this.y=199;}Parent.prototype.fn=function(){}functionChild(){this.d=100;Parent.call(this);//构造函数中的this是当前实例}varp=newParent();varc=newChild();console.log(p)//Parent{x:100,y:199}console.log(c)//Child{d:100,x:100,y:199}这个很好理解,在子类的构造函数中,把父类的this点改成子类的实例,运行同时调用父类的方法,使得父类中的this.x成为子类的instance.x,该方法可以继承父类的私有属性,并且只能继承私有属性和父类的方法。也许你会问我Parent.prototype.call(this)能不能继承父类的公共属性和方法,我只能默默的说call是Function的方法。模拟对象继承模拟对象继承的原理是循环遍历父类实例,然后将父类实例的所有私有方法都取出来,添加到子类实例函数中Parent(){this.x=100;}Parent.prototype.getX=function(){console.log('getX')}functionChild(){varp=newParent();for(varattrinp){//forin可以遍历到原型上的public自定义属性this[attr]=p[attr]}//以下代码只获取私有方法和属性,如果不加this,可以遍历所有的方法和属性/*if(e.hasOwnProperty(attr)){this[attr]=e[attr]}e.propertyIsEnumerable()*///Enumerableproperties==>可以列出的属性一个一个}varp=newParent();变种C=新的孩子();console.dir(c)这个就不过多解释了,唯一重要的是:forin可以遍历到原型上的public自定义属性,所以可以得到private和public的属性和方法,可以遍历private和public,您需要添加限制。但是如果不做hasOwnProperty判断,那么就会把父类的public和private属性都当作private。MixedinheritanceMixedinheritance,那么肯定是混合的,调用继承和原型继承的结合,不管是private还是public,都接过来了。但是有个问题就是在子类的原型上多了一套父类的私有属性,但是不会出问题。因为子类的私有属性也有同一套functionParent(){this.x=100;}Parent.prototype.getX=function(){}functionChild(){Parent.call(this);}Child.prototype=newParent();Child.prototype.constructor=Child;varp=newParent();varc=newChild();console.log(c)//Child{x:100}有多种方式ofmixedinheritance,这是call和prototype的混合,也可以混合call和假装对象继承等等,方法很多。具体使用要看你自己的业务场景。这种混合继承最大的问题在于,无论在什么情况下,构造函数都会被调用两次:一次是在创建子类型原型时,一次是在子类型构造函数内部,是的,子类型最终会包含该子类型的所有实例属性父类型对象,但我们必须在调用子类构造函数时重写这些属性。另一种是调用+复制继承//混合继承:调用继承+复制继承functionextend(newEle,oldEle){for(varattrinoldEle){newEle[attr]=oldEle[attr];}}}函数F(){this.x=100;this.showX=function(){}}F.prototype.getX=function(){};F.prototype.getX1=函数(){};varf1=新F;console.dir(f1)functionS(){F.call(this)//调用继承}extend(S.prototype,F.prototype);//复制继承S.prototype.cc=function(){}varp1=新S;控制台目录(p1);该方法使用call继承继承父类的私有方法,使用forincopy继承父类的公共属性和方法,比较实用。中间件继承中间件继承是通过原型链的机制,子类的prototype.__proto__本来应该直接指向Object.prototype。从父类原型上的__proto__,也可以到Object.prototype==>留在父类.prototype上,父类.prototype是一个中间件,所以子类可以继承父类的public方法类作为自己的公共方法。functionParent(){this.x=100;}Parent.prototype.getX=function(){};functionChild(){}//parent的原型对象相当于一个跳板Child.prototype.__proto__=Parent.prototype;varp=newParent();varc=newChild();console.log(c);寄生组合继承寄生组合:调用继承+Object.create()所谓寄生组合继承就是通过借用构造函数继承属性,并通过原型链的混合形式继承方法。基本思想是不必为了指定子类的原型而调用父类的构造函数。我们所需要的只是父类型原型的副本。本质上就是利用父类原型的寄生继承,然后将结果赋值给子类的原型。所以我们创建一个新的方法functioninheritPrototype(subType,superType){varprototype=Object.create(superType.prototype);//创建对象prototype.constructor=subType;//增强对象subType.prototype=prototype;//指定对象}说明:1.第一步是创建父类型原型的副本。2.第二步,在创建的副本中添加constructor属性,从而弥补因重写原型而丢失的默认constructor属性。3.第三步,将创建的对象赋值给子类型的原型。函数F(){this.x=100;}F.prototype.showX=function(){};函数S(){this.y=200;F.call(this);//只继承私有的;}functioninheritPrototype(subType,superType){varprototype=Object(superType.prototype);//创建对象prototype.constructor=subType;//增强对象subType.prototype=prototype;//指定对象}inheritPrototype(S,F);varp1=newS();console.dir(p1);经典继承(道格拉斯继承)和上面类似,已知一个对象o,需要新建一个对象被创建,这个新对象继承自对象o。//函数封装functioncreate(o){functionF(){}F.prototype=o;返回新的F();}varo={name:"张三",age:18};varo2=create(o);//这样o2就继承自o。以上是ES5部分的继承。ES6的继承就是es6的继承。实现继承,既方便又清晰。许多例子:classSuper{constructor(){this.sup='super??'}superFun(){console.log('parent-function')}}classSubextendsSuper{constructor(){super()this.name='sub??'}subFun(){super.superFun()console.log('subFun');}}letp=newSub()子类必须在construct方法中调用super,否则新建实例时会报错。因为子类没有自己的this对象,而是继承了父类的this对象,所以只能super()。super关键字:既是函数又是对象。作为一个函数,它是父类的构造函数,只能在子类的构造函数中使用,但是使用环境是子类,相当于在子类的Constructor环境中调用父类。sub继承了Super,sub中使用的super()相当于下面的代码Super.prototype.constructor.call(this);//这里的this就是sub的this,在普通方法中作为对象使用(非构造方法,非静态方法),或者指向父类的原型对象,可以使用super.fun()方法调用。但是父类(this.xxx)的属性不能以super.xxx的形式被子类访问。因为这些属性是父类实例的属性,而不是父类的属性。只有写成this.prototype.xxx的才属于父类,不属于实例。它作为一个对象,在一个静态方法中,就是父类本身。所以可以调用父类本身的静态函数。ES6规定通过super.someFunction()调用父类的方法时,环境必须是子类的this,所以我觉得不会乱。比如在子类中写super.someValue等同于在this.someValue中写。再次读取super.someValue时,读取不到刚才设置的值。我想是因为ES6只拦截了对super的写操作被移走了,所有的写操作都移到了子类的this环境中,但是读操作还是会移到父类中,所以读super.someValue等同于读A.prototype.someValue,父类值。ES5和ES6继承的区别。ES5不能继承原生构造函数。原因是它先创建一个子类实例,然后添加父类的属性。native构造函数(Array)有内部属性,无法读取,所以不起作用。.ES6更聪明。先创建一个父类的实例,放到子类的this上,然后执行子类的构造函数,这样就可以继承了。记忆方式整理了一些简单的文字描述,很快就记住了原型继承的所有方法:subclass.prototype=instanceofparentclass。作用:parent(public)->child(public)调用继承方法:执行parentclass.call(this)在子类中作用:parent(private)->child(private)模拟对象继承方法:在子类中执行forin实例作用of循环父类:parent(public-private)->child(private)forin循环更灵活,可以循环实例:也可以使用hasOwnProperty进行public过滤;您可以循环父类的原型并将其添加到子类的原型中。混合继承方式:前几个随意组合,看你的业务情况中间件继承方式:childclass.prototype.proto=parentclass.prototype;效果:parent(public)->child(public)寄生组合继承方法:call+Object.create()效果:parent(privatepublic)->child(publicprivate)