一、结构型1 - 装饰器模式
定义: 不改变原对象的基础上,包装拓展,使其可满足更复杂需求。
装饰器应用场景:
比如这样一个场景:①要给所有按钮加上弹窗校验。②校验未通过时将按钮文本修改并置灰。需要满足不同按钮的自定义需求。如果第一步已经由别人完成,第二步是之后的扩展,扩展时不想关注原来的逻辑,只想进行扩充,这时候就用到装饰器模式了。
装饰器第一步: 将旧逻辑先抽出去。(用到单例模式的案例)
// 弹窗逻辑的抽取
function openModal() {
const modal = new Modal()
modal.style.display = 'block'
}装饰器第二步: 写新逻辑
// 按钮文本修改逻辑
function changeBtnText() {
const btn = document.querySelector('#open')
btn.innerText = '去登录'
}
// 按钮置灰逻辑
function btnGray() {
const btn = document.querySelector('#open')
btn.setAttribute('disabled', true)
}
// 新功能整合
function changeBtn() {
changeBtnText()
btnGray()
}然后把处理逻辑放到按钮中
openBtn.addEventListener('click', ()=>{
openModal()
changeBtn()
})这样实现了只添加不修改。
ES6 中用面向对象方式来写上面的代码:
class OpenModal {
onClick: function(){
const modal = new Modal()
modal.style.display = 'block'
}
}
class BtnDecorator {
// 将按钮实例传入
constructor(btn){
this.btn = btn
}
onClick: function(){
this.btn.onClick()
// 包装一层新逻辑
this.btnChange()
}
btnChange(){
this.changeBtnText()
this.makeBtnDisabled()
}
changeBtnText(){
const btn = document.querySelector('#open')
btn.innerText = '先登录'
}
makeBtnDiabled() {
const btn = document.querySelector('#open')
btn.setAttribute('disabled', true)
}
}
const openModal = new OpenModal()
const btnDecor = new BtnDecorator()
btn.addEventListener('click',function(){
// 下面这两种点击,一个是只有弹窗,一个是补充了新功能
// openModal.onclick()
btnDecor.onclick()
})在ES7中Decorator模式直接可以拿来用。这是一个语法糖。
装饰器的原理:
装饰器定义的方式: 定义装饰器函数,把被装饰者传给装饰器函数。
ES7中的@decorator装饰器就是一个语法糖,他帮我们做了一些工作:①函数传参及调用;②将“属性装饰器”交到你手里。
给类添加装饰器
function classDecorator(target){
target.hasDecorator = true
return target
}
// 将装饰器安装到Button上
@classDecorator
class Button {
// ...
}给方法添加装饰器
看不懂下面一段代码的意思
function funcDecorator(target, name, descriptor) {
let originalMethod = descorator.value
descorator.value = function() {
console.log('我是func的装饰器逻辑')
return originalMethod.apply(this, arguments)
}
return descriptor
}
class Button {
@funcDecorator
onClick() {
console.log('我是func的原有逻辑')
}
}这时候target变成了Button.prototype,就是类的原型对象。这是因为onClick方法总是要依附其实例存在的,修饰onClick其实是修饰他的实例。
装饰器函数是在编译阶段执行的,实例是在运行阶段执行。所以装饰器函数真正触及的就是在类层面,不在实例层面。
???
在类装饰器中传入一个target就行了,但是在上例中方法装饰器有三个参数。第一个参数target:是类的原型。第二个参数:是修饰的目标属性名。第三个参数经常用:属性描述对象。就跟Object.defineProperty的第三个参数一样,由多个属性描述符组成。分为数据描述符(比如value、writteble)和存取描述符(get、set)。拿到这个值,就拿到了目标方法的属性控制权。
用例:React中的高阶组件
高阶组件就是一个函数,接收一个组件作为参数,返回一个新组件。
他是装饰器模式在react中的实践。
下例:把一个组件丢到有红色边框的容器里。
import React, { Component } from 'react'
const HOCdemo = (WrapperComponent)=> class extends Component {
render() {
return <div style={{border: '2px solid #FF00'}}>
<WrapperComponent/>
</div>
}
}
export default HOCdemo咋装饰呢
import React, { Component } form 'react'
import HocDemo from './HOCdemo'
@HocDemo
class TargetDemo extends React.Component {
render(){
// 处理
}
}
export default TargetDemo使用装饰器改写Redux的connect
在React中用Redux的时候,一般需要用connect将状态和组件绑起来。
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import action from './action.js'
class App extends Component {
render() {//App的业务逻辑}
}
function mapStateToProps(state){
// 假设App的状态对应状态树上的app节点
return state.app
}
function mapDispatchToProps(dispatch){
return bindActionCreators(action, dispatch)
}
// 将APP和Redux绑定
export default connect(mapStateToProps, mapDispatchToProps)(App)
connect的俩参数: 第一个是一个函数,建立组件和状态之间的映射关系,第二个参数也是函数,建立组件和store.dispatch之间的的关系,使组件具有dispatch派发状态的能力。
调用connect就返回一个具有装饰作用的函数,此函数可接收一个函数作为参数,使这个目标组件和Redux结合,具有Redux提供的数据和能力。
可以改写为这样,把connect抽取出来:
import { connect } from 'connect'
import { bindActionCreators } from 'react-redux'
import action from './action.js'
function mapStateToProps(state) {
return state.app
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(action, dispatch)
}
// 将connect调用的结果作为一个装饰器输出
export default connect(mapStateToProps, mapDispatchToProps)给组件加上装饰器
import React, {Component} from 'react'
import connect from './connect.js'
@connect
export default class App extends React.Component {
render(){
}
}装饰器模式的优点:
灵活、复用性高,不依托任何逻辑存在。
code-decorators封装了一些常用的装饰器。
二、结构型2 - 适配器模式
适配器想要达到的一个目标就是: 兼容与统一
axios就用到了这种模式,实际上派发请求的dispatchRequest方法中,做了两件事:1是数据转换-将请求体、响应体里的数据格式化;2是调用适配器。
三、结构型3 - 代理模式
代理的应用挺多的,比如VPN、Nginx接口转发、等等。用到代理是因为不能直接访问。
ES6中的proxy
const proxy = new Proxy(obj, handler)第一个参数是目标对象,handler也是一个对象,用来定义代理的行为。当我们通过proxy去访问目标对象时,handler会对我们的行为做一层拦截,也就是说每次行为都要经过handler。
例如婚介所:
const girl = {
name: '小妹',
age: 22,
introduce: 'xxxxx',
avator: 'xxxx',
tmpAvator: 'cccc',
mobile: '24354'
}有些信息是私密信息,需要平台一次过滤,如果是VIP才能看
// 普通信息
const baseInfo = ['name', 'introduce', 'tmpAvator']
// 最私密
const privateInfo = ['avator', 'mobile']
// 某用户为例
const user = {
...// 一些必要信息
isValidated: true,
isVIP: false
}
// 婚介所
const hunjie = new Proxy(girl, {
get: function(girl, key){
if(baseInfo.indexOf(key) !==-1 && !user.isValidate) {
alert('你还没有完成验证')
return
}
if(user.isValidated && privateInfo.indexOf(key) && !user.isVIP) {
alert('只有VIP才能查看这个信息')
}
}
})代理模式的应用
四种: 事件代理、虚拟代理、缓存代理、保护代理。
1. 事件代理
很常见的一种代理。
比如一个ul>li*n的组合,每点击一个li都要有有些操作,那么需要给每个li添加一个监听,li的个数也多,开销越大。
但事件本身是可以冒泡的,从li上冒到ul上,所以我们在ul上绑定一次监听事件就可以监听到所有li的事件。
2. 虚拟代理
懒加载: 用来处理图片的加载时机,比如先用div占位,等滚动到位置后再加载。
图片预加载: 为避免网络不好时白屏,常见的操作是先给img标签弄一个占位图,创建一个image实例,让这个Image实例的src指向实际的图片,观察image实例的加载情况--当对应的真实图片加载完成,即已经有了该图片的缓存内容,再将img的src指向真实的图片。
class PreLoadImage {
constructor(imgNode) {
// 获取真实的dom节点
this.imgNode = imgNode
}
setSrc(imgUrl){
this.imgNode.src = imgUrl
}
}
class ProxyImage {
static Loading_url = 'xxx'
constructor(targetImage) {
this.targetImage = targetImage
}
setSrc(targetUrl){
this.targetImage.setSrc(ProxyImage(Loading_url))
const virtualImage = new Image()
virtualImage.onload = ()=>{
this.targetImage.setSrc(targetUrl)
}
virtualImage.src = targetUrl
}
}3. 缓存代理
计算量大的时候,如果我们已经算过某个值不想再算第二次,就从内存中取结果,这时候需要一个缓存代理,在我们计算结果的同时将计算结果缓存下来。
比如对传入的值求和
const addAll = function(){
console.log('进行一次新计算')
let result = 0
const len= arguments.length
for(let i = 0; i<len;i++) {
result += argument[i]
}
return result
}
const proxyAddAll = (function() {
const resultCache = {}
return function(){
// 将入参转化为一个字符串
const args = Array.prototype.join.call(arguments, ',')
// 检查入参是否有对应的计算结果
if(args in resultCache) {
return resultCache[args]
}
return resultCache[args] = addAll(...arguments)
}
})()保护代理
就上面婚介所那个例子。