• 记录--JS精粹,原型链继承和构造函数继承的 “毛病”


    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

    先从面向对象讲起,本瓜认为:面向对象编程,它的最大能力就是:复用!

    咱常说,面向对象三大特点,封装、继承、多态。

    这三个特点,以“继承”为核心。封装成类,是为了继承,继承之后再各自发展(重写),可理解为多态。所以,根本目的是为了继承,即“复用“!

    如果你用 JavaScript 面向对象的能力来编程的话,能想到的,也只供使用的就是:基于原型

    因为这门语言设计就是这样,我们之前也提过:JavaScript的语言设计主要受到了Self(一种基于原型的编程语言)和 Scheme(一门函数式编程语言)的影响;

    它复用的能力就是来自原型!

    好了,有这个认知基础,我们再看原型继承。

    原型链继承

    原型继承最直接的一种实现就是:原型链继承

    ECMA-262 把原型链定义为 ECMAScript 的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。
    

    我们来看看原型链继承的代码实现:

    1. function SuperType() {
    2. this.property = true;
    3. }
    4. function SubType() {
    5. this.subproperty = false;
    6. }
    7. SuperType.prototype.getSuperValue = function() {
    8. return this.property;
    9. };
    10. SubType.prototype.getSubValue = function () {
    11. return this.subproperty;
    12. };
    13. SubType.prototype = new SuperType(); // 对 SubType 得原型链重新指定,是原型链继承
    14. let instance = new SubType();
    15. console.log(instance.getSuperValue()); // true

    还需要再额外说明查找关系吗??不懂得工友可见这篇 《歪理解?原型链中的函数和对象》

    这里还是用代码展示下它们的指向关系吧:

    上面例子中有 1 个对象 instance , 两个函数,SuperType 和 SubType 。函数是上帝,对象是基本物质。继承来自两方面:1. 继承自祖先(遗产);2. 继承自上帝(天赋);

    1. // 继承自祖先(遗产)
    2. instance.__proto__ === SubType.prototype // true
    3. SubType.prototype.__proto__ === SuperType.prototype // true
    4. // 继承自上帝(天赋)
    5. SuperType.__proto__ === Function.prototype // true
    6. SubType.__proto__ === Function.prototype // true
    7. SuperType.prototype.__proto__ === Object.prototype // true
    8. Object.prototype.__proto__ === null // true

    当然,我们并不是来讲原型链的。重点是:点出原型链继承的“问题”!!

    它的主要问题出现在:原型中包含引用值的时候,原型中包含的引用值会在所有实例间共享。

    1. function SuperType() {
    2. this.colors = ["red", "blue", "green"];
    3. }
    4. function SubType() {}
    5. SubType.prototype = new SuperType() // 原型链继承
    6. let s1 = new SubType()
    7. let s2 = new SubType()
    8. s1.colors.push("yellow")
    9. console.log(s1.colors) // ['red', 'blue', 'green', 'yellow']
    10. console.log(s2.colors) // ['red', 'blue', 'green', 'yellow']

    colors 是个数组,引用值,当它共享给 SubType 的时候,用的是引用值,当我们实例化的时候,如果其中一个实力对它做出了修改,将会影响到其它实例的引用。

    其实,我们也知道,很少在业务代码中这样去写继承:SubType.prototype = new SuperType() ,原型链继承会造成复用的混乱,所以它基本不会被单独使用。

    构造函数继承

    构造函数继承,也叫做:“盗用构造函数”,“对象伪装”或“经典继承”。

    基本思路:在子类构造函数中用 apply()和 call()方法调用父类构造函数。

    上一小节的例子改造为:

    1. function SuperType() {
    2. this.colors = ["red", "blue", "green"];
    3. }
    4. function SubType() {
    5. SuperType.call(this) // 构造函数继承
    6. }
    7. let s1 = new SubType()
    8. let s2 = new SubType()
    9. s1.colors.push("yellow")
    10. console.log(s1.colors) // ['red', 'blue', 'green', 'yellow']
    11. console.log(s2.colors) // ['red', 'blue', 'green']

    完美解决原型链继承的问题,但是它也有它的问题,也是使用构造函数模式自定义类型的问题,

    即:必须在构造函数中定义方法(在原型上定义方法,子类是访问不到的),函数不能重用。

    1. function SuperType() {
    2. }
    3. function SubType() {
    4. SuperType.call(this) // 构造函数继承
    5. }
    6. SuperType.prototype.fn = ()=>{}
    7. let s1 = new SubType()
    8. console.log(s1.fn) // undefined
    1. function SuperType() {
    2. this.fn=()=>{}
    3. }
    4. function SubType() {
    5. SuperType.call(this) // 构造函数继承
    6. }
    7. let s1 = new SubType()
    8. let s2 = new SubType()
    9. console.log(s1.fn === s2.fn) // false

    而这一点,在原型链继承中,又是可以的。。。

    1. function SuperType() {}
    2. function SubType() {}
    3. SuperType.prototype.fn = ()=>{}
    4. SubType.prototype = new SuperType() // 原型链继承
    5. let s1 = new SubType()
    6. console.log(s1.fn) // ()=>{}
    1. function SuperType() {
    2. this.fn=()=>{}
    3. }
    4. function SubType() {}
    5. SubType.prototype = new SuperType() // 原型链继承
    6. let s1 = new SubType()
    7. let s2 = new SubType()
    8. console.log(s1.fn === s2.fn) // true

    所以,综上,原型链继承和构造函数继承的 “毛病” 分别是:

    1. 原型链继承:所有继承的属性和方法都会在对象实例间共享,无法做到实例私有。
    2. 构造函数继承:子类不能访问父类原型上的方法。

    咱就是说,这东西怎么这么拧巴呢。。。

    于是乎一个规避二者“毛病”的继承方式出现了:组合继承~~

    组合继承

    目前最流行的继承模式是组合继承!

    思路是:使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性。

    1. function SuperType(name){
    2. this.name = name;
    3. this.colors = ["red", "blue", "green"];
    4. }
    5. function SubType(name, age){
    6. SuperType.call(this, name) // 构造函数继承
    7. this.age = age;
    8. }
    9. SuperType.prototype.sayName = function() {
    10. console.log(this.name);
    11. }
    12. SubType.prototype = new SuperType() // 原型链继承
    13. SubType.prototype.sayAge = function() {
    14. console.log(this.age);
    15. }
    16. let s1 = new SubType("Nicholas", 29)
    17. let s2= new SubType("Greg", 27)
    18. s1.colors.push("yellow")
    19. console.log(s1.colors) // ['red', 'blue', 'green', 'yellow']
    20. console.log(s2.colors) // ['red', 'blue', 'green']
    21. s1.sayName() // Nicholas
    22. s2.sayName() // Greg
    23. s1.sayAge() // 29
    24. s2.sayAge() // 27

    组合继承,总结起来就是,属性(特别是引用值)通过构造函数去继承,而公用的、需要复用的方法用原型链去继承!!

    说实话,JS 继承真的很奇怪。。。并不是面向对象语言,又要通过原型链去模拟面向对象,真的很多小坑的点需要去注意。(哈哈哈,想想还是函数式好,清晰)

    本文转载于:

    https://juejin.cn/post/7107779239281164301

    如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

     

  • 相关阅读:
    Qt数据库之QSqlQueryModel
    信创国产化,高速数据传输系统-UTS
    机器人制作开源方案 | 四轴飞行器
    导出域内信息
    defineProperty 与 Proxy 的区别
    二叉树题目:最大二叉树
    全面吃透JAVA Stream流操作,让代码更加的优雅
    【Java】运算符
    袋鼠云数栈UI5.0体验升级背后的故事:可用性原则与交互升级
    新时代背景下智慧城市的建设与5G技术有何关联
  • 原文地址:https://blog.csdn.net/qq_40716795/article/details/125622693