jjzjj

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

海底烧烤店ai 2023-07-17 原文

🧑‍💼个人简介:大三学生,一个不甘平庸的平凡人🍬
🖥️ 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的原型
     }
    

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

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

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

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

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

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

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

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

    Chinese.prototype.getAge=function(){
        return this.age;
    }
    

代码

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;
}

这题寄生组合式继承涉及到了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))
        }

    }
}

测试:

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

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);
    }
}

有关【JavaScript】手撕前端面试题:寄生组合式继承 | 发布订阅模式 | 观察者模式的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  5. Hive SQL 五大经典面试题 - 2

    目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类

  6. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  7. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  8. Ruby:标准递归模式 - 2

    我经常迷上ruby​​的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情

  9. ruby - 在 Ruby 中查找多个正则表达式匹配的模式和位置 - 2

    这应该是一个简单的问题,但我找不到任何相关信息。给定一个Ruby中的正则表达式,对于每个匹配项,我需要检索匹配的模式$1、$2,但我还需要匹配位置。我知道=~运算符为我提供了第一个匹配项的位置,而string.scan(/regex/)为我提供了所有匹配模式。如果可能,我需要在同一步骤中获得两个结果。 最佳答案 MatchDatastring.scan(regex)do$1#Patternatfirstposition$2#Patternatsecondposition$~.offset(1)#Startingandendingpo

  10. ruby - sinatra 框架的 MVC 模式 - 2

    我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho

随机推荐