1 前言
前面写了一篇js 原生语法之 prototype,__proto__和 constructor
把它三者之间的关系搞清楚了, 也理明白了js
是怎样通过prototype
实现继承的,这篇文章就讲一讲继承的具体实现方式. 其实就是怎样将想要继承的对象放置到需要继承对象的原型链上.
2 原型链继承
就是直接修改Function
存放的共享数据prototype
constAnimal=function(){}Animal.prototype.eat=function(){console.log('ieat')}constDog=function(){}//Dog.prototype=newAnimal()//Object.setPrototypeOf(Dog.prototype,newAnimal())Dog.prototype=Object.create(newAnimal())Dog.prototype.constructor=Dog//最好修复一下constdog=newDog()dog.eat()
子类构造出来的实例, 在自身上找不到的属性 eat
会到它的原型对象的 Dog.prototype
上去找, 就是 new Animal()
, Animal
实例身上也没有 eat
属性, 会到它的原型对象 Animal.prototype
上找到 eat
3 其它六种继承方式
借用构造函数继承
组合继承
原型式继承
寄生式继承
寄生组合式继承
混入继承多个对象
都是垃圾, 都是糟粕, 其实就是我不想记了...
4 class 继承
主要要学习 class
的继承, 它也是通过 prototype
实现的继承, 是一个语法糖.
如果可以, 我还是希望代码中不要直接修改 prototype
的属性, 例如prototype.XXX = XXX
, 这样的代码真的很难阅读, 并且实现继承的代码做好在最前面.
//父类classAnimal{constructor(options){typeofoptions==='object'&&Object.assign(this,options)}say(){console.log('i...')}}//子类,继承父类classDogextendsAnimal{constructor(options){//在子类中的构造函数中,在使用this之前,需要super(),一下,去先将父类中的构造函数调用一下,得到父类中的实例属性super(options)this.options=options}eat(){console.log('ieat')}}
tsc
转 ts
到 js
之后, 简化代码的结果
/***继承方法*@param{*}child子类*@param{*}b父类*/var__extends=function(child,parent){if(typeofb!=='function'&&parent!==null)thrownewTypeError('Classextendsvalue'+String(parent)+'isnotaconstructorornull')//这里是相当于复制父类的静态方法到子类上,因为这样会在子类child上读取不到的属性,会到父类parent上去读取Object.setPrototypeOf(child,parent)if(parent===null){//如果父类是null,则原型设置为nullchild.prototype=Object.create(parent)}else{//如果父类是一个函数,//直接使用原型链继承child.prototype=newparent()child.prototype.constructor=child}}//为啥是一个立即执行函数,因为class不会提升,可以使用这种方式做到.//但是const就模拟不出来了varAnimal=/**@class*/(function(){//class实际上也就是一个function,//constructor就是函数本体functionAnimal(options){typeofoptions==='object'&&Object.assign(this,options)}//方法通过原型实现Animal.prototype.say=function(){console.log('i...')}returnAnimal})()varDog=/**@class*/(function(_super){//继承父类__extends(Dog,_super)functionDog(options){//实例化父类,得到父类的实例属性var_this=_super.call(this,options)||this_this.options=optionsreturn_this}Dog.prototype.eat=function(){console.log('ieat')}returnDog})(Animal)
5 diy 继承
纯属 diy
constprotoArr=Symbol('protoArr')constaddProto=(child,parent)=>{if(!child[protoArr]){//因为不能修改child.prototype的指向,所以变通了一下,去修改child.prototype的原型对象的指向constoriginProto=Object.getPrototypeOf(child.prototype)//将它原先的原型对象,和要添加的原型对象保存起来child[protoArr]=newSet([originProto,parent.prototype])constnewProp=newProxy({},{get(target,key){//到保存的各个原型对象上去读取需要获取的属性constvalue=[...child[protoArr]].find((i)=>i?.[key]!==void0)?.[key]returnvalue},})//如果在child.prototype上没有读取到的属性,回到它的原型对象上去读取,然后就读取到了这个代理的对象,然后就可以被拦截到Object.setPrototypeOf(child.prototype,newProp)}else{//直接将需要添加的原型链加到保存的地方child[protoArr].add(parent.prototype)}}constdelProto=(child,parent)=>{if(!child[protoArr]){//没有新增过这个原型对象return}else{//删除即可child[protoArr].delete(parent.prototype)}}classStyle1{width(){return100}}classStyle2{height(){return100}}classStyle3{color(){return'#ccc'}}classStyle4{}Style4.prototype.fontSize='12px'classStyle5{backgroundColor(){return'red'}}constgetStyle=(style)=>{constkeys=['width','height','color','fontSize','backgroundColor']returnkeys.reduce((total,cur)=>{if(style[cur]){total[cur]=typeofstyle[cur]==='function'?style[cur]():style[cur]}returntotal},{})}classStyle{}conststyle=newStyle()console.log(getStyle(style))//{}此时,它和它的原型链上读取不到任何属性addProto(Style,Style1)addProto(Style,Style2)addProto(Style,Style3)addProto(Style,Style4)addProto(Style,Style5)console.log(getStyle(style))//{//width:100,//height:100,//color:'#ccc',//fontSize:'12px',//backgroundColor:'red',//}delProto(Style,Style2)console.log(getStyle(style))//{//width:100,//color:'#ccc',//fontSize:'12px',//backgroundColor:'red',//}
6 总结
继承就是通过修改原型对象实现的。