JavaScript实现继承的7种方式总结
作者:布鲁斯要蓝调
什么是继承
用官方点的话讲继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,同时还可以在子类中重新定义以及追加属性和方法。通俗的来说继承就是子类可以从父类那里复用一些属性和方法,从而达到复用代码的一个效果。
实现继承的6种方式
- 原型链继承
- 构造函数继承
- 组合继承(伪经典继承)
- 原型式继承
- 寄生式继承
- 寄生组合式继承
- class 类继承
1. 原型链继承
function Person() { this.name = 'bruce' this.age = 18 this.gender = '男' this.like = { sport: 'running', book: 'JavaScript' } this.getName = function() { console.log(this.name); } } function Student() { this.school = '新华小学' } let s = new Student() console.log(s.name); // 未继承前 undefined Student.prototype = new Person() // 继承之后 let s1 = new Student() s1.getName() // bruce console.log(s1.like.book); // JavaScript s1.like.book = 'Java' // 修改引用类型的值 s1.name = 'jack' // 修改非引用类型的值 console.log(s1.like.book); // Java s1.getName() // jack console.log('----------'); let s2 = new Student() s2.getName() // bruce console.log(s2.like.book); // Java
所谓的原型链继承,在我的理解就是子类通过原型链来继承到父类的属性,在上面的代码中我们也可以看到在子类实例s
没有继承到父类的属性时,子类去访问父类才有的属性值为:undefined
。
在将父类实例对象挂载到子类Student构造函数的原型上面后,那么此时子类的原型上面就有了父类的全部属性。因为Person
的实例对象被显示的挂载到了Student
的原型上面。所以子类在继承之后,所有实例化的子类都拥有了跟父类一样的属性,于是这样子类就通过原型链实现了继承。
优点: 子类可以继承到父类的方法。
缺点:
- 会在子类实例上共享父类所有的引用类型数据,也就是子类的原型被父类属性覆盖。
- 子类实例不能给父类构造函数传参。
function Person(t) { this.property = t } function Student(t) { this.typeproperty = t } Student.prototype = new Person() let s=new Student('a') console.log(s.property) // undefined console.log(s.typeproperty) // a
这样子类实例传的参数就不能给到父类构造函数里面了。
2. 构造函数继承
function Person(name,age) { this.name = name this.age = age this.like = { sport: 'running', book: 'JavaScript' } this.getName = function () { console.log(this.name); } } Person.prototype.sayHello = function () { console.log('你好'); } function Student(name, age,) { Person.call(this, name, age) // 借用构造函数 } let s1 = new Student('bruce',18) s1.getName() // bruce s1.name='jack' s1.getName() // jack s1.like.sport='swimming' console.log(s1.like.sport); // swimming console.log('------'); let s2=new Student('lucy',19) s2.getName() // lucy console.log(s2.like.sport); // running
构造函数继承就是使用强制改变 this 指向,借用一个构造函数继承。相较于原型链继承,构造函数继承不会共享父类上的引用类型数据,不会互相影响。
缺点:1.不能访问到父类原型属性上面的方法和参数,即 Person.prototype
上的都不能访问到。
Person.prototype.sayHello = function () { // 在 Person 的原型上面加一个方法 console.log('你好'); } let s1 = new Student('bruce',18) s1.sayHello() // s1.sayHello is not a function
3. 组合继承(伪经典继承)
相较于原型链继承,构造函数继承好像已经非常完美了,但是它还是存在着一个最大的缺点就是:不能继承到父类的原型上面的属性。但是原型链继承就好像没有这个问题,如果将这两个继承方式组合起来搭配使用是不是就能解决掉所有问题,于是这就有了组合继承。
function Person(name, age) { this.name = name this.age = age this.like = { sport: 'running', book: 'JavaScript' } this.getName = function () { console.log(this.name); } } Person.prototype.sayHello = function () { console.log('你好'); } function Student(name, age) { Person.call(this, name, age) // 构造函数继承 } Student.prototype = new Person() // 原型链继承 let s1 = new Student('bruce', 18) s1.sayHello() // 你好 console.log(s1);
上面先在子类内使用构造函数继承将父类的显示属性挂载到子类上面,此时仍然存在着构造函数继承中子类不能访问到父类原型上面属性的缺点。
所以下面配合原型链继承将父类实例挂载到子类的原型上,此时子类就可以访问父类的原型上的属性,同时构造函数继承将子类的原型被父类属性覆盖的问题解决。
组合继承将前面两种继承的缺点都补全了,但是它也有缺点:
不足:重复调用两次父类函数。
4. 原型式继承
function object(o){ // 参数为父类 function F(){} F.prototype=o return new F() } const person={ sex:'man', age:20 } let realPerson=object(person) console.log(realPerson.sex);// man
通过在一个构造函数的原型上挂载父类,此时再将这个挂载了父类的构造函数实例返回出来,那么这个实例的隐式原型就为这个父类,所以此时子类实例对象可以访问到父类上的属性,这就是原型式继承。这个看起来跟理解起来是不是跟原型链继承很像,也挺好理解的。
缺点: 如果父类属性有引用类型,那么这个引用类型也会被共享出来。
function object(o){ function F(){} F.prototype=o return new F() } const person={ sex:'man', age:20, like:{ sports:'running' } } let r1=object(person) console.log(r1.like);// { sports: 'running' } let r2=object(person) r2.like.sports='singing' console.log(r1.like); // { sports: 'singing' }
实例化两个不同的对象,更改 r2 的sports 属性,结果 r1 的 sports 属性也被修改。
5. 寄生式继承
这种继承方式跟原型式继承有着异曲同工之妙,只不过可以增强这个父类。其存在的缺点也与原型式继承一样。
function createPerson(original) { var clone = Object.create(original) clone.say = function() { // 增强这个对象 console.log('hello'); } return clone }
6.寄生组合式继承
这个是一个比较完美的继承方式,使用组合继承与寄生继承。
function Person(name, age) { this.name = name this.age = age this.like = { sport: 'running', book: 'JavaScript' } this.getName = function () { console.log(this.name); } } Person.prototype.sayHello = function () { console.log('你好'); } function Student(name, age) { Person.call(this, name, age) // 借用构造函数 } const Fn=function(){} Fn.prototype=Person.prototype Student.prototype = new Fn() let s1 = new Student('bruce', 18) s1.sayHello() // 你好 console.log(s1);
这种继承方式结合了组合继承,借助一个空的构造函数,将父类的原型挂载到这个空的构造函数上面,然后将其附在子类的原型上面,这样就解决了组合式继承的缺点。
7.class 类继承
Class 可以通过extends
关键字实现继承,让子类继承父类的属性和方法。
class Person{ constructor(name){ this.name=name } } class Student extends Person{ constructor(name,age){ super(name) this.age=age } } let s=new Student('bruce',20) console.log(s); //Student { name: 'bruce', age: 20 }
以上就是JavaScript实现继承的7种方式总结的详细内容,更多关于JavaScript继承的资料请关注脚本之家其它相关文章!