常见的实现继承的方式有以下几种:
1.原型链式继承
2.构造函数式继承
3.组合式继承
4.原型式继承
5.寄生式继承
6.寄生组合式继承
下面来详细讲些以下每种继承的实现方式,及优缺点
先声明一个叫Parent的构造函数 (引:JavaScript中的构造函数是一种特殊的函数,它用于创建新的对象实例)
function Parent() { this.name = 'parent1'; this.play = [1, 2, 3]; this.sayHei = function(){ console.log("Hei~我是:" + this.name) } } Parent.prototype.getFun = function(){ console.log("你找到了这个方法!输出为:"+this.play) }
一:原型链式继承
实现方案:
function ChildOne() { this.type = 'childOne';
}
// 用.prototype 实现原型链式继承
ChildOne.prototype = new Parent();
var c1 = new ChildOne()
console.log(c1.name) // 'parent1'
console.log(c1.play) // [1, 2, 3]
console.log(c1.sayHei) // 'Hei~我是:parent1'
console.log(c1.getFun) // '你找到了这个方法!输出为:1,2,3'
缺点:
1.原型中包含的引用值会在所有实例间共享 这是因为 在使用原型实现继承时,原型实际上变成了另一个类型的实例
var c2 = new ChildOne() console.log(c2.play) // [1,2,3] c2.play.push(4) console.log(c2.play) // [1,2,3,4] console.log(c1.play) // [1,2,3,4]
console.log(ChildOne.prototype.play) // [1,2,3,4]
// 由上可见,ChildOne原型上的play属性,都被改了
2.子类型在实例化时不能给父类型的构造函数传参
二:构造函数式继承
实现方案
function ChildTwo (){ // 借助call 来调用Parent函数 Parent.call(this) this.type = 'child' } var t1 = new ChildTwo() var t2 = new ChildTwo() console.log(t1.name) // 'parent1' console.log(t1.play) // [1, 2, 3] console.log(t1.sayHei) // 'Hei~我是:parent1' t2.play.push(4) console.log(t2.play) // [1,2,3,4] console.log(t1.play) // [1,2,3]
缺点:
只能继承父类的实例属性和方法,不能访问父类原型上定义的方法
console.log(t1.getFun) // Uncaught TypeError: t1.getFun is not a function console.log(t2.getFun) // Uncaught TypeError: t2.getFun is not a function
三:组合式继承 (把原型链式继承和构造函数式继承的优点结合。基本的思路是:使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。)
实现方案:
function ChildThree() {
// 继承属性 Parent.call(this) }
// 继承方法 ChildThree.prototype = new Parent()
var s1 = new ChildTwo()
var s2 = new ChildTwo()
console.log(s1.name) // 'parent1'
console.log(s1.play) // [1, 2, 3]
console.log(s1.sayHei) // 'Hei~我是:parent1'
console.log(s1.getFun) // '你找到了这个方法!输出为:1,2,3'
s2.play.push(4)
console.log(s2.play) // [1,2,3,4]
console.log(s1.play) // [1,2,3]
完美的解决了上面的问题,唯一的缺点就是 Parent
执行了两次【Parent.call(this) 和 new Parent()】,造成了多构造一次的性能开销
以下是实现对普通对象的继承
let parent = { name: "papa", friends: ["p1", "p2", "p3"], getName: function() { return "你好,我是:"+ this.name; } };
四:原型式继承
用Object.create()实现,这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)
想更详细的了解Object.create() 点这里
实现方案
var child1 = Object.create(parent) var child2 = Object.create(parent) console.log(child1.name) // "papa" console.log(child1.friends) // ["p1", "p2", "p3"]
console.log(child1.getName()) // "你好,我是:papa"
缺点:因为Object.create
方法实现的是浅拷贝, 所以和原型链式继承一样 多个实例的引用类型属性指向相同的内存,存在篡改的可能
var child2 = Object.create(parent)
console.log(child2.friends) // ["p1", "p2", "p3"]
child2.friends.push("p4")
console.log(child2.friends) // ["p1", "p2", "p3","p4"]
console.log(child1.friends) // ["p1", "p2", "p3","p4"]
五:寄生式继承
创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象
实现方案
function clone(origin){ let clone = Object.create(origin) clone.getFriends = function(){ return this.friends; } return clone } var j1 = clone(parent)
console.log(j1.name) // "papa"
console.log(j1.friends) // ["p1", "p2", "p3"]
console.log(j1.getName()) // "你好,我是:papa"
缺点跟原型式继承一样
六:寄生组合式继承
function clone (parent, child) { // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程 child.prototype = Object.create(parent.prototype); } function Parent() { this.name = 'parent1'; this.play = [1, 2, 3]; this.sayHei = function(){ console.log("Hei~我是:" + this.name) } } Parent.prototype.getFun = function(){ console.log("你找到了这个方法!输出为:"+this.play) } function Child() { Parent.call(this); } clone(Parent, Child);
var f1 = new Child()
var f2 = new Child()
console.log(f1.name) // 'parent1'
console.log(f1.play) // [1, 2, 3]
console.log(f1.sayHei) // 'Hei~我是:parent1'
console.log(f1.getFun) // '你找到了这个方法!输出为:1,2,3'
console.log(f2.play) // [1, 2, 3]
f2.play.push(4)
console.log(f2.play) // [1, 2, 3,4]
console.log(f1.play) // [1, 2, 3]
属性都得到了继承,方法也没问题 【这种方式是较优的解决继承的方式】
注:Es6 里 Class 可以通过extends
关键字实现继承,跟寄生组合继承的方式基本类似
参考链接:
1. https://vue3js.cn/interview/JavaScript/inherit.html#%E4%B8%80%E3%80%81%E6%98%AF%E4%BB%80%E4%B9%88
2. https://juejin.cn/post/7144903568649093156#heading-19