关闭

前端装饰器模式快闪

时间: 2020-09-23阅读: 88标签: 模式

狸猫换太子

北宋真宗时,有李妃和刘妃两位小主都有了龙种,谁生了儿子,谁就能豪横。谁曾想刘妃不是个善茬,把剥了皮的狸猫换了李妃刚生完的孩子。天可怜见,执行任务的宫女人性未泯,将孩子送往八贤王处抚养,这才引出后来,包拯陈州放粮救李妃,仁宗认母家团圆。那位说了,好好的谈装饰器模式这怎么说上故事了。各位看官不要急,这前端装饰器模式的实现核心其实就在这五个字上,天空飘来五个字,那都不是事,串了串了……核心其实就在这五个字上——狸猫换太子。


装饰器模式

咱们闲言少叙,言归正传。装饰器模式是设计模式的一种,是为已有功能动态的添加更多功能的一种方式。重点体现了设计模式六大原则之中的单一职责原则和开闭原则。单一职责很好理解,就是专心,就是一个函数只做一件事情。开闭原则是指要对扩展开放,对修改关闭。

说人话,就是在原有功能不变的前提下要加功能、加需求,不是去动写好的函数,而是想办法扩展。这个想出来的办法就是装饰器模式。


最简单的实现

现在我们先来个最简单的实现。假设有如下一个场景,定义一个启动函数,执行就会打印"start"。

let start = function () {
    console.log('start')
}
start();

好了,现在产品来了,他说启动之前,需要加入一段自检功能。 最快的方法当然就是在 start 函数里添加一段检查代码。但是这就违反了设计模式的单一职责原则和开闭原则。这时候就是装饰器模式大展身手的好时候了。

let start = function () {
    console.log('start')
}
const _start = start;
start = function(){
    console.log('check');
    _start()
}
start();

对于原函数,我们没有做任何的修改,因此不必担心原有功能受到影响。首先将原函数对象地址赋值给新的变量,然后重写原函数变量。重写的函数首先加入自检功能,然后执行上面备份到新变量的原函数。这样就实现了一个最简单的装饰器模式。你品,你细品,有没有狸猫换太子的味道。


装饰器模式与代理模式

我想到了这里,你应该对于装饰器模式有所掌握了。所以我们趁热打铁,来认识一下装饰器模式的兄弟代理模式。代理模式也是经典设计模式的一种,和装饰器模式的共同点是都不改变原有功能。代理模式决定了能不能访问到原有功能,是一种控制。装饰器模式决定了你能访问到的是加了其他哪些功能的原有功能。如果非要类比的话,那么代理有点像防火墙,装饰器有点像你买的快递的各种快递外包装。

在 ES6 中提供了一个新的标准内置对象 Proxy ,就是代理模式的实现。你可能已经知道了在 vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。


语法

let p = new Proxy(target, handler);

target 是用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 handler 是一个对象,其属性是当执行一个操作时定义代理的行为的函数。 Proxy 支持 13 种拦截操作,几乎涵盖了对象函数操作的方方面面,完全够开一个新的专题,所以这里就先不做扩展。


装饰器模式与面向切面编程

面向切面编程和面向对象编程一样,是一种编程的思想,是对面向对象编程的一种补充。在后端开发使用的 Java 框架 spring 的特性中有一项特性就是 AOP,面向切面编程。所以到底什么是切面,如果是后端开发理解,还需要引入好多其他术语概念,但是我们前端有 koa 啊,有洋葱模型呀。洋葱模型就是一个切面。


我的函数方法就是洋葱的核心,我想在方法调用前后做一些事情,比如打日志、算时间等等,这时候就将我们的函数方法包裹起来,再加一层,这就是面向切面编程。思考的是程序执行的位置。 面向对象编程可以让我们方便抽象和管理我们的代码,而面向切面编程的重点在于解耦和复用。而装饰器就是实现面向切面编程的一种方式。


源码级别的实现

function.before 和 function.after

webpack 和 babel 还没有大行其道之前,比较经典的 AOP 实现方式就是 function.before 和 function.after,同样我们也可以用他们来实现装饰器模式。

Function.prototype.before = function(fn){
    var _this = this;       // 用来保存调用这个函数的引用,如func_1调用此函数,则_this指向func_1
    return function(){      // 返回一个函数,这个函数包含原函数和新函数,原函数指的是func_1,新函数指的是fn
        fn.apply(this,arguments);   // 执行新函数
        return _this.apply(this,arguments);     // 执行原函数
    }
}

Function.prototype.after = function(fn){
    var _this = this;
    return function(){
        var r = _this.apply(this,arguments); // 先执行原函数,也就是func_1
        fn.apply(this,arguments);   // 再执行新函数
        return r;
    }
}


var func_1 = function () {
   console.log("2")
}

func_1 = func_1.before(function () {
   console.log("1");
}).after(function () {
   console.log("3");
} )

func_1();   // 输出1、2、3

Object.defineProperty()

后来,我们有了 webpack 和 babel 。 Object.defineProperty() 的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性。

/**
* param obj 当前操作对象
* param prop 需要定义的对象属性名称
* param desc 属性描述符
*/
Object.defineProperty(obj, prop, desc)

属性描述符包括数据描述符: value,writable ;存取描述符: get,set ;描述符: configurable,enumerable ; 这个方法可不得了,存取描述符 get,set 是 vue 框架的基础,而数据描述符就是 Babel 实现装饰器的基础。

Babel 将我们的@函数名最终转换为

Object["define" + "Property"](target, property, desc);

由此可见,装饰方法本质上还是使用 Object.defineProperty() 来实现的。

function before(target, key, descriptor) {
  const fn = descriptor.value;
  return {
    ...descriptor,
    value() {
      console.log('before')
      return fn.apply(this, arguments);
    }
  }
}
function after(target, key, descriptor) {
  const fn = descriptor.value;
  return {
    ...descriptor,
    value() {
      let result = fn.apply(this, arguments);
      console.log('after');
      return result;
    }
  }
}
class Test {
  @after
  @before
  func(){
    console.log('func')
  }
}
const test = new Test();
test.func();

经过 Babel 编译后运行,可以输出: before func after


工程化的实现

此处以 webpack 打包项目为例,如果在一个 webpack 打包项目中你还没有使用装饰器,那么你的项目在开发效率和代码重构上就还有提升空间。 如果你使用 babel6 ,安装并配置 babel 插件 babel-plugin-transform-decorators-legacy ,如果使用 babel7 ,安装并配置官方插件 @babel/plugin-proposal-decorators 。 完成配置后,可以将项目中无关业务的功能尝试使用装饰器的方式实现,如登陆条件判断,防抖,节流,埋点统计等。


优秀装饰器的样子

core-decorators 是一个装饰器第三方模块,提供了一些常见的装饰器,通过学习它,我们可以更好地理解装饰器。它的 github 地址是 https://github.com/jayphelps/core-decorators ,目前已经有4.2K的赞。

原文 https://zhuanlan.zhihu.com/p/264364114


站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9710

关闭

vue-router的hash 模式和 history 模式

url 中带有#的便是 hash 模式,#后面是 hash 值,它的变化会触发 hashchange 这个事件。通过这个事件我们就可以知道 hash 值发生了哪些变化。

requirejs中的define

我们已经了解到模块模式是为单例创建私有变量和特权方法的,我们将模块函数转换成了立即执行函数,立即调用这个函数并将返回值直接赋值给单例的模块实例标识符。

TypeScript 设计模式之单例模式

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。单例模式用于保证一个类仅有一个实例,并提供一个访问它的全局访问点。

JS单例模式

限制类的实例化次数只能是一次。如果该实例不存在的情况下,可以通过一个方法创建一个类来实现创建类的新实例,如果实例已经存在,它会简单返回该对象的引用。

js中的策略模式

什么是策略模式,官方定义是:定义一些列算法,把他们封装起来,并且可以相互替换。说人话(⊙ˍ⊙):就是把看似毫无联系的代码提取封装、复用,使之更容易被理解和拓展。常见的用于一次if判断、switch枚举、数据字典等流程判断语句中

设计模式有六大设计原则

即使抛开所有设计模式,能按照上述六大原则进行代码设计开发,代码质量就能得到很好的保证。所有的设计模式不见得一次性都能记住,不用则不熟。但如果能透传理解上面的原则,可能实际写代码中会不自觉就把某一个模式给实现出来了。

浅谈js抽象工厂模式

简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。 比如你去专门卖鼠标的地方你可以买各种各样的鼠标

MVC、MVP 和 MVVM的对比

三个非常重要的架构模式:MVC (Model(模型)-View(视图)-Controller(控制器)),MVP (Model(模型)-View(视图)-Presenter(中介者)),MVVM (Model(模型)-View(视图)-ViewModel(视图模型))

发布订阅和观察者模式的区别

有些人认为观察者模式就是发布订阅模式,实际上观察者模式是包含了订阅发布模式,发布订阅模式只是观察者模式中的一种。观察者模式是观察者和被观察者之间的通信,而发布订阅模式中间增加了一个中转层

js设计模式:观察者和发布订阅模式

总是把这两个当作同一个模式,但其实是不太一样的,现在重温一下。观察者直接订阅目标,当目标触发事件时,通知观察者进行更新;发布订阅模式通过一个调度中心进行处理,使得订阅者和发布者分离开来

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!