前端应该知道的常用设计模式之-结构型

本文深入探讨了JavaScript中的三种设计模式:装饰器模式用于灵活添加功能,如按钮点击弹窗;适配器模式通过axios展示了如何兼容不同调用方式和环境;代理模式则讲解了事件代理、虚拟代理和缓存代理的应用,如图片预加载和数据访问权限控制。这些模式帮助实现代码的复用、兼容和安全。
摘要由CSDN通过智能技术生成
一、装饰器模式-适用于灵活添加功能模块(场景:点击按钮打开弹窗,并修改该按钮禁用和文字内容)
(1)、ES6实现方式
//  定义按钮
class OpenButton {
    onClick() {
        const dialog = new Dialog()
        dialog.visible = true
    }
}
// 定义按钮装饰器
class Decorator {
    constructor(button) {
        this.button = button
    }
    // 按钮点击
    onClick() {
        this.button.onClick()
        this.disabledButton()
        this.changeButtonText()
    }
    disabledButton() {
        const btn = getElementById('#btn')
        btn.setAttribute('disabled', true)
    }
    changeButtonText() {
        const btn = getElementById('#btn')
        btn.innerText('按钮文字已修改')
    }
    changexx() {
    	
    }
}

// 创建按钮
const btn = new OpenButton()
const decorator = new Decorator(btn)
// 绑定点击事件
document.getElementById('#btn').addListener('click', () => {
	decorator.onClick()
})

*思考:单一职责原则:将不同的职责分离,可以做到每个职责都能被灵活地复用;不同职责之间无法相互干扰,不会出现因为修改了 A 逻辑而影响了 B 逻辑

(2)、ES7 @语法糖
// 1、装饰类
function classDecorator(target) {
	// 装饰器第一个参数target是目标类
    target.hasDecorator = true
    return target
}

@classDecorator
class Button {
}
console.log('Button被装饰了', Button.hasDecorator) // 'Button被装饰了', true

// 2、装饰方法
function funcDecorator(target, name, descriptor) {
	// 装饰器第三个参数指向解释器
	let originalMethod = descriptor.value
	descriptor.value = function() {
		console.log('修饰后的')
		return originalMethod.apply(this, arguments)
	}
	retturn descriptor
}
class Button{
	@funcDecorator
	onClick() {
		console.log('原始的')
	}
}
const button = new Button()
button.onClick() // 修饰后的 原始的
二、适配器模式-适用于同时兼容多种条件(场景:axios适配的兼容方案)
(1)、axios常用的三种调用方式
axios.get('/user')
.then(() => {})
.catch(() => {})

axios.post('/user', {name:'xxx'})
.then(() => {})
.catch(() => {})

axios({
    method: 'post',
    url: '/user',
    data: {}
})
(2) axios适配各种调用形式、适配xhr和nodejs的实现源码
// 若用户未手动配置适配器,则使用默认的适配器
var adapter = config.adapter || defaults.adapter;
// dispatchRequest方法的末尾调用的是适配器方法
return adapter(config).then(function onAdapterResolution(response) {
	// 请求成功的回调
    throwIfCancellationRequested(config);
    // 转换响应体
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );
    return response;
  }, function onAdapterRejection(reason) {
  	// 请求失败的回调
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);
      // 转换响应体
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }
    return Promise.reject(reason);
  });
  
  // axios/lib/default.js中默认adapter适配器逻辑
  function getDefaultAdapter() {
      var adapter;
      // 判断当前是否是node环境
      if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
        // 如果是node环境,调用node专属的http适配器
        adapter = require('./adapters/http');
      } else if (typeof XMLHttpRequest !== 'undefined') {
        // 如果是浏览器环境,调用基于xhr的适配器
        adapter = require('./adapters/xhr');
      }
      return adapter;
    }

*思考:一个好的适配器:把变化留给自己,把统一留给用户。axios中,所有关于 http 模块、关于 xhr 的实现细节,全部被 Adapter 封装进了自己复杂的底层逻辑里,暴露给用户的都是十分简单的统一的东西——统一的接口,统一的入参,统一的出参,统一的规则。

三、代理模式-在行为与目标之前再加一层处理手段(场景:事件、虚拟、缓存、保护)
(1)、事件代理: 点击任何一个li标签,都能开启一个弹窗
<ul id="wrapper">
    <li>item1</li>
    <li>item2</li>
    <li>item3</li>
</ul>
// 常规思维:遍历每一个li标签,并绑定事件

// 利用事件代理只在父元素上绑定事件,并判定事件target是否是li标签
document.getElementById('wrapper').addEventListener('click', (e) => {
    if(e.target.tagName === 'li') {
        // 以下是监听函数的函数体
        e.preventDefault()
        alert(`我是${e.target.innerText}`)
    }
})
(2)、虚拟代理:图片预加载,先用站位图站位,请求到实际图片资源时再替换img的src
class PreLoadImage{
    constructor(imageNode) {
        // 获取真实的image
        this.imageNode = imageNode
    }
    // 修改image的src属性值
    setSrc(imageUrl) {
        this.imageNode.src = imageUrl
    }
}

class ProxyImage {
	static LOADING_URL = 'xx'
	constructor(tagetImage) {
		// 真实的image,PreLoadImage实例
		this.tagetImage = tagetImage
	}
	// 操作虚拟image完成图片资源的加载
	setSrc() {
		// 真实img节点初始化时展示的是一个占位图
        this.targetImage.setSrc(ProxyImage.LOADING_URL)
        // 创建一个帮我们加载图片的虚拟Image实例
        const virtualImage = new Image()
        // 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
        virtualImage.onload = () => {
            this.targetImage.setSrc(targetUrl)
        }
        // 设置src属性,虚拟Image实例开始加载图片
        virtualImage.src = targetUrl
	}
}
(3)、缓存代理:计算量大时缓存数据结果,再次调用时使用缓存
// addAll方法:对传入的所有参数做求和
const addAll = function() {
    console.log('进行了一次新计算')
    let result = 0
    const len = arguments.length
    for(let i = 0; i < len; i++) {
        result += arguments[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)
    }
})()
proxyAddAll(1,2,3) // 6
proxyAddAll(1,2,3) // 6
(4)、保护代理:利用es6中的proxy实现在数据操作(get、set)过程中的保护
// obj是代理的目标对象;handler对象用来定义保护行为,当操作obj时handler会进行拦截
const proxy = new Proxy(obj, handler)

场景:交友网站访问网友的权限校验
const baseInfo = ['name']
const privateInfo = ['age', 'avater']

const present = {
    type: '游艇',
    value: 60,
}

const girl = {
	name: '艾美丽',
	age: 18,
	avater: '空姐',
	// 礼物数组
    presents: [],
    // 拒收50块以下的礼物
    bottomValue: 50,
    // 记录最近一次收到的礼物
    lastPresent: present,
}

const guest = {
	name: '小强',
	isVip: false,
	isValidated: false
}

const interceptor = new Proxy(girl, {
	get: function (girl, key) {
		if(baseInfo.indexOf(key)!==-1 && !user.isValidated) {
          alert('您还没有完成验证')
          return
      	}
      	
      	// 此处我们认为只有验证过的用户才可以购买VIP
      if(user.isValidated && privateInfo.indexOf(key) && !user.isVip) {
          alert('只有VIP才可以查看该信息哦')
          return
      }
	},
	set: function(girl, key, value) {
		// 最近一次送来的礼物会尝试赋值给lastPresent字段
        if(key === 'lastPresent') {
          if(val.value < girl.bottomValue) {
              alert('sorry,您的礼物被拒收了')
              return
          }
          // 如果没有拒收,则赋值成功,同时并入presents数组
          girl.lastPresent = val
          girl.presents = [...girl.presents, val]
        }
    }
})

// proxy的处理器类型汇总

// 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
handler.getPrototypeOf()

// 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
handler.setPrototypeOf()

// 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
handler.isExtensible()
 
// 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
handler.preventExtensions()

// 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
handler.getOwnPropertyDescriptor()
 
// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
andler.defineProperty()
 
// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
handler.has()

// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.get()
 
// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.set()

// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.deleteProperty()

// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.ownKeys()

// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
handler.apply()
// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
handler.construct()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值