• JavaScript中子类调用父类方法的实现


    一、前言

    最近在项目中,前端框架使用JavaScript面向对象编程,遇到了诸多问题,其中最典型的问题就是子类调用父类(super class)同名方法,也就是如C#中子类中调用父类函数base.**。以下摘录了园友幻天芒JavaScript实现继承的几种方式 的具体介绍以作备忘。

    二、JavaScript实现继承的几种方式

    既然要实现继承,那么我们首先得有一个基类,代码如下:

    1. // 定义一个动物类
    2.     function Animal(name) {
    3.         // 属性
    4.         this.name = name || 'Animal';
    5.         // 实例方法
    6.         this.sleep = function () {
    7.             console.log(this.name + '正在睡觉!');
    8.         }
    9.     }
    10.     // 原型方法
    11.     Animal.prototype.eat = function (food) {
    12.         console.log(this.name + '正在吃:' + food);
    13.     };

    1、原型链继承

    核心: 将父类的实例作为子类的原型

    1. //定义动物猫
    2. function Cat() {
    3. }
    4. Cat.prototype = new Animal();
    5. Cat.prototype.name = 'cat';
    6.  
    7. // Test Code
    8. var cat = new Cat();
    9. console.log(cat.name); //cat
    10. cat.eat('fish'); //cat正在吃:fish
    11. cat.sleep(); //cat正在睡觉
    12. console.log(cat instanceof Animal); //true
    13. console.log(cat instanceof Cat); //true

    特点:

    1.非常纯粹的继承关系,实例是子类的实例,也是父类的实例;

    2.父类新增原型方法/原型属性,子类都能访问到;

    3.简单,易于实现;

    缺点:

    1.要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

    2.无法实现多继承;

    3.创建子类时,无法向父类构造函数传参;

    4.来自原型对象的属性是所有实例所共享;

    2、构造继承

    核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

    1. function Cat(name) {
    2.     Animal.call(this);
    3.     this.name = name || 'Tom';
    4. }
    5.  
    6. // Test Code
    7. var cat = new Cat();
    8. console.log(cat.name);
    9. console.log(cat.sleep());
    10. console.log(cat instanceof Animal); // false
    11. console.log(cat instanceof Cat); // true

    特点:

    1. 解决了1中,子类实例共享父类引用属性的问题;

    2. 创建子类实例时,可以向父类传递参数;

    3. 可以实现多继承(call多个父类对象);

    缺点:

    1. 实例并不是父类的实例,只是子类的实例;

    2. 只能继承父类的实例属性和方法,不能继承原型属性/方法;

    3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能;

    3、实例继承

    核心:为父类实例添加新特性,作为子类实例返回

    1. function Cat(name) {
    2.     var instance = new Animal();
    3.     instance.name = name || 'Tom';
    4.     return instance;
    5. }
    6.  
    7. // Test Code
    8. var cat = new Cat();
    9. console.log(cat.name);
    10. console.log(cat.sleep());
    11. console.log(cat instanceof Animal); // true
    12. console.log(cat instanceof Cat); // false

    特点:

    1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果;

    缺点:

    2.无法实现多继承;

    4、拷贝继承

    1. function Cat(name) {
    2.     var animal = new Animal();
    3.     for (var p in animal) {
    4.         Cat.prototype[p] = animal[p];
    5.     }
    6.     Cat.prototype.name = name || 'Tom';
    7. }
    8.  
    9. // Test Code
    10. var cat = new Cat();
    11. console.log(cat.name);
    12. console.log(cat.sleep());
    13. console.log(cat instanceof Animal); // false
    14. console.log(cat instanceof Cat); // true

    特点:

    1. 支持多继承;

    缺点:

    1. 效率较低,内存占用高(因为要拷贝父类的属性);

    2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到);

    5、组合继承

    核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

    1. function Cat(name) {
    2.     Animal.call(this);
    3.     this.name = name || 'Tom';
    4. }
    5. Cat.prototype = new Animal();
    6. Cat.prototype.constructor = Cat;
    7.  
    8. // Test Code
    9. var cat = new Cat();
    10. console.log(cat.name);
    11. console.log(cat.sleep());
    12. console.log(cat instanceof Animal); // true
    13. console.log(cat instanceof Cat); // true

    特点:

    1.弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法;

    2.既是子类的实例,也是父类的实例;

    3.不存在引用属性共享问题;

    4.可传参;

    5.函数可复用;

    缺点:

    1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了);

    6、寄生组合继承

    核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

    1. function Cat(name){
    2.     Animal.call(this);
    3.     this.name = name || 'Tom';
    4. }
    5. (function(){
    6.     // 创建一个没有实例方法的类
    7.     var Super = function(){};
    8.     Super.prototype = Animal.prototype;
    9.     //将实例作为子类的原型
    10.     Cat.prototype = new Super();
    11.     Cat.prototype.constructor = Cat; // 需要修复下构造函数
    12. })();
    13.  
    14. // Test Code
    15. var cat = new Cat();
    16. console.log(cat.name);
    17. console.log(cat.sleep());
    18. console.log(cat instanceof Animal); // true
    19. console.log(cat instanceof Cat); //true

    特点:

    1. 堪称完美;

    缺点:

    1.实现较为复杂;

    三、实现子类调用父类方法的解决方案

    上述的几种继承方式,如要实现子类Cat,在eat方法中调用父类Animal的eat方法,该如何实现呢? 以寄生组合继承为例:

    1. // 定义一个动物类
    2. function Animal(name) {
    3.     // 属性
    4.     this.name = name || 'Animal';
    5.     // 实例方法
    6.     this.sleep = function () {
    7.         console.log(this.name + '正在睡觉!');
    8.     }
    9. }
    10. // 原型方法
    11. Animal.prototype.eat = function (food) {
    12.     console.log("Father." + this.name + '正在吃:' + food);
    13. };
    14. function Cat(name) {
    15.     Animal.call(this);
    16.     this.name = name || 'Tom';
    17. }
    18. (function () {
    19.     // 创建一个没有实例方法的类
    20.     var Super = function () { };
    21.     Super.prototype = Animal.prototype;
    22.     //将实例作为子类的原型
    23.     Cat.prototype = new Super();
    24.     Cat.prototype.constructor = Cat; // 需要修复下构造函数
    25.     Cat.prototype.eat = function (food) {
    26.         console.log("SubClass." + this.name + '正在吃:' + food);
    27.         Super.prototype.eat.apply(this, Array.prototype.slice.apply(arguments));
    28.     };
    29. })();
    30.  
    31. // Test Code
    32. var cat = new Cat();
    33. console.log(cat.name);
    34. cat.eat('fish'); //cat正在吃:fish
    35. console.log(cat instanceof Animal); // true
    36. console.log(cat instanceof Cat); //true

    测试结果:

    其中的关键代码在于:

    我们看到,子类要想调用父类的方法,必须使用父类原型方法的apply(Super.prototype.eat.apply), 也就是必须得在子类中使用父类对象,这种以硬编码方式的调用,虽然能解决问题,但确不是特别优雅。

    四、优化后的子类调用父类方法的实现方式

    以上的继承方式,都不能实现子类调用父类方法, 在重写子类原型函数后,会将继承父类函数给覆盖。先来看实例:

    父类:

    1. var Animal = Class.extend({
    2.     construct: function (name) {
    3.         arguments.callee.father.construct.call(this);
    4.         this.name = name || "Animal";
    5.     },
    6.     eat: function (food) {
    7.         console.log("Father." + this.name + '正在吃:' + food);
    8.     },
    9.     sleep: function () {
    10.         console.log("Father." + this.name + '正在睡觉!');
    11.     }
    12. });

    子类:

    1. var Cat = Animal.extend({
    2.     construct: function () {
    3.         arguments.callee.father.construct.call(this,"Cat");
    4.     },
    5.     eat: function (food) {
    6.         console.log("SubClass." + this.name + '正在吃:' + food);
    7.         arguments.callee.father.eat.call(this, food);
    8.     },
    9.     sleep: function () {
    10.         console.log("SubClass." + this.name + '正在睡觉!');
    11.         arguments.callee.father.sleep.call(this);
    12.     }
    13. });

    测试代码:

    1. // Test Code
    2. var cat = new Cat();
    3. console.log(cat.name);
    4. console.log(cat.eat('fish'));
    5. console.log(cat.sleep());
    6. console.log(cat instanceof Animal); // true
    7. console.log(cat instanceof Cat); //true

    输出结果:

    在实例中,我们看到cat实例对象,在eat函数中,既调用了Cat类的eat函数,也调用了Animal类的eat函数,实现了我们想要达到的效果。实现该种继承的关键点在于Class基类中的处理,为子类每个function对象,都添加了father属性,指向父类的原型(prototype

    优化的设计,它使用了JavaScript中一点鲜为人知的特性:callee。
    在任何方法执行过程中,你可以查看那些通过"arguments"数组传入的参数,这是众所周知的,但很少有人知道"arguments"数组包含一个名为"callee"的属性,它作为一个引用指向了当前正在被执行的function,而后通过"father"便可以方便的获得当前被执行function所在类的父类。这是非常重要的,因为它是获得此引用的唯一途径(通过"this"对象获得的function引用总是指向被子类重载的function,而后者并非全是正在被执行的function)。

    这种实现方式类似于C#中base.**的用法调用,而不需要在子类中出现父类名称的硬编码,但也存在缺点,不能够实现多继承。

    五、总结

    附录一、Class基类

    1. //定义最顶级类,用于js继承基类
    2. function Class() { }
    3. Class.prototype.construct = function () { };
    4. Class.extend = function (def) {
    5.     var subClass = function () {
    6.         if (arguments[0] !== Class) { this.construct.apply(this, arguments); }
    7.     };
    8.  
    9.     var proto = new this(Class);
    10.  
    11.     var superClass = this.prototype;
    12.     for (var n in def) {
    13.         var item = def[n];
    14.         if (item instanceof Function) item.father = superClass;
    15.         proto[n] = item;
    16.     }
    17.     subClass.prototype = proto;
    18.  
    19.     //赋给这个新的子类同样的静态extend方法
    20.     subClass.extend = this.extend;
    21.     return subClass;
    22. };

    附录二、寄生组合继承实现多继承,并实现子类调用父类方法案例

    1. // 定义一个房间类
    2. function Room() {
    3.     // 属性
    4.     this.name = name || 'Zoom';
    5. }
    6. // 原型方法
    7. Room.prototype.open = function () {
    8.     console.log("Father." + this.name + '正在开门');
    9. };
    10.  
    11.  
    12. // 定义一个动物类
    13. function Animal(name) {
    14.     // 属性
    15.     this.name = name || 'Animal';
    16.     // 实例方法
    17.     this.sleep = function () {
    18.         console.log(this.name + '正在睡觉!');
    19.     }
    20. }
    21. // 原型方法
    22. Animal.prototype.eat = function (food) {
    23.     console.log("Father." + this.name + '正在吃:' + food);
    24. };
    25. function Cat(name) {
    26.     Animal.call(this);
    27.     Room.call(this);
    28.     this.name = name || 'Tom';
    29. }
    30. // Cat类
    31. (function () {
    32.     Cat.prototype.constructor = Cat;
    33.  
    34.     Cat.prototype.eat = function (food) {
    35.         console.log("SubClass." + this.name + '正在吃:' + food);
    36.         Animal.prototype.eat.apply(this, Array.prototype.slice.apply(arguments));
    37.     };
    38.  
    39.     Cat.prototype.open = function () {
    40.         console.log("SubClass." + this.name + '正在开门');
    41.         Room.prototype.open.apply(this, Array.prototype.slice.apply(arguments));
    42.     };
    43. })();
    44.  
    45. // Test Code
    46. var cat = new Cat();
    47. console.log(cat.name);
    48. cat.open();
    49. cat.eat('fish'); //cat正在吃:fish
    50. console.log(cat instanceof Animal); // false
    51. console.log(cat instanceof Cat); //true

    测试结果:

    附录三、参考文章

        https://blog.csdn.net/rainxie_/article/details/39991173

  • 相关阅读:
    都为你整理好了,5种Java 随机方式对比!你都知道吗?
    你不知道的,Java代码性能优化的 40+ 细节,赶快收藏!
    大厂技术总监,送给刚毕业和快要毕业的程序员——7点建议
    .NET Core 微服务学习与实践系列文章目录索引(2019版)
    ManagementEventWatcher throws ManagementException with call to Stop()
    postman工具的使用
    java实体类读取属性文件,并赋值
    使用idea创建springboot的maven项目
    手写Promise实现过程
    用Javascript制作随机星星效果图
  • 原文地址:https://www.cnblogs.com/xyb0226/p/9127424.html
Copyright © 2020-2023  润新知