• 【JavaScript】手撕前端面试题:寄生组合式继承 | 发布订阅模式 | 观察者模式


    🧑‍💼个人简介:大三学生,一个不甘平庸的平凡人🍬
    🖥️ NodeJS专栏:Node.js从入门到精通
    🖥️ 博主的前端之路(源创征文一等奖作品):前端之行,任重道远(来自大三学长的万字自述)
    🖥️ TypeScript知识总结:TypeScript从入门到精通(十万字超详细知识点总结)
    👉 你的一键三连是我更新的最大动力❤️!


    1、寄生组合式继承

    要求

    补全JavaScript代码,要求通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:

    1. 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
    2. 给"Chinese"构造函数的原型上添加"getAge"函数,该函数返回调用该函数对象的"age"属性

    思路

    寄生组合式继承是引用类型最理想的继承范式,它融合了组合式继承寄生式继承的优点,而组合式继承又是融合了原型链借用构造函数的技术,从而发挥两者之长,所以寄生组合式继承实际是三种技术的融合。

    • 寄生式继承的思路是:创建一个仅用于封装继承过程的函数
    • 组合式继承的思路是:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

    这一题的思路是:

    1. 先创建一个inheritPrototype函数,该函数属于寄生式继承模式,作用是实现实现对原型属性和方法的继承:

      // subType子类构造函数,superType父类构造函数
      function inheritPrototype(subType,superType){
      	 // Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。
           var prototype = Object.create(superType.prototype); // 创建父类型的一个副本对象
           prototype.constructor = subType; // 修复prototype的constructor 
           subType.prototype = prototype; // 将prototype设为subType的原型
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      这里涉及到原型链的知识:一个构造函数的prototype指向它的原型对象,而它的原型对象的constructor属性又指向到这个构造函数。上面的代码中因为要让prototype设置为subType的原型,所以prototype.constructor需要指向到subType

      调用inheritPrototype后,subType就继承了superType的属性和方法,这些属性和方法存在于subType的原型上,这样一来subType的所有实例就能访问到同一个存在的属性或方法(这些属性和方法相当于是公有的)。

    2. 给"Human"构造函数的原型上添加"getName"函数:

      Human.prototype.getName=function (){
         return this.name;
      }
      
      • 1
      • 2
      • 3
    3. 通过借用构造函数来实现ChineseHuman实例属性的继承:

      function Chinese(name,age) {
      	// 继承了Human,还传了参数
          Human.call(this,name); // 借用构造函数模式
          this.age = age;
          this.color = 'yellow';
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      Chinese内部调用Human构造函数,实际上是为Chinese的实例设置了Human上具有的属性和方法(不包含Human原型上的属性和方法),这样一来Chinese的所有实例就能拥有自己的属性和方法(这些属性和方法相当于是私有的)。

    4. 调用inheritPrototype(Chinese,Human); 来实现ChineseHuman原型属性和方法的继承。

    5. 给"Chinese"构造函数的原型上添加"getAge"函数:

      Chinese.prototype.getAge=function(){
          return this.age;
      }
      
      • 1
      • 2
      • 3

    代码

    function inheritPrototype(subType,superType) {
        var prototype = Object.create(superType.prototype);
        prototype.constructor = subType;
        subType.prototype = prototype;
    }
    
    
    function Human(name) {
        this.name = name
        this.kingdom = 'animal'
        this.color = ['yellow', 'white', 'brown', 'black']
    }
    
    Human.prototype.getName = function () {
        return this.name;
    }
    
    function Chinese(name,age) {
        Human.call(this,name);
        this.age = age;
        this.color = 'yellow';
    }
    
    inheritPrototype(Chinese,Human);
    
    
    Chinese.prototype.getAge = function() {
        return this.age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这题寄生组合式继承涉及到了JavaScript面向对象的程序设计,需要理解对象,构造函数,原型,原型链等的知识,博主之后会出文章对JavaScript面向对象的程序设计进行讲解,敬请期待!

    2、发布订阅模式

    要求

    补全JavaScript代码,完成"EventEmitter"类实现发布订阅模式。

    注意:

    1. 同一名称事件可能有多个不同的执行函数
    2. 通过"on"函数添加事件
    3. 通过"emit"函数触发事件

    思路

    • 因为同一名称事件可能有多个不同的执行函数,所以我们需要先定义一个handler对象用来保存订阅事件的列表,对象内的key为订阅事件名称,value是一个包含该订阅事件所有的执行函数的数组
    • on函数接收两个参数,分别代表订阅事件名称和执行函数,在on函数内判断handler对象内是否存在该订阅事件,从而决定是向handler对象内初始化该订阅事件还是向该订阅事件的函数列表中添加新函数。
    • emit函数接收多个参数,第一个参数代表订阅事件名称,后面的参数是需要向订阅事件处理函数传递的参数,handler对象内存在该订阅事件时就遍历执行该订阅事件的函数列表数组中的所有处理函数。

    代码

    class EventEmitter {
        // 补全代码
        constructor() {
            this.handler = {}; // 保存订阅事件的列表
        }
        on(type, fn) {
            const fnArr = this.handler[type];
            if (fnArr) {
                // 如果订阅事件存在,存放订阅事件的回调函数
                fnArr.push(fn);
            } else {
                // 如果订阅事件不存在,则初始化该事件
                // 因为同一名称事件可能有多个不同的执行函数,所以用数组来存放所有的执行函数
                this.handler[type] = [fn];
            }
        }
    
        emit(type, ...args) {
            const fnArr = this.handler[type];
            if (fnArr) {
                // 如果订阅事件存在,遍历并执行订阅事件的处理函数
                fnArr.forEach(cb => cb(...args))
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    测试:

    let sign1 = 0;
    let sign2 = 0;
    const emitter = new EventEmitter();
    emitter.on('add', function () { sign1++ });
    emitter.emit('add');
    emitter.on('add', function () { sign2++ });
    emitter.emit('add');
    const judge = sign1 === 2 && sign2 === 1;
    console.log(judge); // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3、观察者模式

    要求

    补全JavaScript代码,完成"Observer"、"Observerd"类实现观察者模式

    要求如下:

    1. 被观察者构造函数需要包含"name"属性和"state"属性且"state"初始值为"走路"。
    2. 被观察者创建"setObserver"函数用于保存观察者们。
    3. 被观察者创建"setState"函数用于设置该观察者"state"并且通知所有观察者。
    4. 观察者创建"update"函数用于被观察者进行消息通知,该函数需要打印(console.log)数据,数据格式为:小明正在走路。其中"小明"为被观察者的"name"属性,"走路"为被观察者的"state"属性。

    注意:

    1. "Observer"为观察者,"Observerd"为被观察者。

    思路

    • 根据题目的第二个要求:被观察者创建"setObserver"函数用于保存观察者们。可得知setObserver函数应该接受一个observer参数,该参数代表观察者,同时因为要保存这些观察则,所以在Observerd被观察者初始化的时候应该创建一个用来保存观察者的数组observers
    • 根据题目的第三个要求和第四个要求可知setState函数接受一个state参数用来更新Observerd被观察者自身的state,同时setState函数还应该遍历observers数组,并调用数组中的每一项的update方法,以此来通知所有观察者。

    代码

    // 被观察者
    class Observerd {
        constructor(name) {
            this.name = name
            this.state = '走路'
            this.observers = [] // 存放观察者
        }
        setObserver(observer) {
            this.observers.push(observer)
        }
        setState(state) {
            this.state = state
            // 遍历通知每一个观察者
            this.observers.forEach(observer => {
                observer.update(this)
            })
        }
    }
    // 观察者
    class Observer {
    	// update被被观察者(Observerd)调用,用来接收被观察者的数据
        update(observerd) {
            console.log(observerd.name + '正在' + observerd.state);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 相关阅读:
    day23集合02
    cs231n--正则化与优化
    Git 分支管理规范
    港联证券:资金融通构成强支撑 “一带一路”金融合作开新局
    【Linux】从零开始学习Linux基本指令(一)
    CFdiv2-Two Pizzas-(预处理+状态压缩)
    GBase 8c产品简介
    机器学习---决策树的划分依据(熵、信息增益、信息增益率、基尼值和基尼指数)
    Redis与分布式-主从复制
    在Python中实现一个简单的社交媒体应用
  • 原文地址:https://blog.csdn.net/m0_51969330/article/details/127666036