- 原型链继承
代码示例:
Person.prototype.age = 55;
function Person() { }
Student.prototype = new Person()
function Student(name) {
this.name = name
}
var zhangsan = new Student('zhangsan')
function Father() {
this.sex = 'male'
}
Father.prototype.age = 30
Father.prototype.address = {
province: '浙江省',
city: '杭州市'
}
function Son() {
this.school = '第一小学'
}
Son.prototype = new Father()
var zhangsan = new Son()
console.log(zhangsan.age) // 30
var zhangsi = new Son()
zhangsan.address.city = '绍兴市'
console.log(zhangsi)
这个示例是让Son继承Father的实例/原型链属性/方法。
Son.prototype = new Father()这句话是什么意思呢?
我们知道
①原型链是通过__ proto 属性一层一层向上寻找的,
②实例化Son的时候,Son会把自身的prototype属性赋给实例对象(zhangsan)的 proto __属性。
Son.prototype = new Father()的目的就是让zhangsan通过__ proto __ 找到Father的实例,进而能访问到Father的自身和原型链上的属性和方法
如果不这样操作,那么zhangsan的 __ proto __ 向上一路找到Object.prototype去了。
这两张图就是改变Son.prototype指向前后的差别,可以看出如果不改变,根本没Father什么事,也就继承不了其相关属性和方法
接着上述代码,我们再创建一个Son的实例
var zhangsi = new Son()
zhangsan.address.city = '绍兴市'
console.log(zhangsi.address.city) //绍兴市
可以看出zhangsi的address也被改变了,但是我们明明只是操作的zhangsan的address啊
这就是此继承方式的弊端之一,如果父类实例/原型链上有引用类型属性,那么多个子类实例的其中一个实例对象改变这个引用类型属性,就会导致其他子类实例对象的相关属性也跟着改变。
接着上述代码,我们来给Father.prototype添加一个属性
Father.prototype.job = ‘teacher’
console.log(zhangsan.job) // teacher
console.log(zhangsi.job) // teacher
这个特点,说不上完全是优点还是缺点,优点是易于扩展,能在已经创建好的实例上添加属性/方法,缺点是所有子类实例都能拿到这个新增属性/方法,隐私性不好。
总结:
优点:
1.结构简单,易于理解
2.父类新增的属性/方法,子类实例对象都能访问到,扩展性好
缺点:
1.子类能改变父类原型中引用类型的属性,且一个实例对象改变,其他实例对象跟着改变
2.子类实现继承的时候无法通过构造函数传参
3.不能实现多继承,子类不能继承多个父类的属性/方法
- 构造函数继承
function Father(age) {
this.age = age;
}
Father.prototype.job = 'teacher'
Father.prototype.run = function () {
console.log('I run very fast');
}
function Son(age, name) {
Father.call(this, age)
this.name = name
}
Son.prototype.height = '180'
Son.prototype.eat = function () {
console.log('I eat a lot')
}
var zhangsan = new Son(18, 'zhangsan')
console.log(zhangsan.age); // 18
console.log(zhangsan.height); // 180
console.log(zhangsan.eat); // function eat(){}
console.log(zhangsan.job); // undefined
console.log(zhangsan.run); // undefined
Son函数并没有age属性,但是Son的实例对象zhangsan却有age属性,说明age是继承而来。
这种方法的关键点在于,要在子类的构造函数中使用call/apply调用父类的构造函数
总结:
优点:
1.可以实现多继承(子类构造函数中调用多个父类构造函数)
2.创建子类实例时,可以向父类构造函数传参
缺点:
1.不能继承父类实例和原型上的方法/属性
- 组合继承
function Father(age) {
this.age = age;
}
Father.prototype.job = 'teacher'
Father.prototype.run = function () {
console.log('I run very fast');
}
function Son(age, name) {
Father.call(this, age)
this.name = name
}
Son.prototype = new Father()
Son.prototype.height = '180'
Son.prototype.eat = function () {
console.log('I eat a lot')
}
var zhangsan = new Son(18, 'zhangsan')
console.log(zhangsan.age); // 18
console.log(zhangsan.height); // 180
console.log(zhangsan.eat); // function eat(){}
console.log(zhangsan.job); // teacher
console.log(zhangsan.run); // function run(){}
这种方式的思路是使用原型链实现对原型属性和方法的继承,借用构造函数实现对函数实例属性和方法的继承。是最常用的一种方式。
- es6 class 继承
class Father {
constructor(age) {
this.job = 'teacher'
this.age = age
}
run() {
console.log('I run very fast');
}
}
class Son extends Father{
constructor(age) {
super(age)
this.height = "180"
}
eat() {
console.log('I eat a lot')
}
}
var zhangsan = new Son(55)
console.log(zhangsan.job) //teacher
console.log(zhangsan.age) //55
console.log(zhangsan.run); // function run(){}
console.log(zhangsan.height); //180
console.log(zhangsan.eat); //function eat(){}
es6语法引入class和extends关键字来实现继承,还可以通过static关键字定义静态属性/方法,虽然这只是语法糖级别的,但是更加语义化,向传统的后端语言靠近一步。
以上就是常见的js继承方式了,其他还有一些寄生式继承、寄生组合式继承等,我窃以为这几种方式不太常用,把文中说的几种形式彻底搞懂就足够大多数场景使用了。
个人总结,如有错误,请指正。