树酱的前端知识体系构建(下)

6,680 阅读16分钟

前沿:这周慢更了,但树酱还是来了,上周分享了他关于前端的知识体系构建上篇传送门,主要包括Vue、Node、前端工程化模块、性能优化等四大模块,这篇主要跟你聊聊关于安全、设计模式、微前端等方面的知识体系构建

1 安全

前端安全问题有哪些呢?比如发生在生浏览器、单页面应用中的,常见的前端安全攻击有:XSS(跨站脚本攻击)、CSRF(跨站请求伪造)、站点劫持等。正因为这些漏洞的存在,我们才需要根据不同的安全问题制定安全策略应对措施

1.1 XSS

1.1.1 关于XSS

XSS 全称叫 Cross Site Script,顾名思义就是跨站脚本攻击,XSS攻击是通过在网站注入恶意的脚本,然后借助脚本改变原有的网页,当用户访问网页时,对浏览器一种控制和劫持,XSS攻击主要分为以下几种类型

  • 反射型XSS:攻击者通过特定的方式来诱惑受害者去访问一个包含恶意代码的URL
  • 存储型XSS:将恶意脚本放到服务器中,下次只要受害者浏览包含此恶意脚本的页面就会自动执行恶意代码
  • 基于DOM型XSS:客户端的js对页面dom节点进行动态的操作,比如插入、修改页面的内容
  • SQL注入:通过客户端的输入把SQL命令注入到一个应用的数据库中,从而执行恶意的SQL语句

1.1.2 防范措施

针对XSS安全问题,我们主要有以下几点防范措施

  • Cookie设置HttpOnly

因为浏览器禁止页面javascript访问带HttpOnly属性的Cookie,因为XSS攻击本质上可以通过恶意脚本获取cookie的信息,然后发起cookie劫持攻击,模拟用户的登录,导致用户权限和信息暴露

除了httpOnly,还有SecureOnly(只允许https请求读取)和 HostOnly (只允许主机域名与domain一致)

  • 检查输入内容

对用户输入的所有内容都进行过滤和检查

  • 比如是否包含一些特殊字符、<,>等,将它们转义为实体字符
  • 将不可信数据作为 URL 参数值时需要对参数进行 URL 编码(encodeURIComponent)

🌲 拓展阅读:

1.2 CSRF

1.2.1 关于CSRF

CSRF全称叫 Cross Site Request Forgery,顾名思义叫跨站请求伪造,通过劫持受信的用户(已登陆)向服务器发送非用户本身意愿的请求,以此来完成攻击,生活中用户很多验证信息都是存在于cookie中,不法分子(我)可以利用用户自己的 cookie 来通过安全验证。简单说CSRF主要具备以下两个特点:

  • 一般发生在第三方域名
  • CSRF攻击者不能获取到Cookie等信息,只能使用来通过安全验证

1.2.2 防范措施

  • 验证码

通过在用户提交操作前,添加验证码验证环境,验证码可以强制约束用户和应用进行交互,可以杜绝CSRF在用户不知情的情况下发起网络请求,不过过多添加验证码会让用户“繁琐”,得不偿失,这种方式需要看相应的场景

  • Referer 源的检测(同源检测)

通过设置Referer 源来拒绝返回信息,如若referer为空,或referer不属于自身域名下或着子域等,则拒绝返回信息。举个场景:有一天我想要爬某个网站的接口数据,可是这些接口使用了Referer源检测来对我发起请求的请求头中的Host和referer进行检查,如果不是指定的源就不予返回数据,Referer就相当于一个拦截器,以此达到防范的效果,客户端就无奈了,无法篡改 Referer

但也是有方式也有弊端,可以绕开的呢。为啥? 可以通过前端向自己定义后端接口发起请求,在后端接口函数中用axios添加自定义Referer并发起代请求~以此绕开

  • 请求地址添加token验证(CSRF Token)

这种方式就是经典中的经典,我们经常看到在form表单最后添加隐藏的唯一的token <input type=”hidden” name=”csrftoken” value=”token”/> 通过每次提交表单的时候,把token以参数的方式加入请求,然后服务端通过校验该token(和登陆时写入session的token对比)是否一致,来判断是否为CSRF攻击。这种方式有点古老~

token为什么不放在cookie?你忘了啊,CSRF劫持的就是Cookie,放在Cookie又会被冒用,就没有意义了

  • Samesite Cookie

通过设置SamesiteCookie为Strict,浏览器在任何跨域都不会带上cookie,以此来防止CSRF攻击

这种方式的弊端在于,一旦设置了SamesiteCookie,也就意味着你的子域名也将无法共享你的cookie

🌲 拓展阅读:

2.设计模式

前端主要常见的设计模式主要有以下几种:单例模式、策略模式、发布-订阅者模式、适配器模式、装饰器模式、代理模式等等

2.1 单例模式

单例模式是最简单的设计模式之一,一个类只返回一个实例,一旦创建再次调用就直接返回,目的在于解决一个全局使用的类频繁地创建与销毁

2.1.1 如何实现

该模式无非就是通过一一个变量来标志当前是否已经为某个类创建过对象,

2.1.2 实际应用

常见的应用工具有:如第三方库jQuery,lodash,moment等,树酱在之前的工具库中封装监控工具sentry也用到了单例模式,可以看链接 github

2.2 策略模式

策略模式顾名思义就是通过不同策略来区分,简单来说就是预先把需要用到策略定义好,涉及的算法集合起来,以备不同的业务场景使用。减少if-else的嵌套逻辑,提升代码的健壮性

2.2.1 关于策略模式

本质上是将算法的使用和算法的实现分离开,也就是将不变的部分与变化的部分完全隔开

一个基于策略模式下的程序至少包含以下两部分

  • 策略类:也就是前沿介绍中提到的,通过策略类预先定好具体的算法
  • 环境类(Context):用于接受客户的请求,然后根据该请求去策略类中找到合适的进行“委托”,有点选秀的意思

下面通过一个经典的例子(策略模式计算奖金)来介绍👇

2.2.2 经典例子:薪资计算

假设公司目前年终要对不同绩效(A、B、C)的员工进行奖金划分,这时就需要对不同绩效编写不同的算法来计算,这个时候你觉得不就是几行if-else就搞定了吗?so easy~

但是当你冷静下来🤔,你会发现这种方式存在缺陷,比如随着绩效定义的不断增加,你需要维护大量嵌套if-else逻辑,很容易把你带进死胡同,同时算法的复用性差,根本无法重复调用,因为你将策略和环境(context上下文)混为一起

基于上面例子,做个改造,通过组合函数来重构,简单说就是将不同算法封装起来,可以解决上一节那个例子的复用性问题,但是植根不治本,calculateBonus函数会随着算法的增多而变大,还是存在大量if-else语句

那么有没有更好的方式可以去解决以上所说的问题呢?答案是有的,就是我们今天的主角“策略模式”,通过策略模式进行重构改造,也就是完全将算法使用和算法实现分离开,各管各的

2.2.3 经典例子重构:薪资计算(面向对象思想的策略模式重构)

面向对象思想的策略模式:通过把一系列的算法封装起来,“挂在”原型链上,然后当需要计算时,将这些绩效算法策略传入到初始化好的Bonus对象中,当调用getBonus()时,Bonus对象本身不具备计算方法,是通过将计算的步骤“委托”给传入的策略去进行处理

  • 步骤1: 通过将不同绩效规则来封装到策略类中去,通过原型继承 (策略类)

  • 步骤2: 完成规则封装后,接下来是如何定义奖金类(Context)

2.2.4 经典例子重构:薪资计算(函数思想的策略模式重构)

上一节是通过传统的面向对象的思维去实现策略模式,但是这种模式稍微显得复杂和冗余,更简单的方式则是直接把策略类定义为函数,无论绩效指标增加了什么,calculateBonus函数不需要变更,只要增加新策略的函数即可,如下所示👇

策略模式的使用,消除了源码中大量的if-else 语句,我们不再把算法计算逻辑放在Context(上下文),而是分开成若干个策略单独维护,需要用到算法是时,才将计算委托给某个策略来完成,以此来完成相应的计算并返回计算结果,而且“策略是可以相互替换的”,你需要时,我就给你发一张“委任状”,然后你就可以去办事了

最后总结策略模式两个特性,分别是

  • 封装变化:算法封装,方便复用
  • 委托:需要时将计算委托给某个策略

2.3 发布-订阅模式

发布-订阅模式是通过定义了对象一对多的依赖关系来实现的。当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。简单说:树酱订阅了纽约时报周刊,周刊每周五都会固定向每个会员发起最新订阅信息,因为订阅了它,所以我在它每次更新的实话都能收到最新信息的通知

2.3.1 关于发布-订阅模式

上面举的例子中,其实还缺少一个角色:EventHub,这个鬼玩意是干嘛的呢?其实就是一个消息通道channerl,也有点像中间商也就是我们生活中的“中介”,这时候我想起最近要准备上市的找房大平台贝壳,平台左边是房东发布的“真实”房源,右手是刚需买房的金主,房东有房源或房源信息更新,均会通知我们(发布),金主则会向我们反馈需求,如意向的房屋信息,他们将会被通知(订阅)。这就是一个发布-订阅模式,可以看下面这个示意图

生活中我们常用的公众号也是这种逻辑,当你订阅了一个公众号,公众号有最新信息要推送,那么它只负责推送信息,并不用关心是谁订阅了我,只要有信息推送,那么就推送给所有的订阅者,这也是发布-订阅模式的应用场景

2.3.2 如何实现

扯了一大堆概念的介绍,下面我们看看开发中是如何实现的,主要包括以下几个步骤

  • 1.创建一个 EventHub 并在 EventHub 添加一个缓存列表,用于存放回调函数以便通知订阅者
  • 2.发布消息时,EventHub 遍历缓存列表,依次触发回调
  • 3.在EventHub中定义 trigger (发布) 、addlisten (订阅) 、off (取消订阅) 等方法

下面是一个简单版的发布-订阅模式EventHub的实现

使用如下

2.3.3 应用场景

该模式优点在于实现了松耦合,即可以让发布者发布消息、又可以让订阅者接受消息,而不是寻找一种方式把两个分离的系统强连接起来。在不清楚对方细节的情况下仍然可以互相通信,适合不同的人员、团队开发不同模块的场景。

思考🤔:发布-订阅模式跟观察者模式有点像,这两者什么区别?

🌲 拓展阅读

2.4 适配器模式

举个生活中的例子介绍:我带了我的macbookpro去酒店办公,发现酒店的插头都不匹配,找不到合适的插口能够插入这个插头,为了办公这个时候我只能拿起了我的公牛转换插头才能让macbookpro正常使用。适配器就是相当于这个“公牛转换插头”,将原本由于插头不兼容而不能让我正常办工的电脑充电,coding中因为接口不兼容而不能一起工作的一些类也是一样逻辑,这时候就需要适配器模式

🌲 拓展阅读

2.5 装饰器模式

装饰者模式就是在原有不改变对象的基础上,给对象添加额外的职责,也就是扩展功能,且不会影响原有接口的功能。主要包含以下几个优点

  • 可以在原有的类扩展设计出多个不同的具体装饰类,创造出多个不同行为的组合
  • 一般扩展类的功能,我们可以使用继承和装饰来达到我们的目的,但是通过装饰模式扩展对象比直接采用继承方式更加灵活

🌲 拓展阅读

2.6 代理模式

代理模式顾名思义就是找个代理替代,也就是为一个对象提供一个代用品,以便控制对它的访问,举个例子:树酱在前端娱乐圈混,找了个经纪人为我处理日常一些事情,这个经纪人就是它的代理proxy

代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,

2.6.1 经典例子(图片的预加载处理)

我们在看看一个实际应用到开发的例子,经典的图片的预加载处理使用代理模式设计

👨‍🎓 啊乐同学:那为啥需要用代理模式来设计图片的预加载处理?

初衷是因为上文的myImage对象除了负责给img节点设置src,还要负责预加载图片。但是这样的话,在面向对象设计中,如果一个对象承担了多项职责,就意味着这个对象可能会变的巨大,而且后期如果想把这个功能(需求不大)删掉,还需要改原有的对象,如果把把预加载图片这个行为分开岂不是更好?

通过proxyImage来间接的访问myImage,而且还可以proxyImage这个代理对象加入一些其他的操作,比如图片加载所需的loading等等

2.6.2 ES6 Proxy代理

ES6 Proxy是通过调用 new Proxy() ,创建一个代理对象用来替代原有的对象,这个代理对象会对原有对象进行虚,更厉害的是,ES6的Proxy可以把本体没法改变的内部属性也改了,看看下面这个简单的例子:

const p = new Proxy(target, handler);

  • target : 你要代理的对象,可以是数组, 对象, 函数等等

  • handler: 你要对代理对象自定义操作方法的一个集合

🌲 拓展阅读

3 微前端

微前端本质是是一种项目架构方案,是为了解决前端项目太过庞大,导致项目管理维护难、团队协作乱、升级迭代困难、技术栈不统一等等问题,有点类似微服务的概念,是将微服务理念扩展到前端开发的一种应用

3.1 Iframe

iFrame 是微前端集成的最简单方式之一。可以说iFrame 里的页面是完全独立的,而且iFrame 页面中的静态资源(js、css)都是相互隔离的,互相不干扰,相当于一个独立的环境,具备沙箱隔离,可以让前端应用之间可以相互独立运行,但是IFrame局限性也很大,主要包括以下几点👇:

  • 子项目需调整,需要隐藏自身页面中的导航(公共区域)
  • iFrame嵌入的视图控制难,有局限性
  • 刷新无法保存记录,也就意味着当浏览器刷新状态将消失,后退返回无效
  • iframe 阻塞主页面加载

3.2 Ngxin 路由分发

通过配置ngix的location来制定不同路由映射的静态资源访问路径,将多个子项目聚合成一体,来配置不同路由的转发代理

这种通过nginx路由分发也有局限性:

  • web应用之间的复用性差,每个应用都是独立的,无法共享数据和资源
  • 每个独立的项目之间切换,需要重新加载,容易出现白屏影响用户体验

3.3 Single-spa

官方号称“一个用于前端微服务化的JavaScript前端解决方案”,single-spa 听起来很高大上,它能兼容各种技术栈,并且在同一个页面中可以使用多种技术框架(React, Vue, Angular等任意技术框架),不用考虑因新的技术框架而去重构旧项目的代码

大概的原理是,首先需要一个主应用(容器应用),需要先注册子应用,然后当url匹配到相应的子应用路由后,将会先请求子应用的资源,然后挂载子应用,同理,当url切换出该子应用路由时,将卸载该应用,以此达到切换子应用的效果,通过子应用生命周期boostrap(获取输出的资源文件) 、 mount、unmount的交替 聊聊Single-SPA 的优点:

  • 各项目独立开发、部署、迭代,互不影响效率高
  • 开发团队可以选择自己的技术并及时更新技术栈。
  • 相互之间的依赖性大大降低
  • 有利于CI/CD,更快的交付产品

🌲推荐阅读:

3.4 Qiankun(蚂蚁金服微前端框架)

qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统,关于实际架构落地看这篇基于qiankun落地部署微前端爬”坑“记

未完待续...

请你喝杯🍵 记得三连哦~

1.阅读完记得给🌲 酱点个赞哦,有👍 有动力

2.关注公众号前端那些趣事,陪你聊聊前端的趣事

3.文章收录在Github frontendThings 感谢Star✨