1. 构造函数继承
关键代码:Son:Parent.call(this,...args)
原理:在Son构造函数中,调用父类的构造函数,利用call改变了this指向,使得Son对象添加了name属性,完成继承
缺点:父函数原型链上的属性和方法不能被继承
(Child对象没有继承Parent对象的原型,只是通过调用父类构造方法,给子类添加了属性)
继承实现:
function Parent(name) {
this.name = name
}
function Son(name, age) {
// ******
Parent.call(this, name)
this.age = age
}
Parent.prototype.sayHi = function() {
console.log('hi')
}
代码测试:
let p = new Parent('ls')
p.sayHi()// hi
let son = new Son('zs', 15)
console.log(son)
son.sayHi() // 报错
运行结果:
2. 原型链继承
关键代码: Son.prototype = new Parent();
原理:通过子类的原型指向父类构造函数的实例,使得子类可以沿原型链访问到父类及父类原型上的属性和方法;
优点:能继承构造函数属性,也能继承父类原型上的属性和方法
缺点: 父类原型上的引用类型的属性被修改后,可能会影响所有的实例
注意:// s2.address = {1:1} 这样对某个实例整体修改,只会改变该实例的address
// s2.address[0] = 444 这样对某个引用属性的具体某一项进行修改,会改变所有实例
只是可以继承父类的属性和方法,但在子类实例对象上并不具有父类的所有属性和方法
function Parent(name) {
this.name = name
this.address = ['122','144']
}
Parent.prototype.sayHi = function() {
console.log('hi')
}
function Son(name, age) {
this.name = name
this.age = age
}
Son.prototype = new Parent('ls')
代码测试:
let s = new Son('ww',17)
console.log(s)//Son {name: 'ww', age: 17}
console.log(Son.prototype)// Parent {name: 'ls', address: Array(2)}
s.sayHi() // hi
let s2 = new Son('yj', 22)
console.log(s2)
s2.address[0] = 444
// s2.address = {1:1}
console.log(s.address)// [444, '144']
console.log(s2.address)// [444, '144']
s2.address = {1:1}
console.log(s.address)// [444, '144']
console.log(s2.address)// {1:1}
运行结果:
只是可以继承父类的属性和方法(通过原型链查找到父类的属性和方法),但在子类实例对象上并不具有父类的所有属性和方法
3. 组合继承
组合继承 :构造函数(Parent.call(this, name))
+原型链继承(Son.prototype = new Parent('zs'))
原理:结合前两者,即给子实例中添加了父类的属性和方法,也通过原型链子类可以访问父类及父类原型上的方法(父类上的属性和方法重复继承了两遍(耗内存),子类的构造函数会覆盖原型上的父类构造函数)
优点:解决了父类引用属性被共享的问题;子实例中具有父类的所有属性和方法
缺点:使用子类创建实例对象时,其原型中会存在两份相同的属性和方法,造成性能上的浪费;
继承实现:
function Parent(name) {
this.name = name
this.address = ['bj','xa','sh']
}
function Son(name, age) {
// ******
Parent.call(this, name)
this.age = age
}
Parent.prototype.sayHi = function() {
console.log('hi')
}
Son.prototype = new Parent('zs')
代码测试:
let s1 = new Son('ls', 22)
console.log(s1) // Son {name: 'ls', address: Array(3), age: 22}
let s2 = new Son('ww', 20)
console.log(s2) // Son {name: 'ww', address: Array(3), age: 20}
s1.address[0] = '11111'
console.log(s1.address) // ['11111', 'xa', 'sh']
console.log(s2.address) // ['bj', 'xa', 'sh']
运行结果:
可见,具有重复的属性
可见,解决了引用参数共享的问题。
4. 原型式继承
核心原理:object.create()原理:
用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了一个可以随意增添属性的实例或对象
实现方法:创建一个空函数,空函数的原型 等于 需要创建的对象,返回 对象的一个实例
特点:用函数封装,将传入的参数对象 变成了 返回参数对象的原型
缺点:所有实例都会继承原型上的属性,无法实现复用(新实例属性都是后面添加的)
继承实现:
function createObj(obj) {
function Parent() { }
Parent.prototype = obj
return new Parent()
}
let person = {
name: 'zs',
age: 20,
address: ['xa', 'sh'],
sayHi: function() {
console.log('hi')
}
}
let p1 = createObj(person)
let p2 = createObj(person)
console.log(p1)
console.log(p2)
// p1,p2为两个空的父类实例对象,该实例对象的原型为:传入参数的对象
// 将传入的参数对象 变成了 返回参数对象的原型
console.log(p1.__proto__ === person) // true
console.log(p2.__proto__ === person) // true
p1.sayHi()
p2.age = 32 // 给p2加了一个age属性=32
console.log('p1.age='+p1.age) // 20// 原型上的age
console.log('p2.age='+p2.age) // 32// 实例上的age
运行结果:
5. 寄生式继承
核心思想:使用Object.create api克隆一个(传入的对象的拷贝)对象,并返回该拷贝对象
创建了一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象
优点:没有创建自定义类型,只是给原型式继承 套了个盒子,返回该对象
缺点:没用到原型,无法复用
继承实现:
function createObj(obj) {
let cloneObj = Object.create(obj)
cloneObj.sayHi = function() {
console.log('Hi')
}
return cloneObj
}
let person = {
name: 'zs',
age: 20,
address: ['xa', 'sh'],
sayHi: function() {
console.log('hi')
}
}
let p1 = createObj(person)
let p2 = createObj(person)
console.log(p1)
console.log(p2)
console.log(p1.__proto__ === person) // true
console.log(p2.__proto__ === person) // true
p1.sayHi()//Hi
p2.age = 32
console.log('p1.age='+p1.age) // 20
console.log('p2.age='+p2.age) // 32
运行结果:
6. 寄生组合式继承
核心代码:Son.prototype = Object.create(Parent.prototype)
+Parent.call(this, name)
组合继承最大的问题:调用两次父类构造函数;一次是在创建子类时,一次是在子类构造函数内部
思想:组合继承的改良,通过子类的原型指向父类原型的拷贝,解决了组合继承的缺点
不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个拷贝
使用寄生式继承来继承父类原型,然后将返回的 新对象 赋给子类原型
代码实现:
function Parent(name) {
this.name = name
this.address = ['xa','sh','bj']
this.ParentFun = function() {
console.log('ParentFun')
}
}
Parent.prototype.sayHi = function() {
console.log('Hi ! Parent.prototype.sayHi')
}
function Son(name,age) {
Parent.call(this, name)
this.age = age
}
Son.prototype = Object.create(Parent.prototype)
Son.prototype.SonFun = function() {
console.log('SonFun')
}
测试:
let s1 = new Son('zs',12)
let s2 = new Son('ls',32)
console.log(s1)
console.log(s2)
运行结果:
可见,没有重复的父类属性和方法
测试修改父类引用属性的某项值:
s1.address[0] = 's1'
s2.address[0] = 's2'
console.log(s1.address)
console.log(s2.address)
console.log(s1)
console.log(s2)
运行结果
可见,可以正常修改,且不影响其他实例的修改。
7. class + extends+super ES6继承
代码实现:
class Parent{
constructor(name) {
this.name = name
this.address = ['sh','xa']
}
parentFun() {
console.log('parent Function')
}
}
class Son extends Parent{
constructor(name, age) {
super(name)
this.age = age
this.grade = ['122','333']
}
parentFun() {
console.log('Son Fun')
}
sonFun() {
console.log('son own fun')
}
}
测试代码:
let p = new Parent('fq', 44)
console.log(p)
let s = new Son('zs', 20)
console.log(s)
运行截图:
在类中写的方法都会挂载到原型上?
总结:
1. 构造函数继承:Son:Parent.call(this,...args)
2. 原型链继承:Son.prototype = new Parent();
3. 组合继承:Son:Parent.call(this,...args) + Son.prototype = new Parent();、
4. 原型式继承:
function createObj(obj) {
function Parent() { }
Parent.prototype = obj
return new Parent()
}
5. 寄生式继承:
function createObj(obj) {
let cloneObj = Object.create(obj)
cloneObj.sayHi = function() {
console.log('Hi')
}
return cloneObj
}
6. 寄生组合式:Son.prototype = Object.create(Parent.prototype) + Parent.call(this, name)
7. ES6 继承:class + super + extends