zoukankan      html  css  js  c++  java
  • 前端装饰器模式快闪

    狸猫换太子

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

    装饰器模式

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

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

    最简单的实现

    现在我们先来个最简单的实现。假设有如下一个场景,定义一个启动函数,执行就会打印"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 啊,有洋葱模型呀。洋葱模型就是一个切面。

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

    http://www.ssnd.com.cn 化妆品OEM代加工

    源码级别的实现

    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 。 完成配置后,可以将项目中无关业务的功能尝试使用装饰器的方式实现,如登陆条件判断,防抖,节流,埋点统计等。

  • 相关阅读:
    解决UITableView中Cell重用机制导致内容出错的方法总结
    Hdu 1052 Tian Ji -- The Horse Racing
    Hdu 1009 FatMouse' Trade
    hdu 2037 今年暑假不AC
    hdu 1559 最大子矩阵
    hdu 1004 Let the Balloon Rise
    Hdu 1214 圆桌会议
    Hdu 1081 To The Max
    Hdu 2845 Beans
    Hdu 2955 Robberies 0/1背包
  • 原文地址:https://www.cnblogs.com/xiaonian8/p/15003723.html
Copyright © 2011-2022 走看看