网站挂载,上海网站建设定制公,个人注册公司流程和费用标准,dz做分类网站文章目录 一、什么是设计模式#xff1f;二、设计几个原则三、常见的设计模式及实际案例3.1、单例模式3.1.1、Element UI3.1.2、Vuex 3.2、工厂模式3.2.1、VNode3.2.2、vue-route 3.3、策略模式3.3.1、表格 formatter3.3.2、表单验证 3.4、代理模式3.4.1、拦截器3.4.2、前端框… 文章目录 一、什么是设计模式二、设计几个原则三、常见的设计模式及实际案例3.1、单例模式3.1.1、Element UI3.1.2、Vuex 3.2、工厂模式3.2.1、VNode3.2.2、vue-route 3.3、策略模式3.3.1、表格 formatter3.3.2、表单验证 3.4、代理模式3.4.1、拦截器3.4.2、前端框架的数据响应式化 3.5、适配器模式3.5.1、Vue 计算属性3.5.2、源码中的适配器模式 3.6、观察者模式/发布-订阅模式3.6.1、什么是观察者模式3.6.2、什么是发布-订阅模式3.6.3、EventBus3.6.3.1、创建事件中心管理组件之间的通信3.6.3.2、发送事件3.6.3.3、接收事件 3.6.4、Vue源码 四、最后 一、什么是设计模式
设计模式是一套被反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。它是为了可重用代码让代码更容易的被他人理解并保证代码的可靠性。
设计模式实际上是“拿来主义”在软件领域的贯彻实践它是一套现成的工具拿来即用。下面来看一下设计模式的设计原则。
二、设计几个原则
单一职责原则、开放封闭原则、里式替换原则、接口隔离原则 、依赖反转原则 、最少知识原则。
下面我们一起来看看几种在前端领域常见的设计模式 单例模式、工厂模式、策略模式、代理模式、适配器模式、观察者模式/发布-订阅模式 三、常见的设计模式及实际案例
3.1、单例模式
单例模式 Singleton Pattern又称为单体模式保证一个类只有一个实例并提供一个访问它的全局访问点。也就是说第二次使用同一个类创建新对象的时候应该得到与第一次创建的对象完全相同的对象。
3.1.1、Element UI
Element UI是使用Vue开发的一个前端UI框架。ElementUI 中的全屏 Loading 蒙层调用有两种形式 指令形式Vue.use(Nonradioactive) 服务形式Vue.prototype.$loading service 指令形式注册的使用方式
div :v-loading.fullscreentrue.../div服务形式注册的使用方式
this.$loading({ fullscreen: true })用服务方式使用全屏 Loading 是单例的即在前一个全屏 Loading 关闭前再次调用全屏 Loading并不会创建一个新的 Loading 实例而是返回现有全屏 Loading 的实例。
下面是 ElementUI 实现全屏 Loading 的源码
import Vue from vue
import loadingVue from ./loading.vue
const LoadingConstructor Vue.extend(loadingVue)
let fullscreenLoading
const Loading (options {}) {if (options.fullscreen fullscreenLoading) {return fullscreenLoading}let instance new LoadingConstructor({el: document.createElement(div),data: options})if (options.fullscreen) {fullscreenLoading instance}return instance
}
export default Loading这里的单例是 fullscreenLoading是存放在闭包中的如果用户传的 options 的 fullscreen 为 true 且已经创建了单例则直接返回之前创建的单例如果之前没有创建过则创建单例并赋值给闭包中的 fullscreenLoading 后返回新创建的单例实例。
3.1.2、Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。Vuex它们都实现了一个全局的 Store 用于存储应用的所有状态。这个 Store 的实现正是单例模式的典型应用。 Vuex 使用单一状态树用一个对象就包含了全部的应用层级状态。至此它便作为一个唯一数据源 (SSOT)而存在。这也意味着每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段在调试的过程中也能轻易地取得整个当前应用状态的快照。
// 安装vuex插件
Vue.use(Vuex)
// 将store注入到Vue实例中
new Vue({el: #app,store
})通过调用Vue.use()方法安装了 Vuex 插件。Vuex 插件是一个对象它在内部实现了一个 install 方法这个方法会在插件安装时被调用从而把 Store 注入到Vue实例里去。也就是说每 install 一次都会尝试给 Vue 实例注入一个 Store。
let Vue // 这个Vue的作用和楼上的instance作用一样
...
export function install (_Vue) {// 判断传入的Vue实例对象是否已经被install过Vuex插件是否有了唯一的stateif (Vue _Vue Vue) {if (process.env.NODE_ENV ! production) {console.error([vuex] already installed. Vue.use(Vuex) should be called only once.)}return}// 若没有则为这个Vue实例对象install一个唯一的VuexVue _Vue// 将Vuex的初始化逻辑写进Vue的钩子函数里applyMixin(Vue)
}可以保证一个 Vue 实例即一个 Vue 应用只会被 install 一次 Vuex 插件所以每个 Vue 实例只会拥有一个全局的 Store。
3.2、工厂模式
工厂模式就是根据不用的输入返回不同的实例一般用来创建同一类对象它的主要思想就是将对象的创建与对象的实现分离。 在创建对象时不暴露具体的逻辑而是将逻辑封装在函数中那么这个函数就可以被视为一个工厂。工厂模式根据抽象程度的不同可以分为简单工厂、工厂方法、抽象工厂。
3.2.1、VNode
和原生的 document.createElement 类似Vue 这种具有虚拟 DOM 树Virtual Dom Tree机制的框架在生成虚拟 DOM 的时候提供了 createElement 方法用来生成 VNode用来作为真实 DOM 节点的映射
createElement(h3, { class: main-title }, [createElement(img, { class: avatar, attrs: { src: ../avatar.jpg } }),createElement(p, { class: user-desc }, hello world)
])createElement 函数结构大概如下
class Vnode (tag, data, children) { ... }
function createElement(tag, data, children) {return new Vnode(tag, data, children)
}3.2.2、vue-route
在Vue在路由创建模式中也多次用到了工厂模式
export default class VueRouter {constructor(options) {this.mode mode // 路由模式switch (mode) { // 简单工厂case history: // history 方式this.history new HTML5History(this, options.base)breakcase hash: // hash 方式this.history new HashHistory(this, options.base, this.fallback)breakcase abstract: // abstract 方式this.history new AbstractHistory(this, options.base)breakdefault:// ... 初始化失败报错}}
}mode 是路由创建的模式这里有三种 History、Hash、Abstract其中History 是 H5 的路由方式Hash 是路由中带 # 的路由方式Abstract 代表非浏览器环境中路由方式比如 Node、weex 等this.history 用来保存路由实例vue-router 中使用了工厂模式的思想来获得响应路由控制类的实例。
3.3、策略模式
策略模式 Strategy Pattern又称政策模式其定义一系列的算法把它们一个个封装起来并且使它们可以互相替换。封装的策略算法一般是独立的策略模式根据输入来调整采用哪个算法。关键是策略的实现和使用分离。
3.3.1、表格 formatter
Element UI 的表格控件的 Column 接受一个 formatter 参数用来格式化内容其类型为函数并且还可以接受几个特定参数像这样Function(row, column, cellValue, index)。
以文件大小转化为例后端经常会直接传 bit 单位的文件大小那么前端需要根据后端的数据根据需求转化为自己需要的单位的文件大小比如 KB/MB。
首先实现文件计算的算法
export const StrategyMap {// Strategy 1: 将文件大小bit转化为 KB bitToKB: val {const num Number(val)return isNaN(num) ? val : (num / 1024).toFixed(0) KB},// Strategy 2: 将文件大小bit转化为 MB bitToMB: val {const num Number(val)return isNaN(num) ? val : (num / 1024 / 1024).toFixed(1) MB}
}
// Context: 生成el表单 formatter
const strategyContext function(type, rowKey){ return function(row, column, cellValue, index){StrategyMap[type](row[rowKey])}
}
export default strategyContext在组件中直接使用
templateel-table :datatableDatael-table-column propdate label日期/el-table-columnel-table-column propname label文件名/el-table-column!-- 直接调用 strategyContext --el-table-column propsizeKb label文件大小(KB):formatterstrategyContext(bitToKB, sizeKb)/el-table-columnel-table-column propsizeMb label附件大小(MB):formatterstrategyContext(bitToMB, sizeMb)/el-table-column/el-table
/template
script typetext/javascriptimport strategyContext from ./strategyContext.jsexport default {name: ElTableDemo,data() {return {strategyContext,tableData: [{ date: 2019-05-02, name: 文件1, sizeKb: 1234, sizeMb: 1234426 },{ date: 2019-05-04, name: 文件2, sizeKb: 4213, sizeMb: 8636152 }]}}}
/script
style scoped/style运行结果如下图 3.3.2、表单验证
除了表格中的 formatter 之外策略模式也经常用在表单验证的场景。Element UI 的 Form 表单 具有表单验证功能用来校验用户输入的表单内容。实际需求中表单验证项一般会比较复杂所以需要给每个表单项增加 validator 自定义校验方法。
实现通用的表单验证方法
// src/utils/validates.js
// 姓名校验 由2-10位汉字组成
export function validateUsername(str) {const reg /^[\u4e00-\u9fa5]{2,10}$/return reg.test(str)
}
// 手机号校验 由以1开头的11位数字组成
export function validateMobile(str) {const reg /^1\d{10}$/return reg.test(str)
}
// 邮箱校验
export function validateEmail(str) {const reg /^[a-zA-Z0-9_-][a-zA-Z0-9_-](\.[a-zA-Z0-9_-])$/return reg.test(str)
}增加一个柯里化方法用来生成表单验证函数
// src/utils/index.js
import * as Validates from ./validates.js
// 生成表格自定义校验函数
export const formValidateGene (key, msg) (rule, value, cb) {if (Validates[key](value)) {cb()} else {cb(new Error(msg))}
}具体使用
templateel-form refruleFormlabel-width100pxclassdemo-ruleForm:rulesrules:modelruleFormel-form-item label用户名 propusernameel-input v-modelruleForm.username/el-input/el-form-itemel-form-item label手机号 propmobileel-input v-modelruleForm.mobile/el-input/el-form-itemel-form-item label邮箱 propemailel-input v-modelruleForm.email/el-input/el-form-item/el-form
/template
script typetext/javascriptimport * as Utils from ../utilsexport default {name: ElTableDemo,data() {return {ruleForm: { pass: , checkPass: , age: },rules: {username: [{validator: Utils.formValidateGene(validateUsername, 姓名由2-10位汉字组成),trigger: blur}],mobile: [{validator: Utils.formValidateGene(validateMobile, 手机号由以1开头的11位数字组成),trigger: blur}],email: [{validator: Utils.formValidateGene(validateEmail, 不是正确的邮箱格式),trigger: blur}]}}}}
/script效果如图 3.4、代理模式
代理模式 Proxy Pattern又称委托模式它为目标对象创造了一个代理对象以控制对目标对象的访问。
代理模式把代理对象插入到访问者和目标对象之间从而为访问者对目标对象的访问引入一定的间接性。正是这种间接性给了代理对象很多操作空间比如在调用目标对象前和调用后进行一些预操作和后操作从而实现新的功能或者扩展目标的功能。
3.4.1、拦截器
在项目中经常使用 Axios 的实例来进行 HTTP 的请求使用拦截器 interceptor 可以提前对 request 请求和 response 返回进行一些预处理比如 1、request 请求头的设置和 Cookie 信息的设置 2、权限信息的预处理常见的比如验权操作或者 Token 验证 3、数据格式的格式化比如对组件绑定的 Date 类型的数据在请求前进行一些格式约定好的序列化操作 4、空字段的格式预处理根据后端进行一些过滤操作 5、response 的一些通用报错处理比如使用 Message 控件抛出错误 除了 HTTP 相关的拦截器之外还有路由跳转的拦截器可以进行一些路由跳转的预处理等操作。
3.4.2、前端框架的数据响应式化
Vue 2.x 中通过 Object.defineProperty 来劫持各个属性的 setter/getter在数据变动时通过发布-订阅模式发布消息给订阅者触发相应的监听回调从而实现数据的响应式化也就是数据到视图的双向绑定。
为什么 Vue 2.x 到 3.x 要从 Object.defineProperty 改用 Proxy 呢是因为前者的一些局限性导致的以下缺陷 1、无法监听利用索引直接设置数组的一个项例如vm.items[indexOfItem] newValue 2、无法监听数组的长度的修改例如vm.items.length newLength 3、无法监听 ES6 的 Set、WeakSet、Map、WeakMap 的变化 4、无法监听 Class 类型的数据 5、无法监听对象属性的新加或者删除 3.5、适配器模式
适配器模式Adapter Pattern又称包装器模式将一个类对象的接口方法、属性转化为用户需要的另一个接口解决类对象之间接口不兼容的问题。
主要功能是进行转换匹配目的是复用已有的功能而不是来实现新的接口。也就是说访问者需要的功能应该是已经实现好了的不需要适配器模式来实现适配器模式主要是负责把不兼容的接口转换成访问者期望的格式而已。
3.5.1、Vue 计算属性
Vue 中的计算属性也是一个适配器模式的实例以官网的例子为例
templatediv idexamplepOriginal message: {{ message }}/p !-- Hello --pComputed reversed message: {{ reversedMessage }}/p !-- olleH --/div
/template
script typetext/javascriptexport default {name: demo,data() {return {message: Hello}},computed: {reversedMessage: function() {return this.message.split().reverse().join()}}}
/script对原有数据并没有改变只改变了原有数据的表现形式。
3.5.2、源码中的适配器模式
Axios 的用来发送请求的 adapter 本质上是封装浏览器提供的 API XMLHttpRequest。
module.exports function xhrAdapter(config) {return new Promise(function dispatchXhrRequest(resolve, reject) {var requestData config.datavar requestHeaders config.headersvar request new XMLHttpRequest()// 初始化一个请求request.open(config.method.toUpperCase(),buildURL(config.url, config.params, config.paramsSerializer), true)// 设置最大超时时间request.timeout config.timeout// readyState 属性发生变化时的回调request.onreadystatechange function handleLoad() { ... }// 浏览器请求退出时的回调request.onabort function handleAbort() { ... }// 当请求报错时的回调request.onerror function handleError() { ... }// 当请求超时调用的回调request.ontimeout function handleTimeout() { ... }// 设置HTTP请求头的值if (setRequestHeader in request) {request.setRequestHeader(key, val)}// 跨域的请求是否应该使用证书if (config.withCredentials) {request.withCredentials true}// 响应类型if (config.responseType) {request.responseType config.responseType}// 发送请求request.send(requestData)})
}这个模块主要是对请求头、请求配置和一些回调的设置并没有对原生的 API 有改动所以也可以在其他地方正常使用。这个适配器可以看作是对 XMLHttpRequest 的适配是用户对 Axios 调用层到原生 XMLHttpRequest 这个 API 之间的适配层。
3.6、观察者模式/发布-订阅模式
3.6.1、什么是观察者模式
观察者模式Observer Pattern定义了一种一对多的关系让多个订阅者对象同时监听某一个发布者或者叫主题对象这个主题对象的状态发生变化时就会通知所有订阅自己的订阅者对象使得它们能够自动更新自己。
3.6.2、什么是发布-订阅模式
其实它是发布订阅模式的一个别名但两者又有所不同。这个别名非常形象地诠释了观察者模式里两个核心的角色要素——发布者和订阅者。 观察者模式是由具体目标调度的而发布-订阅模式是统一由调度中心调的
3.6.3、EventBus
在Vue中有一套事件机制其中一个用法是 EventBus。可以使用 EventBus 来解决组件间的数据通信问题。
3.6.3.1、创建事件中心管理组件之间的通信
// event-bus.js
import Vue from vue
export const EventBus new Vue()3.6.3.2、发送事件
templatedivfirst-com/first-comsecond-com/second-com/div
/template
script
import firstCom from ./firstCom.vue
import secondCom from ./secondCom.vue
export default {components: { firstCom, secondCom }
}
/scriptfirstCom组件中发送事件
templatedivbutton clickadd加法/button /div
/template
script
import {EventBus} from ./event-bus.js // 引入事件中心
export default {data(){return{num:0}},methods:{add(){EventBus.$emit(addition, {num:this.num})}}
}
/script3.6.3.3、接收事件
在secondCom组件中发送事件
templatediv求和: {{count}}/div
/template
script
import { EventBus } from ./event-bus.js
export default {data() {return {count: 0}},mounted() {EventBus.$on(addition, param {this.count this.count param.num;})}
}
/script3.6.4、Vue源码
发布-订阅模式在源码中应用很多比如双向绑定机制的场景 响应式化大致就是使用 Object.defineProperty 把数据转为 getter/setter并为每个数据添加一个订阅者列表的过程。这个列表是 getter 闭包中的属性将会记录所有依赖这个数据的组件。也就是说响应式化后的数据相当于发布者。
每个组件都对应一个 Watcher 订阅者。当每个组件的渲染函数被执行时都会将本组件的 Watcher 放到自己所依赖的响应式数据的订阅者列表里这就相当于完成了订阅一般这个过程被称为依赖收集Dependency Collect。 组件渲染函数执行的结果是生成虚拟 DOM 树Virtual DOM Tree这个树生成后将被映射为浏览器上的真实的 DOM树也就是用户所看到的页面视图。 当响应式数据发生变化的时候也就是触发了 setter 时setter 会负责通知Notify该数据的订阅者列表里的 WatcherWatcher 会触发组件重渲染Trigger re-render来更新update视图。
// src/core/observer/index.js 响应式化过程
Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {// ...const value getter ? getter.call(obj) : val // 如果原本对象拥有getter方法则执行dep.depend() // 进行依赖收集dep.addSubreturn value},set: function reactiveSetter(newVal) {// ...if (setter) { setter.call(obj, newVal) } // 如果原本对象拥有setter方法则执行dep.notify() // 如果发生变更则通知更新}
})四、最后
本人每篇文章都是一字一句码出来希望对大家有所帮助多提提意见。顺手来个三连击点赞收藏关注✨一起加油☕