面试官: JavaScript如何实现继承?

简介: 面试官: JavaScript如何实现继承?

一、继承是什么?

继承(inheritance)是面向对象软件技术当中的一个概念。


如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”


继承的优点:


1.继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码


2.在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能


关于继承,可以先举一个形象的例子


汽车类作为父类,货车和轿车作为子类继承了汽车类的 一些属性和方法也可以对父类的属性和方法进行重写,也可以增加了自己的一些特有的属性和方法。

            // 汽车父类
            class Car {
                constructor(color, type) {
                    this.color = color;
                    this.type = type;
                }
                getColor() {
                    return this.color;
                }
            }
            // 货车子类
            class Truck extends Car {
                constructor(color, speed, container) {
                    super(color, name);
                    this.container = container;
                }
                getColor() {
                    return `货车的颜色为` + super.getColor();
                }
            }
            // Suv子类
            class Suv extends Car {
                constructor(color, speed, quick) {
                    super(color, name);
                    this.quick = quick;
                }
                getColor() {
                    return `Suv的颜色为` + super.getColor();
                }
            }
            let truck1 = new Truck('red', 200, 300);
            console.log(truck1.getColor()); // 货车的颜色为red
            let suv1 = new Suv('green', 200, 300);
            console.log(suv1.getColor()); // Suv的颜色为red

从这个例子中就能详细说明汽车、卡车以及suv之间的继承关系


二、继承的几种方式

1.原型链继承

    原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针

            function Parent() {
                this.name = 'parent';
                this.friends = ['zs', 'lisi', 'wangwu '];
            }
            function Child() {
                this.type = 'child';
            }
            Child.prototype = new Parent();
            let child1 = new Child();
            let child2 = new Child();
            child1.friends.push('sunqi');
            console.log(child1.friends, child2.friends);
            console.log(child1.__proto__);

下图是上面例子的原型关系图,可以知道 Child.prototype 执行了Parent的实例对象parent1,当改变child1对象的 friends的值时,child1实例对象本身没有这个属性,就会向上查找,找到了parent1实例对象身上,而child1 和 child2 指向了同一个 parent1实例对象,内存空间是共享的


当为 parent1.friends 增加push值时,直接修改了公共的原型对象 parent1上的值。


07f38320eb064a2c8503a7003676c0e1.png

打印输出如下

2.构造函数继承

借用call调用Parent函数, 只是调用了Parent的构造函数构造了对象,没有实现真正的原型继承,只能构造出 Parent实例的属性和方法,访问不到原型上的属性和方法

父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法

        <script>
            function Parent() {
                this.name = 'parent';
            }
            Parent.prototype.getName = function () {
                return this.name;
            };
            function Child() {
                Parent.call(this);
                this.type = 'child';
            }
            let child = new Child();
            console.log(child);
            console.log(child.name);
            console.log(child.getName());
        </script>

3.组合继承

组合继承是将前面的两种方法结合起来。

代码如下:

        <script>
            function Parent() {
                this.name = 'parent';
                this.friends = ['zs', 'lisi', 'wangwu '];
            }
            // 为Parent原型对象上添加方法
            Parent.prototype.getName = function () {
                return this.name;
            };
            function Child() {
                Parent.call(this);
                this.type = 'child';
            }
            Child.prototype = new Parent();
            // 手动挂载构造器
            Child.prototype.constructor = Child;
            let child1 = new Child();
            let child2 = new Child();
            child1.friends.push('sunqi');
            console.log(child1.friends, child2.friends); // 不互相影响
            console.log(child1.getName());
            console.log(child2.getName());
        </script>

组合继承 将前面两种继承方法的优点结合在一起,实例化Child时,调用了父类的构造方法,child1和child2是相互独立的,都有自己的值,所以互不影响,当调用getName方法时,child1和child2方法上并没有这个方法,向上查找,找到了Parent的原型对象上的getName方法。

340c3bcd3859427b919af72e108b5798.png

打印输出结果如下:

4.原型式继承

利用了 Object.create()方法实现普通对象的继承

Object.create(proto, [propertiesObject])

该方法创建一个新对象,并指定该对象的原型对象 ------- proto

        <script>
            let parent = {
                name: 'parent',
                friends: ['zs', 'lisi', 'wangwu'],
                getName() {
                    return this.name;
                },
            };
            // 相当于 child.__proto__ == parent
            let child1 = Object.create(parent);
            console.log(child1.__proto__ == parent); // true
            // 为 child1 添加属性name
            child1.name = 'child1';
            child1.friends.push('sunqi');
            let child2 = Object.create(parent);
            child2.friends.push('child2');
            console.log(child1);
            console.log(child2);
            console.log(child1.name == child1.getName()); //true
            console.log(child1.friends); // ['zs', 'lisi', 'wangwu', 'sunqi', 'child2'] 
            console.log(child2.friends); // ['zs', 'lisi', 'wangwu', 'sunqi', 'child2']
        </script>

输出打印结果如下:

child1和child2都没有自己的friend属性,都要向上查找,找到了parent对象上的friend方法,指向的同一内存,因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能

5.寄生式继承

寄生式继承在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法

        <script>
            let parent = {
                name: 'parent',
                friends: ['zs', 'lisi', 'wangwu'],
                getName() {
                    return this.name;
                },
            };
            // 定义继承的方法,
            function clone(proto) {
                let clone = Object.create(proto);
                // 添加自己的方法
                clone.getFriends = function () {
                    return this.friends;
                };
                return clone;
            }
            let child = clone(parent);
            console.log(child.getName());  // parent
            console.log(child.getFriends()); // ['zs', 'lisi', 'wangwu']
        </script>

6.寄生组合式继承

结合和第五种寄生式方法和第三种组合式方法

 <script>
            function clone(parent, child) {
                // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
                child.prototype = Object.create(parent.prototype);
                child.prototype.constructor = child;
            }
            function Parent() {
                this.name = 'parent';
                this.play = [1, 2, 3];
            }
            Parent.prototype.getName = function () {
                return this.name;
            };
            function Child() {
                Parent.call(this);
                this.friends = 'child';
            }
            clone(Parent, Child);
            Child.prototype.getFriends = function () {
                return this.friends;
            };
            let child = new Child();
            console.log(child); //{friends:"child",name:"parent",play:[1,2,3],__proto__:Parent}
            console.log(child.getName()); // parent
            console.log(child.getFriends()); // child
        </script>

原型链继承示意图

通过这种方法,我们发现,Child实例化出的实例化对象child 即能实例化出自己的属性和方法,也能继承到原型对象上的属性和方法。

三、总结

可以用一张图来总结

开发人员认为寄生组合式继承是最理想的继承方式

欢迎大家评论区讨论,一起学习

目录
相关文章
|
28天前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
1月前
|
JavaScript 前端开发
Javascript如何实现继承?
【10月更文挑战第24天】JavaScript 中实现继承的方式有很多种,每种方式都有其优缺点和适用场景。在实际开发中,我们需要根据具体的需求和情况选择合适的继承方式,以实现代码的复用和扩展。
|
1月前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。
|
1月前
|
JavaScript 前端开发 开发者
js实现继承怎么实现
【10月更文挑战第26天】每种方式都有其优缺点和适用场景,开发者可以根据具体的需求和项目情况选择合适的继承方式来实现代码的复用和扩展。
31 1
|
3月前
|
自然语言处理 JavaScript 前端开发
一文梳理JavaScript中常见的七大继承方案
该文章系统地概述了JavaScript中七种常见的继承模式,包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承等,并探讨了每种模式的实现方式及其优缺点。
一文梳理JavaScript中常见的七大继承方案
|
3月前
|
JavaScript 前端开发
js之class继承|27
js之class继承|27
|
3月前
|
JSON JavaScript 前端开发
js原型继承|26
js原型继承|26
|
3月前
|
JavaScript 前端开发 开发者
JavaScript 类继承
JavaScript 类继承
22 1
|
3月前
|
JavaScript 前端开发
JS的几种继承方式
JavaScript中的几种继承方式视频。
17 0
|
4月前
|
开发者 图形学 iOS开发
掌握Unity的跨平台部署与发布秘籍,让你的游戏作品在多个平台上大放异彩——从基础设置到高级优化,深入解析一站式游戏开发解决方案的每一个细节,带你领略高效发布流程的魅力所在
【8月更文挑战第31天】跨平台游戏开发是当今游戏产业的热点,尤其在移动设备普及的背景下更为重要。作为领先的游戏开发引擎,Unity以其卓越的跨平台支持能力脱颖而出,能够将游戏轻松部署至iOS、Android、PC、Mac、Web及游戏主机等多个平台。本文通过杂文形式探讨Unity在各平台的部署与发布策略,并提供具体实例,涵盖项目设置、性能优化、打包流程及发布前准备等关键环节,助力开发者充分利用Unity的强大功能,实现多平台游戏开发。
113 0