关于 Javascript 的几种继承
原型链的概念:
原型链通俗易懂的理解就是可以把它想象成一个链条,互相连接构成一整串链子
而原型链中就是实例对象和原型对象之间的链接
每个函数都有一个prototype属性,这个prototype属性就是我们的原型对象,我们拿这个函数通过new构造函数创建出来的实例对象,这个实例对象自己会有一个指针(_proto_)指向他的构造函数的原型对象
这样构造函数和实例对象之间就通过( _proto_ )连接在一起形成了一条链子
prototype,用来实现基于原型的继承与属性的共享
避免了代码冗余,公用的属性和方法,可以放到原型对象中,这样,通过该构造函数实例化的所有对象都可以使用该对象的构造函数中的属性和方法!同时减少了内存占用
继承方式:
- 原型链继承
缺点:Parent 中的引用属性会被每个子类实例共享,以及继承过多 Parent 中定义过的属性
function Parent() { this.parentPrototype = "parent prototype" //验证这种继承方法的确定,如果父类示例中存在一个引用类型的属性,将会被所有子类共享 this.parentObj = { info: "我是 parent 引用属性parentObj中的 info" } } function Children() { } //将Children的原型对象指定为Parent的实例,通过原型链,将Parent中的属性赋值给Children实例 Children.prototype = new Parent(); const a = new Children(); console.log(a.parentPrototype); // parent prototype //缺点 const b = new Children(); //在a示例中改动继承的引用属性 a.parentObj.info = "我是a实例中 引用属性parentObj中的 info" //b与a示例共享引用属性 console.log(b.parentObj.info); // 我是a实例中 引用属性parentObj中的 info
- 借用构造函数继承
优点:1.避免了子类实例共享引用属性的情况;2.可以在实例化时给Parent构造函数传递参数
缺点:如果Parent中存在一个函数,那么每次实例化Children的时候,都会创建一个同样函数,函数的复用性就难以体现
function Parent() { this.parentPrototype = "parent prototype" this.obj = { info: "parent obj info" } this.fn = function () { console.log("打印功能") } }
// 在子类构造函数内部调用父类的构造函数,需要使用call绑定上下文 function Children() { Parent.call(this); } const a = new Children(); console.log(a.parentPrototype); // parent ptototype //缺点 此时Parent()会再次创建一个fn函数,这个是没有必要的 const b = new Children(); a.obj.info = "a obj info"; //优点 避免了子类实例共享引用属性 console.log(b.obj.info) // parent obj info;
- 组合继承(原型链 + 构造函数)--常用的继承方式
优点:避免了子类实例共享引用属性,同时避免了父类构造函数重复对function属性的创建
function Parent() { this.parentPrototype = "我是Parent 中的属性" } //Parent中的方法,在原型上定义 Parent.prototype.pFn = function () { console.log('我是Parent中的方法'); } function Children() { //Parent中的属性仍然在构造函数中继承 Parent.call(this); } //将Children的原型对象赋值为 Parent实例,这样Parent中的方法也能够被Children继承 Children.prototype = new Parent(); const c = new Children(); console.log(c.parentPrototype); //我是Parent 中的属性 c.pFn(); //我是Parent中的方法
- 原型式继承
缺点:和原型链继承一样,后代实例会共享父类引用属性
function objFn(o) { o.objFnPrototype = "我是 objFnPrototype" function F() {} F.prototype = o; return new F(); } let a = objFn({ name: "name1" }); console.log(a.name); //name1 console.log(a.objFnPrototype); //我是 objFnPrototype
- 寄生继承
缺点:和原型链继承一样,parent中的引用属性,会被所有示例共享
function createObje(obj) { let clone = Object.assign(obj); //接受到对象后,原封不动的创建一个新对象 clone.prototype1 = "我是新增的prototype1"; //在新对象上新增属性,这就是所谓的寄生 return clone; //返回新对象 } const parent = { parentPrototype: "parentPrototype" } //c实例,就继承了parent的所有属性 let c = createObje(parent); console.log(c.parentPrototype); //parentPrototype
- 寄生组合继承(寄生 + 组合)
优点:和组合继承一样,只不过没有组合继承的调用两次父类构造函数的缺点
function inherProto(superType, subType) { //拷贝一个超类的原型副本 let proto = { ...superType.prototype }; //将原型的超类副本作为子类的原型对象,也就是第一种中的原型链继承方式,只不过继承的是超类原型的副本 subType.prototype = proto; //这一步比较迷,官方的说法是,我们在拷贝超类的原型的时候,拷贝的proto对象,将会丢失默认自己的构造函数,也就是superType, //所以我们这里将它的构造函数补全为subType。貌似不做这一步也没啥问题,但是缺了点东西可能会有其他的副作用,所以还是补上 proto.constructor = subType; } function Super() { this.superProto = "super proto"; this.colors = ["red", "yelloy"]; } function Sub() { this.subProto = "sub proto"; this.name = "sub name"; //这里还是借用构造函数的套路 Super.call(this); } Super.prototype.getName = function () { console.log(this.name); } //这里要在定义完Super的属性后执行,因为继承的是超类原型的副本,与Super.prototype是两个对象,在这之后再改变Super.prototype,就已经不会在影响到Sub所继承的副本超类原型对象了 inherProto(Super, Sub); let a = new Sub(); console.log(a.getName);
引用:
https://www.cnblogs.com/wrhbk/p/14477637.html
https://blog.csdn.net/qq_51486722/article/details/122863959