装饰者模式在日常开发中的缩影和vue中的使用详解

目录
  • 一、日常开发
    • 1、数据埋点
    • 2、表单校验
  • 二、框架功能(vue)
    • 1、数组监听
    • 2、重写挂载
  • 总结

一、日常开发

装饰者模式以其不改变原对象,并且与原对象有着相同接口的特点,广泛应用于日常开发和主流框架的功能中。

1、数据埋点

假如我们开发了一个移动端网页,有图书搜索、小游戏、音频播放和视频播放等主要功能,初期,我们并不知道这几个功能用户的使用规律。

有一天,产品经理说,我想要各个功能用户的使用规律,并且通过echarts绘制折线图和柱状图,能加吗?

这就加......

起初:

<button id="smallGameBtn">小游戏</button>
<script>
    var enterSmallGame = function () {
        console.log('进入小游戏')
    }
    document.getElementById('smallGameBtn').onclick = enterSmallGame;
</script>

通过装饰者模式增加数据埋点之后:

<button id="smallGameBtn">小游戏</button>
<script>
     Function.prototype.after= function (afterFn) {
        var selfFn = this;
        return function () {
            var ret = selfFn.apply(this, arguments)
            afterFn.apply(this.arguments)
            return ret
        }
    }
    var enterSmallGame = function () {
        console.log('进入小游戏')
    }
    var dataLog = function () {
        console.log('数据埋点')
    }
    enterSmallGame = enterSmallGame.after(dataLog)
    document.getElementById('smallGameBtn').onclick = enterSmallGame;
</script>

定义Function.prototype.after函数,其中通过闭包的方式缓存selfFn,然后返回一个函数,该函数首先执行selfFn,再执行afterFn,这里也很清晰的可以看出两个函数的执行顺序。

在当前例子中,首先执行进入小游戏的功能,然后,再执行数据埋点的功能。

可以看出,加了数据埋点,执行函数是enterSmallGame,不加也是。同时,也未对函数enterSmallGame内部进行修改。

2、表单校验

假如,我们开发了登录页面,有账号和密码输入框。

我们知道,校验是必须加的功能。

不加校验:

账号:<input id="account" type="text">
密码:<input id="password" type="password">
<button id="loginBtn">登录</button>
<script>
    var loginBtn = document.getElementById('loginBtn')
    var loginFn = function () {
        console.log('登录成功')
    }
    loginBtn.onclick = loginFn;
</script>

通过装饰者模式加校验之后:

账号:<input id="account" type="text">
密码:<input id="password" type="password">
<button id="loginBtn">登录</button>
<script>
    var loginBtn = document.getElementById('loginBtn')
    Function.prototype.before = function (beforeFn) {
        var selfFn = this;
        return function () {
            if (!beforeFn.apply(this, arguments)) {
                return;
            }
            return selfFn.apply(this, arguments)
        }
    }
    var loginFn = function () {
        console.log('登录成功')
    }
    var validateFn = function () {
        var account = document.getElementById('account').value;
        var password = document.getElementById('password').value;
        return account && password;
    }
    loginFn = loginFn.before(validateFn)
    loginBtn.onclick = loginFn;
</script>

定义Function.prototype.before函数,其中通过闭包的方式缓存selfFn,然后返回一个函数,该函数首先执行beforeFn,当其返回true时,再执行afterFn

在当前例子中,首先执行表单校验的功能,然后,提示登录成功,进入系统。

可以看出,加了表单校验,执行函数是loginFn,不加也是。同时,也未对函数loginFn内部进行修改。

二、框架功能(vue)

1、数组监听

vue的特点之一就是数据响应式,数据的变化会改变视图的变化。但是,数组中通过下标索引的方式直接修改不会引起视图变化,通过pushpopshiftunshiftsplice等方式修改数据就可以。

这里我们只看响应式处理数据的核心逻辑Observer

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data
  constructor (value: any) {
    if (Array.isArray(value)) {
       protoAugment(value, arrayMethods)
    }
  }
}

如果需要响应式处理的数据满足Array.isArray(value),则可通过protoAugment对数据进行处理。

function protoAugment (target, src: Object) {
  target.__proto__ = src
}

这里修改目标的__proto__ 指向为srcprotoAugment(value, arrayMethods)执行的含义就是修改数组的原型指向为arrayMethods

import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

通过const arrayProto = Array.prototype的方式缓存Array的原型,通过const arrayMethods = Object.create(arrayProto)原型继承的方式让arrayMethods上继承了Array原型上的所有方法。这里有定义了包含pushsplice等方法的数组methodsToPatch,循环遍历methodsToPatch数组并执行def方法:

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

这里的目的是当访问到methodsToPatch中的方法method的时候,先const result = original.apply(this, args)执行的原始方法,获取到方法的执行结果result。然后通过switch (method) 的方式针对不同的方法进行参数的处理,手动响应式的处理,并且进行视图重新渲染的通知ob.dep.notify()

整个过程可以看出,是对数组的原型进行了装饰者模式的处理。目的是,针对pushpopshiftunshiftsplice等方法进行装饰,当通过这些方法进行数组数据的修改时,在执行本体函数arrayProto[method]的同时,还执行了手动响应式处理和视图更新通知的操作。

2、重写挂载

vue还有特点是支持跨平台,不仅可以使用在web平台运行,也可以使用在weex平台运行。

首先定义了公共的挂载方法:

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

通过装饰者模式处理平台相关的节点挂载和模板编译:

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }
  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  // 这里执行缓存的与平台无关的mount方法
  return mount.call(this, el, hydrating)
}

可以看出,在web平台中先缓存公共的挂载方法。当其处理完平台相关的挂载节点和模板编译等操作后,再去执行与平台无关的挂载方法。

总结

装饰者模式,是一种可以首先定义本体函数进行执行。然后,在需要进行功能添加的时候,重新定义一个用来装饰的函数。

装饰的函数可以在本体函数之前执行,也可以在本体函数之后执行,在某一天不需要装饰函数的时候,也可以只执行本体函数。因为本体函数和通过装饰者模式改造的函数有着相同的接口,而且也可以不必去熟悉本体函数内部的实现。

以上就是装饰者模式在日常开发中的缩影和vue中的使用详解的详细内容,更多关于vue 使用装饰者模式的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue2从数据变化到视图变化发布订阅模式详解

    目录 引言 一.发布订阅者模式的特点 二.vue中的发布订阅者模式 1.dep 2.Object.defineProperty 3.watcher 4.dep.depend 5.dep.notify 6.订阅者取消订阅 小结 引言 发布订阅者模式是最常见的模式之一,它是一种一对多的对应关系,当一个对象发生变化时会通知依赖他的对象,接受到通知的对象会根据情况执行自己的行为. 假设有财经报纸送报员financialDep,有报纸阅读爱好者a,b,c,那么a,b,c想订报纸就告诉financialDe

  • 代理模式在vue中的使用示例解析

    目录 引言 1.图片预加载 2.缓存代理 3.跨域代理 总结 引言 当本体处于保护.缓存.虚拟或者过滤等情况下时,一个数据不适合被访问或者一个方法不能被直接调用,可以采用代理模式,先创建一个代理(本体对象或者方法的替身),作为访问者和本体之间的中介或者桥梁. 本体访问和代理访问的区别 不使用代理:访问 ==> 本体 使用代理:访问 ==> 代理 ==> 本体 1.图片预加载 // 本体 var myImage = (function () { var imgNode = document

  • vue路由history模式页面刷新404解决方法Koa Express

    目录 为什页面刷新会出现404 Node服务使用Koa框架 Node服务使用Express框架 为什页面刷新会出现404 因为vue项目中路由hash模式改为了history模式,由于hash模式时url带的#号后面是哈希值不会作为url的一部分发送给服务器,而history模式下当刷新页面之后浏览器会直接去请求服务器,而服务器没有这个路由,于是就出现404. 那为什么页面跳转就是正常的?跳转时其实不是通过请求服务器的,而是通过js操作history的API改变地址完成的. 建议:非C端系统可以

  • Vue uni-app以H5模式引入Jquery配置教程

    目录 Vue配置Jquery uni-app配置Jquery 总结 Vue配置Jquery 安装Jquery npm install jquery --save or yarn add jquery main.js中引入jquery,供全局使用 import Vue from 'vue' import jquery from "jquery"; Vue.prototype.$ = jquery; 在页面中使用,运行如下代码,在控制台就可以查看引入结果 <template>

  • vue history模式刷新404原因及解决方法

    目录 项目场景: 问题描述 原因分析: 第一步 第二步 总结 项目场景: 提示:这里简述项目相关背景: vue项目路由history模式 问题描述 提示:这里描述项目中遇到的问题: vue history模式刷新404原因 原因分析: 因为在history模式下,只是动态的通过js操作window.history来改变浏览器地址栏里的路径,并没有发起http请求,但是当我直接在浏览器里输入这个地址的时候,就一定要对服务器发起http请求,但是这个目标在服务器上又不存在,所以会返回404 解决方案

  • vue3如何使用eventBus订阅发布模式

    目录 Ⅰ. 什么是eventBus? Ⅱ. vue3 如何使用 步骤一 (eventBus 容器) 步骤二 ( 订阅者 ) 步骤三 ( 发布者 ) 总结 Ⅰ. 什么是eventBus? 通俗的讲,就是在任意一个组件,想把消息(参数) -> 传递到任意一个组件 ,并执行一定逻辑. Ⅱ. vue3 如何使用 eventBus vue3中没有了,eventBus,所以我们要自己写,但是非常简单. 步骤一 (eventBus 容器) 在src目录,创建个bus文件夹,存放 自己写的 bus.js : 编

  • iOS开发中以application/json上传文件实例详解

    本文通过实例代码给大家讲解iOS中以application/json上传文件的形式,具体内容详情大家参考下本文. 在和sever后台交互的过程中.有时候.他们需要我们iOS开发者以"application/json"形式上传. NSString *accessUrl = [NSString stringWithFormat:@"%@/xxx",@"https://www.xxxxx.com:xxxx"]; NSMutableURLRequest

  • Rust开发WebAssembly在Html和Vue中的应用小结(推荐)

    目录 我最大的感受 Rust在web上的应用 本文主题 应用工具:HBuilder.CLion 1.创建一个wasm 2.在Html中的应用 4.最近遇到的问题 最初是由Mozilla研究院的Graydon Hoare设计创造,然后在Dave Herman, Brendan Eich以及很多其他人的贡献下逐步完善的.Rust的设计者们通过在研发Servo网站浏览器布局引擎过程中积累的经验优化了Rust语言和Rust编译器. 我最大的感受 开始我是抵触它的,原因很简单,它太难学了!!!好害怕语法上

  • Java中的引用和动态代理的实现详解

    我们知道,动态代理(这里指JDK的动态代理)与静态代理的区别在于,其真实的代理类是动态生成的.但具体是怎么生成,生成的代理类包含了哪些内容,以什么形式存在,它为什么一定要以接口为基础? 如果去看动态代理的源代码(java.lang.reflect.Proxy),会发现其原理很简单(真正二进制类文件的生成是在本地方法中完成,源代码中没有),但其中用到了一个缓冲类java.lang.reflect.WeakCache<ClassLoader,Class<?>[],Class<?>

  • java中常见的6种线程池示例详解

    之前我们介绍了线程池的四种拒绝策略,了解了线程池参数的含义,那么今天我们来聊聊Java 中常见的几种线程池,以及在jdk7 加入的 ForkJoin 新型线程池 首先我们列出Java 中的六种线程池如下 线程池名称 描述 FixedThreadPool 核心线程数与最大线程数相同 SingleThreadExecutor 一个线程的线程池 CachedThreadPool 核心线程为0,最大线程数为Integer. MAX_VALUE ScheduledThreadPool 指定核心线程数的定时

  • java开发Dubbo负载均衡与集群容错示例详解

    目录 负载均衡与集群容错 Invoker 服务目录 RegistryDirectory 获取Invoker列表 监听注册中心 刷新Invoker列表 StaticDirectory 服务路由 Cluster FailoverClusterInvoker FailfastClusterInvoker FailsafeClusterInvoker FailbackClusterInvoker ForkingClusterInvoker BroadcastClusterInvoker Abstract

  • Java开发学习之Bean的作用域和生命周期详解

    目录 一.Bean 的作用域 二.Spring 的执行流程 三.Bean 的生命周期 一.Bean 的作用域 在之前学习Java基础的时候,有接触到作用域这样的概念.一个变量并不一定在任何区域都是有效的,限定这个变量的可用性的代码范围就是该变量的作用域. 但是在这里 Bean 的作用域的概念和以前所认为的作用域有所不同. Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式. 接下来,将会举一个案例来讲讲什么是作用域,什么是行为模式 案例概要: 创建一个共有的 Bean

  • .NET 中配置从xml转向json方法示例详解

    目录 一.配置概述 二.配置初识 三.选项模式 四.选项依赖注入 五.其它配置 六.托管模式 一.配置概述 在.net framework平台中我们常见的也是最熟悉的就是.config文件作为配置,控制台桌面程序是App.config,Web就是web.config,里面的配置格式为xml格式. 在xml里面有系统生成的配置项,也有我们自己添加的一些配置,最常用的就是appSettings节点,用来配置数据库连接和参数. 使用的话就引用包System.Configuration.Configur

  • MySQL中增删改查操作与常见陷阱详解

    目录 本文导读 一.MySQL的增删改查 1.insert语句 2.delete语句 3.update语句原理 4.select 二.15种MySQL数据操作语句 1.REPLACE语句 2.CALL语句 3.TABLE语句 4.WITH语句 三.MySQL查询陷阱 总结 本文导读 本文作为MySQL系列第二篇文章,详细讲解了MySQL的增删改查的语句.语义和一些我们经常在开发工作中暴露的问题,MySQL的增删改查又叫数据操作语句,本文有讲些了一些常用的数据操作语句,select语句后续将作为一

  • Struts中的Action 单例与多例详解

     Struts中的Action 单例与多例详解 struts2中action是多例的,即每次访问网络地址的时候都会产生一个action public class pr_action { public pr_action(){ System.out.println("创建action成功!!!"); } public void execute(){ } } 运行代码可以看到,每次访问该网络地址都会在控制台输出一次!!! 如果是单例的话,若出现两个用户都修改一个对象的属性值,则会因为用户修

  • iOS中关于信鸽推送的使用demo详解

    最近在看推送方面的知识,用的是信鸽推送主要是因为后台用的是信鸽 推送用第三方推送,也就是在客户端建一个广播接收器,当服务器发送消息时发送到信鸽,信鸽再发送一次,广播接受器接受下: 我实现的功能比较简单,当app在运行状态时,会在主页展示一个弹窗展示推送的消息:如果app不在运行状态且service没被销毁就展示默认的通知 那么如何在主页展示弹窗:当广播接受器收到我要的消息时,用观察者模式,收到消息在发送个消息个主界面 官方的Demo连接:http://xg.qq.com/xg/help/ctr_

随机推荐