实现继承分为多种方式,但主要还是通过原型链来实现的。
原型链继承
原型链继承就是使子类的原型指向父类构造出来的实例。
核心代码:
Child.prototype = new Father()
// 伪代码:Child.__proto__ = Father.prototype
可以看到,子类的原型对象被赋值为父类的一个实例。这说明:通过子类构造出来的实例能够访问父类原型上的属性和方法。
如果说父类构造函数里面包含一个引用值的属性,看代码
function Father() {
this.friends = ['Lucy', 'Mike', 'John'];
}
function Child() {}
Child.prototype = new Father()
let c1 = new Child()
console.log(c1.friends) // ['Lucy', 'Mike', 'John']
c1.friends.push('May') // 这里对c1的friends属性进行修改
console.log(c1.friends) // ['Lucy', 'Mike', 'John', 'May']
let c2 = new Child()
console.log(c2.friends) // ['Lucy', 'Mike', 'John', 'May']
那么当执行到Child.prototype = new Father()
这一句的时候,相当于创建了Child.prototype.friends
属性。最终结果是子类所有的实例都会共享这个friends
属性,而且从上面的代码可以看到,我们只是修改了实例c1
的属性,这却影响到实例c2
的属性,很显然这并不是我们想要的。而且可以发现,子类在实例化的时候并不能给父类传参。所以我们引出借用构造函数继承的方法。
借用构造函数继承
借用构造函数继承就是在子类构造函数中调用父类构造函数。
核心代码:
function Child() {
Father.call(this)
}
这里可以使用call()
和apply()
方法以新创建的对象为上下文执行构造函数,意思是Father
构造函数在Child
实例化的新对象的上下文中执行了,相当于新的Child
对象上运行了Father
函数中的所有初始化代码。看代码,我们这里还是用上面的例子来测试,看看有什么不同?
function Father(name) {
this.name = name
this.friends = ['Lucy', 'Mike', 'John']
}
function Child(name) {
Father.call(this, name) // 相当于子类拥有了父类实例上的属性和方法
}
let c1 = new Child('xiaoming');
c1.friends.push('May')
console.log(c1.friends) // ['Lucy', 'Mike', 'John', 'May']
let c2 = new Child('xiaohong');
console.log(c2.friends) // ['Lucy', 'Mike', 'John']
这时你会发现,我们解决了原型链继承中子类实例化时无法给父类传参的问题,而且当父类构造函数里面包含引用值的属性时,由于每次实例化子类时,都会运行一次父类构造函数中的初始化代码,也就是每个新创建出来的实例,都会有属于自己的friends属性,所以也解决了所有子类共享同一个引用值属性所带来实例间互相影响的问题。
好像看起来还不错, 但是有一个问题,如果你想共享一个方法,你会怎么做?方法应该写在哪里?还是在上面的基础上修改,上代码
情况一:把方法写在父类构造函数里面
function Father(name) {
this.name = name
this.friends = ['Lucy', 'Mike', 'John']
-----------edit code----------
this.sayName = function() {
console.log(this.name)
}
------------------------------
}
function Child(name) {
Father.call(this, name) // 相当于子类拥有了父类实例上的属性和方法
}
let c1 = new Child('xiaoming');
c1.sayName() // xiaoming
let c2 = new Child('xiaohong');
c2.sayName() // xiaohong
这个结果虽然是我们要的,但是又有一个问题,我们把sayName()
这个方法写在父类构造函数里面,说明每次实例化都要重复地去创建这个函数,这就多余了。所以尝试下其他方式。
情况二:把方法写在父类的原型上面
function Father(name) {
this.name = name
this.friends = ['Lucy', 'Mike', 'John']
}
-----------------edit code----------------
Father.prototype.sayName = function() {
console.log(this.name)
}
------------------------------------------
function Child(name) {
Father.call(this, name)
}
let c1 = new Child('xiaoming')
c1.sayName() // TypeError: c1.sayName is not a function
报错了,Father.call(this, name)
这句代码只是让子类拥有父类实例上的属性和方法,并不能访问父类原型上的方法。所以这个方式也是不对
情况三:把方法写在子类的构造函数里面
function Father(name) {
this.name = name
this.friends = ['Lucy', 'Mike', 'John']
}
function Child(name) {
Father.call(this, name)
---------------edit code---------------
this.sayName = function() {
console.log(this.name)
}
---------------------------------------
}
let c1 = new Child('xiaoming')
c1.sayName() // xiaoming
其实这种情况跟第一种情况是类似的,我们想要的是方法能够复用,而不是重复创建同一个方法。
所以我们引出组合继承
组合继承
组合继承结合上述两者的优点,即使用原型链继承原型上的属性和方法,而通过借用构造函数继承实例上的属性。
继续上面的例子
function Father(name) {
this.name = name
this.friends = ['Lucy', 'Mike', 'John']
}
Father.prototype.sayName = function() {
console.log(this.name)
}
function Child(name) {
Father.call(this, name) // 借用构造函数继承父类实例上的属性
}
Child.prototype = new Father() // 原型链继承父类原型上的方法
let c1 = new Child('xiaoming');
c1.sayName() // xiaoming
c1.friends.push('May')
console.log(c1.friends) // ['Lucy', 'Mike', 'John', 'May']
let c2 = new Child('xiaohong');
c2.sayName() // xiaohong
console.log(c2.friends) // ['Lucy', 'Mike', 'John']
其实上面的代码还存在问题,那就是性能问题:就是父类的构造函数始终会被调用两次,一次是在给子类原型赋值的时候调用,另一次是在子类构造函数中调用。当属性多的时候,对性能会有影响。于是引出了寄生式组合继承。这里需要自行了解下原型式继承和寄生式继承。这里就不作介绍了。
寄生式组合继承
寄生式组合继承是不同过调用父类构造函数给子类原型赋值,而是通过取得父类原型的一个副本来给子类原型赋值。
function inheritPrototype(Child, Father) {
let prototype = Object.create(Father.prototype) // 复制父类原型的一个副本,constructor指向Father(因为Object.create()方法内部重写了原型)
prototype.constructor = Child // 重写construcotr指向
Child.prototype = prototype
}
function Father(name) {
this.name = name
this.friends = ['Lucy', 'Mike', 'John']
}
Father.prototype.sayName = function() {
console.log(this.name)
}
function Child(name) {
Father.call(this, name)
}
inheriPrototype(Child, Father);
let c1 = new Child('xiaoming');
c1.sayName(); // xiaoming
console.log(c1.constructor) // Child
所以,寄生式组合继承是实现继承的最佳模式。
网友评论