javascript实现继承的几种方式,及其优缺点总结
面向对象3个基本特征:封装、多态和继承,继承可以使子类具有父类的属性和方法。
在javascript中,通过原型链来实现继承,有以下几种方法。
//声明父类 function SuperClass() { //父类属性 this.superValue = "super" } //为父类添加共有方法 SuperClass.prototype.showSuperValue = function () { console.log(this.superValue) } //声明子类 function SubClass() { this.subValue = 'suber' } //通过将原型对象设置为父类的实例,实现继承 SubClass.prototype = new SuperClass() //上述赋值操作会覆盖构造函数,重新设置一下 SubClass.prototype.constructor = SubClass //为子类添加共有方法 SubClass.prototype.showSubValue = function () { console.log(this.subValue) } //实例化 let sub1 = new SubClass() console.log(sub1.superValue) console.log(sub1.subValue) sub1.showSuperValue() sub1.showSubValue()
我们在实例化一个对象的时候,新创建的对象会赋值构造函数中的属性和方法,并且将__proto__指向了类的原型对象。
实例化sub1时,会复制subValue属性,因此subValue是sub1的自有属性,sub1的__proto__指向了SubClass.prototype。
而SubClass.prototype由SuperClass实例化而来,因此它复制了SuperClass的属性superValue,还有后来添加的showSubValue方法。
SubClass.prototype也有__proto__属性,指向了SuperClass.prototype,而SuperClass.prototype只有一个showSuperValue方法
console.log(sub1.hasOwnProperty('subValue')) //true console.log(sub1.hasOwnProperty('superValue')) //false console.log(sub1.__proto__) /* SubClass { superValue: 'super', showSubValue: [Function], constructor: [Function: SubClass] } */ console.log(sub1.__proto__.__proto__) //SuperClass { showSuperValue: [Function] }
当访问一个实例的属性时,先在该实例的自有属性中查找,如果找不到就到__proto__属性中查找,如果还找不到继续往上找,直到__proto__为null,都找不到则说明未定义该属性。
当修改一个实例的属性时,如果不是该实例的自有属性,则添加一个自有属性,如
let sub1 = new SubClass() console.log(sub1.hasOwnProperty('superValue')) //false sub1.superValue = 'sub1' //修改继承的属性,变成了添加自有同名属性 console.log(sub1.hasOwnProperty('superValue')) //true
实例会复制类的构造函数中的属性,但是会共享父类的属性,如果父类属性为引用类型,则会出问题。
function SuperClass() { this.superValue = ["super"] //父类属性为引用类型 } function SubClass() { } SubClass.prototype = new SuperClass() let sub1 = new SubClass() let sub2 = new SubClass() sub1.superValue.push('text') console.log(sub1.superValue) //["super","text"] console.log(sub2.superValue) //["super","text"]
类式继承缺点:
1. 如果父类属性的共有属性是引用类型,会被子类中所有实例共有,一个子类实例修改这个共有属性,会影响其他实例
2. 由于子类实现的继承是靠其原型protype对父类的实例化实现,因此没法向父类传递参数
//声明父类 function SuperClass(name) { //引用类型共有属性 this.superValue = ["super"] this.name = name } //父类的原型方法 SuperClass.prototype.showSuperValue=function () { console.log(this.superValue) } //声明子类 function SubClass(name) { //继承父类 SuperClass.call(this,name) } let sub1 = new SubClass("subname1") let sub2 = new SubClass("subname2") sub1.superValue.push('test') console.log(sub1.name) //subname1 console.log(sub1.superValue) //[ 'super', 'test' ] console.log(sub2.superValue) //[ 'super' ] sub1.showSuperValue() //报错,未定义该方法
构造函数继承的核心是在构造函数中通过call方法,调用父类的构造函数,传递this和参数。但是由于未涉及原型protorype,所以父类的方法没有得到继承。
将上述两种方式进行组合,集合两种方式的优点。
function SuperClass(name) { this.superValue = ["super"] this.name = name } SuperClass.prototype.showSuperValue=function () { console.log(this.superValue) } function SubClass(name) { //构造函数继承,继承父类的属性 SuperClass.call(this,name) } //类式继承,子类原型继承父类 SubClass.prototype = new SuperClass() let sub1 = new SubClass("subname1") let sub2 = new SubClass("subname2") sub1.superValue.push('test') console.log(sub1.name) console.log(sub1.superValue) console.log(sub2.superValue) sub1.showSuperValue() //[ 'super', 'test' ]
通过这种组合,子类实例修改父类继承下来的引用类型属性superValue,不会影响其他实例;
并且子类实例化过程中,又能将参数传递到父类的构造函数中,如name。
但是还是存在一个问题,子类构造函数中调用了一次父类的构造函数,在设置子类原型时,又调用了一次,共调用了两次。
2006年格拉斯•克罗克福德发布一篇《JavaScript 中原型式继承》的文字,他的观点是借助原型prototype可以根据已有的对象,创建一个新对象,同时不必创建新的自定义对象类型。
function inheritObject(o) { function F() {} F.prototype = o return new F() }
封装了一个继承函数,通过类式继承的方式继承传入的参数对象o
function inheritObject(o) { function F() {} F.prototype = o return new F() } let book = { name:'js book', alikeBook:['css book','html book'] } let book1 = inheritObject(book) book1.name = 'canvas book' book1.alikeBook.push('svg book') let book2 = inheritObject(book) book2.name = 'php book' console.log(book1.name) //canvas book console.log(book1.alikeBook) //[ 'css book', 'html book', 'svg book' ] console.log(book2.name) //php book console.log(book2.alikeBook) //[ 'css book', 'html book', 'svg book' ]
跟类式继承一样,修改父类的引用属性,会影响其他实例对象。
function inheritObject(o) { function F() {} F.prototype = o return new F() } let book = { name:'js book', alikeBook:['css book','html book'] } function createBook(obj) { let o = inheritObject(obj) o.getName = function () { console.log(this.name) } return o } let book1 = createBook(book) console.log(book1.name) book1.getName()
寄生式继承,对原型继承进行了二次封装,在封装中对继承的对象进行了拓展,这样新创建的对象不仅拥有父类的属性和方法,而且还添加新的属性和方法。
function inheritObject(o) { function F() {} F.prototype = o return new F() } function inheritPrototype(subClass,superClass) { let p = inheritObject(superClass.prototype) p.constructor = subClass subClass.prototype = p }
封装一个函数,来实现子类原型继承于父类原型,这里没有使用构造函数,完整的继承如下。
function inheritObject(o) { function F() {} F.prototype = o return new F() } function inheritPrototype(subClass,superClass) { let p = inheritObject(superClass.prototype) p.constructor = subClass subClass.prototype = p } function SuperClass(name) { this.name = name } SuperClass.prototype.getName = function () { console.log(this.name) } function SubClass(name,color) { //构造函数继承 SuperClass.call(this,name) this.color = color } //寄生式继承父类原型 inheritPrototype(SubClass,SuperClass) SubClass.prototype.getColor = function () { console.log(this.color) } let instance = new SubClass('wang','green') instance.getName() instance.getColor()
通过这种方式,可以在构造函数中像父类传递参数,而且父类构造函数只执行了一次。
点赞(0)