1.前端常见的设计模式用法,及使用场景
1.单例模式 单例模式 (Singleton Pattern)又称为单体模式,保证一个类只有一个实例,并提供一个访问它的全局访问点 。 也就是说,当再次创建时,应该得到与第一次创建完全相同的对象
class ManageGame {
static _schedule = null;
static getInstance = function () {
ManageGame._schedule = ManageGame._schedule || new ManageGame();
return ManageGame._schedule;
};
constructor() {
ManageGame._schedule = ManageGame._schedule || this;
return ManageGame._schedule;
}
}
const schedule1 = new ManageGame();
其他的应用场景例如,vuex,redux中的store都是单例模式实现的
1.工厂模式
根据不同的输入返回不同类的实例,一般用来创建同一类对象。比如可以获取不同的水果,西瓜,葡萄,哈密瓜等,但是它们都属于水果的。
它的主要思想是将对象的创建与对象的实现分离
// src/index.js
export default class VueRouter {
constructor(options) {
this.mode = mode // 路由模式
switch (mode) { // 简单工厂
case 'history': // history 方式
this.history = new HTML5History(this, options.base)
break
case 'hash': // hash 方式
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract': // abstract 方式
this.history = new AbstractHistory(this, options.base)
break
default:
// ... 初始化失败报错
}
}
}
vue-router源码基于工厂模式
那么什么时候使用工厂模式呢:
- 对象的创建比较复杂,而访问者无需知道创建的具体流程;
- 处理大量具有相同属性的小对象;
发布-订阅模式
在众多设计模式中,可能最常见、最有名的就是发布-订阅模式了。
发布-订阅模式又叫观察者模式,它定义了一种一对多的关系,让多个订阅者对象同时监听某一个发布者,或者叫主题对象,这个主题对象的状态发生变化时就会通知所有订阅自己的订阅者对象,使得它们能够自动更新自己。
const shoesPub = {
shoeBook: [], // 售货员的小本本
subShoe(phoneNumber) {
// 买家在小本本是登记号码
this.shoeBook.push(phoneNumber);
},
notify() {
// 售货员打电话通知小本本上的买家
for (const customer of this.shoeBook) {
customer.update();
}
},
};
const customer1 = {
phoneNumber: "152xxx",
update() {
console.log(this.phoneNumber + ": 去商场看看");
},
};
const customer2 = {
phoneNumber: "138yyy",
update() {
console.log(this.phoneNumber + ": 给表弟买双");
},
};
shoesPub.subShoe(customer1); // 顾客登记
shoesPub.subShoe(customer2); // 顾客登记
// 鞋子来了
shoesPub.notify(); // 打电话通知买家到货了
Vue 就是利用发布-订阅模式来实现视图层和数据层的双向绑定
性能检测工具
浏览器自带 Lighthouse工具 可分析:
Performance:性能Accessibility:可访问性Best Practices:最佳实践SEO:对搜索引擎有没有做优化Progressive Web App(PWA):渐进式应用价值,包括离线也能给客户进行访问
https握手过程
http握手过程
https握手过程
webpack做过哪些优化
webpack优化分为两个方向:
1.构建速度层面优化
2.代码输出质量优化
构建速度层面优化:
1.缩小打包作用域
1.1 test/exclude/include 确定loader规则范围
1.2 resolve.modules 默认值为['node_modules'],含义是先去当前目录的node_modules下去找模块,没有找到就去上一级../node_modules中找,当第三方模块的路径确定时,可以指定其绝对路径,以减少寻找。
1.3 noParse 对完全不需要解析的库进行忽略
防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能
1.4 预编译资源模块
DllPlugin结合DllReferencePlugin
等等
2.多进程构建
2.1 thread-loader插件
webpack 是单线程模型的,所以webpack 需要一个一个地处理任务,不能同时处理多个任务。
thread-loader会将你的 loader 放置在一个 worker 池里面运行,以达到多线程构建。
2.2 多进程并行压缩代码
webpack默认提供了UglifyJS插件来压缩JS代码,但是它使用的是单线程压缩代码,也就是说多个js文件需要被压缩,它需要一个个文件进行压缩
目前有三种主流的多进程压缩方案:
parallel-uglify-plugin
uglifyjs-webpack-plugin
terser-webpack-plugin
这些插件能开启多个子进程,把对多个文件压缩的工作分别给多个子进程去完成,但是每个子进程还是通过UglifyJS去压缩代码。无非就是变成了并行处理该压缩了,并行处理多个子任务,效率会更加的提高
代码输出质量优化
1. 用 webpack 实现 CDN 的接入
静态资源的导入 URL 需要变成指向 CDN 服务的绝对路径的 URL 而不是相对于 HTML 文件的 URL。
静态资源的文件名称需要带上有文件内容算出来的 Hash 值,以防止被缓存。
不同类型的资源放到不同域名的 CDN 服务上去,以防止资源的并行加载被阻塞。
web-webpack-plugin 单页面应用生成 HTML 文件
extract-text-webpack-plugin
2. 多入口项目提取公共代码
SplitChunksPlugin插件来进⾏公共模块抽取
SplitChunksPlugin 进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置)
3. 按需加载
webpack支持两种动态代码拆分技术:
- import()语法,用import引入的模块以及其子模块会被分割打包成一个独立的chunk
- 传统的require.ensure
4. 使用tree shaking
tree shaking是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import和 export
使用:
- 使用 ES2015 模块语法(即 import 和 export)。
- 导入模块副作用的设置,在项目 package.json 文件中,添加一个 sideEffects入口,或者在module.rules配置选项中设置 sideEffects。
- 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如 UglifyJSPlugin)。
跨页面通讯方案
现有解决方案
一、localStorage
场景:
-
页面AB均已存在,可以通过onstorage监听共享域获取值。
-
页面A存在,点击打开新页面B后进行通信。可以在B页面内通过localStorage.getItem(key)获取值。
注意点:
- 页面A修改localStorage,不会触发onstorage事件
- 如果localStorage值未发生变化,不会触发onstorage事件
- 部分浏览器隐身模式下,无法设置localStorage。如safari
优势:浏览器支持效果好、API直观。onstorage的设定,就像是为了页面间通信做的准备。
二、url
通过url中携带query参数,在B页面初始化时获取参数。
场景:仅可用于A页面下打开新页面B的场景
优势:浏览器支持效果好、持久化、无需配置。
缺陷:语义不明的内部状态暴露在url中不合理 ,同时数据较多时导致url臃肿。
三、postMessage
A页面通过window.open获取B页面的句柄。通过otherWindow.postMessage传递数据,在B页面通过onmessage获取A页面传递的参数,也可以通过e.source.postMessage传递信息给A页面。
场景:
- AB页面均存在时,通过postMessage与onmessage组合即可
- A存在,打开新页面B进行传递时,A页面需要监听B页面完成message注册后才能传递。
注意点
- Window.open方法的第二个参数为设置子页面的name,再次调用open方法会触发同name属性子页面刷新。
- 该方案允许跨源传递数据,注意数据的安全性。如果知道通信窗口的origin属性,建议配置第二个参数进行限制接收方(推荐配置)
- 异步请求后window.open会被浏览器拦截,当然也可以通过改变otherWindow.location属性来避开
优点:点对点通信,跨域的场景下可以使用
劣势:使用面小,高度依赖window.open。比如:通过a标签打开B页面,无法使用。
四、 BroadCast channel
它的出现,是为了解决postMessage只能点对点通信的问题,广播形式能够在同域的页面下进行一对多的通信。通过相同的口令连接到同一个频道(像是面对面建群?)
场景:
- AB页面均存在,可通过onmessage与postMessage随意通信,处理
- 打开新页面B,需要监听B页面完成message注册后才能传递。
优点:实现一对多的数据传递(同名频道内),API直观。功能相比下更为全面
缺点:浏览器支持效果如下图,不乐观。
五、cookie
类似于localStorage的低配版,使用document.cookies保存。(仅为可能性讨论,不推荐)
场景:
- AB页面都存在,只能通过轮询检查cookie是否变化。。。emmm
- 打开新页面B进行通信,问题不大,同样在初始化后,获取cookie值进行处理
优点:。。。
缺点:cookie自身的特殊性,谁也不希望每次请求都带上一堆不相干的参数。。
webpack总结
打包原理
webpack打包原理是根据文件间的[依赖关系]对其进行静态分析,将这些模块按指定规则生成静态资源,当 webpack处理程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,将所有这些模块打包成一个或多个bundle。
webpack构建流程:
初始化参数—>开始编译---->确定入口---->编译模块—>完成编译---->输出数据---->输出完成
优势:
代码层面:
-
- 体积更小,加载更快
-
- 编译[高级语言]和语法
-
- 兼容性和错误检查
研发流程层面:
- 1.统一高效的[开发环境]
- 2.统一的构建流程和产出标准
- 3.集成公司构建规范(提测、上线)
核心概念:
entry:入口,webpack的执行从entry开始,
output:出口,输出结果,webpack的输出位置,
loader:模块转换器,用于把webpack不能直接打包的文件[类型转换]
plugins:插件,用于把模块原内容按需求转换成新内容
mode:通过选择development或者production来设置mode参数
chunk:代码块,即打包后输出的文件
基本功能和工作原理
当[源代码]没办法直接运行的时候,通过转化将源代码换成可执行的代码,一般包括
- 1.代码转换:将无法直接运行的文件代码编译成可以执行的代码
- 2.文件优化:[压缩文件]代码、压缩合并图片等
- 3.代码分割:提取多个页面的公共代码、提取首屏不需要执行的部分的代码让其异步加载
- 4.模块合并:在有很多模块文件的环境中,需要将模块分类合并成一个文件
- 5.自动刷新:监听本地源代码变化,自动重新构建、刷新浏览器
- 6.代码校验:在代码被提交前需要校验代码格式等是否符合规范
- 7.自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统
常见问题
1、loader和plugin的区别:
&、不同的作用:
loader:webpack将一切文件视为模块,但是webpack原生只能解析JS文件,如果想打包其他文件的话,就会用到相应的loader,它是用来让webpack拥有加载解析非JS文件的工具
plugins:插件,可以扩展fwebpack的功能,让其更具有灵活性
&、不同的用法:
loader:在module.rules中配置,也就是说它作为模块的规则存在,类型是数组,数组中的每一项是对象,plugins是在插件中单独配置,类型是数组,每一项是插件实例,参数都通过[构造函数]传入
2、什么事模块化,为什么要用模块化
模块化是指把一个复杂的系统分解到多个模块以方便编码
不用模块化的话,会出现喝多问题,比如无法合理地管理项目依赖跟版本,也无法方便的控制依赖的加载顺序,当项目变大时难以维护,这时候需要模块化来[组织代码]
3、DevServer[开发工具]
用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
Webpack 在启动时可以开启监听模式,开启监听模式后 Webpack 会监听本地[文件系统]的变化,发生变化时重新构建出新的结果。
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 开启HMR功能
// 当修改了webpack配置,新配置要想生效,必须重新webpack服务
hot: true
}
4、什么是HMR功能
HMR又叫热替换,它能在不重新加载整个页面的前提下,通过将更改的模块替换掉被更改的模块,再重新执行实现实时预览
优点:只更新变更内容,以节省宝贵的开发时间。调整样式更加快速,几乎相当于在浏览器中更改样式
5、什么是Tree-sharking?
指打包中去除那些引入了但在代码中没用到的[死代码](传统的DCE方法是除去不可能执行的代码)包括空格、注释等
6、babel和webpack的区别
babel JS新语法编译工具,只关心语法,不关心模块化
webpack -打包构建工具,是多个Loader plugin的集合
7、类似webpack的工具还有哪些
(1)、webpack适用于大型复杂的前端站点构建/
(2)、rollup适用于基础库的打包,如vue、react
(3)、parcel适用于简单的实验性项目,他可以满足低门槛的快速看到效果
由于parcel在打包过程中给出的调试信息十分有限,所以一旦打包出错难以调试,所以不建议复杂的项目使用parcel
8、常见loader
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
babel-loader:把 ES6 转换成 ES5
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
9、常见的plugins
define-plugin:定义[环境变量]
commons-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
一下更新
浏览器运行机制
- 输入url,浏览器根据 DNS 服务器得到域名的 IP 地址
- 向服务器请求,服务器返回请求,客户端得到返回的内容
- 自上而下生成dom树
- 渲染css树,生成cssom
- 渲染layout,计算节点大小,位置
- 根据以上进行页面渲染
浏览器eventLoop和node的eventLoop区别
事件循环在 浏览器 和 Node 中的区别很容易被人忽视,执行顺序整理如下:
浏览器环境下:
while (true) {
宏任务队列.shift();
微任务队列全部任务();
}
Node 环境下:
while (true) {
loop.forEach((阶段) => {
阶段全部任务();
nextTick全部任务();
microTask全部任务();
});
loop = loop.next;
}
前端安全
[XSS] 攻击
按照之前说的思路,先讲概念,说用途
什么是[XSS攻击]
XSS即Cross Site Scripting([跨站脚本攻击],指的是攻击者想尽一切办法将一些可执行的代码注入到网页中,利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。为了不和层叠样式表CSS混淆,故将其缩写为 XSS
XSS攻击如何进行防范
1.输入输出过滤
一切用户输入皆不可信,在输出时进行验证,一般做法是将 ‘ ” < > & 这些个危险字符进行转义。
2. Cookie 的 HttpOnly
当用户的登录凭证存储于服务器的 session 中,而在浏览器中是以 cookie 的形式存储的。很多XSS攻击目标都是窃取用户cookie伪造身份认证。
可以通过在 cookie 中设置 HttpOnly 属性,js脚本将无法读取到 cookie 信息。
ctx.cookies.set(name, value, {
httpOnly: true // 默认为 true
})
3. CSP(内容安全策略)
CSP (Content Security Policy,内容安全策略)是 W3C 提出的 ,本质上就是白名单制度,开发者明确告诉浏览器哪些外部资源可以加载和执行。它的实现和执行全部由浏览器完成,我们只需提供配置。
CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。
两种方法可以启用 CSP:
- 一种是通过 HTTP 头信息的
Content-Security-Policy的字段 - 另一种是通过网页的
<meta>标签
什么是CSRF攻击
CSRF:跨站点请求伪造(Cross-Site Request Forgeries),也被称为 one-click attack 或者 session riding。冒充用户发起请求(在用户不知情的情况下), 完成一些违背用户意愿的事情(如修改用户信息,删除评论等)。
举个例子,好友小A在银行存有一笔钱,输入用户名密码登录银行账户后,发送请求给xiaofan账户转888:
http://bank.example.com./withdraw?account=xiaoA&amount=888&for=xiaonfan
转账过程中, 小A不小心打开了一个新页面,进入了黑客(xiaohei)的网站,而黑客网站有如下html代码:
<html>
<!--其他内容-->
<img src=http://bank.example.com./withdraw?account=xiaoA&amount=888&for=xiaohei width='0' height='0'>
<!--其他内容-->
</html>
这个模拟的img请求就会带上小A的session值, 成功的将888转到xiaohei的账户上。例子虽然是get,post请求提交表单同样会被攻击。
如何防御
- 验证码:强制用户必须与应用进行交互,才能完成最终请求。此种方式能很好的遏制 CSRF,但是用户体验相对差。
- 尽量使用 post ,限制 get 使用;上一个例子可见,get 太容易被拿来做 CSRF 攻击,但是 post 也并不是万无一失,攻击者只需要构造一个form就可以。
- Referer check:请求来源限制,此种方法成本最低,但是并不能保证 100% 有效,因为服务器并不是什么时候都能取到 Referer,而且低版本的浏览器存在伪造 Referer 的风险。
- token:token 验证的 CSRF 防御机制是公认最合适的方案。
点击劫持
点击劫持(click hijacking)也称为 UI 覆盖攻击。它通过一些内容(如游戏)误导被攻击者点击,虽然被攻击者点击的是他所看到的网页,但其实所点击的是另一个置于原网页上面的透明页面。
根据先点击劫持原理示意图,分析典型点击劫持攻击流程:
- 攻击者构建了一个非常有吸引力的网页
- 将被攻击的页面放置在当前页面的 iframe 中
- 使用样式将 iframe 叠加到非常有吸引力内容的上方
- 将iframe设置为100%透明
- 用户在不知情的情况下点击按钮,触发执行一些其他命令。
如何防御
点击劫持攻击需要首先将目标网站载入到恶意网站中,使用 iframe 载入网页是最有效的方法。
所以可以设置我们的网页不允许使用iframe被加载到其他网页中就可以避免这种情况了,我们可以通过在响应头中设置X-Frame-Options(服务器端进行),X-Frame-Options可以设置以下三个值:
DEBY:不允许任何网页使用iframe加载我这个页面。SAMEORIGIN:只允许在相同域名(也就是自己的网站)下使用iframe加载这个页面。ALLOWED-FROM origin: 允许任何网页通过iframe加载我这个网页。
这种方式在一些老旧的浏览器上是不支持的,具体可以通过can i use去查看