详解在Javascript中进行面向切面编程


Posted in Javascript onApril 28, 2019

面向切面编程(Aspect-oriented programming,AOP)是一种编程范式。做后端 Java web 的同学,特别是用过 Spring 的同学肯定对它非常熟悉。AOP 是 Spring 框架里面其中一个重要概念。可是在 Javascript 中,AOP 是一个经常被忽视的技术点。

场景

假设你现在有一个牛逼的日历弹窗,有一天,老板让你统计一下每天这个弹窗里面某个按钮的点击数,于是你在弹窗里做了埋点;

过了一个星期,老板说用户反馈这个弹窗好慢,各种卡顿。你想看一下某个函数的平均执行时间,于是你又在弹窗里加上了性能统计代码。

时间久了,你会发现你的业务逻辑里包含了大量的和业务无关的东西,即使是一些你已经封装过的函数。
那么 AOP 就是为了解决这类问题而存在的。

关注点分离

分离业务代码和数据统计代码(非业务代码),无论在什么语言中,都是AOP的经典应用之一。从核心关注点中分离出横切关注点,是 AOP 的核心概念。
在前端的常见需求中,有以下一些业务可以使用 AOP 将其从核心关注点中分离出来

  1. Node.js 日志log
  2. 埋点、数据上报
  3. 性能分析、统计函数执行时间
  4. 给ajax请求动态添加参数、动态改变函数参数
  5. 分离表单请求和验证
  6. 防抖与节流

 装饰器(Decorator)

提到 AOP 就要说到装饰器模式,AOP 经常会和装饰器模式混为一谈。

在ES6之前,要使用装饰器模式,通常通过Function.prototype.before做前置装饰,和Function.prototype.after做后置装饰(见《Javascript设计模式和开发实践》)。

Javascript 引入的 Decorator ,和 Java 的注解在语法上很类似,不过在语义上没有一丁点关系。Decorator 提案提供了对 Javascript 的类和类里的方法进行装饰的能力。(尽管只是在编译时运行的函数语法糖)

埋点数据上报

因为在使用 React 的实际开发中有大量基于 Class 的 Component,所以我这里用 React 来举例。
比如现在页面中有一个button,点击这个button会弹出一个弹窗,与此同时要进行数据上报,来统计有多少用户点击了这个登录button。

import React, { Component } from 'react';
import send from './send';

class Dialog extends Component {

  constructor(props) {
    super(props);
  }

  @send
  showDialog(content) {
    // do things
  }

  render() {
    return (
      <button onClick={() => this.showDialog('show dialog')}>showDialog</button>
    )
  }
}

export default Dialog;

上面代码引用了@send装饰器,他会修改这个 Class 上的原型方法,下面是@send装饰器的实现

export default function send(target, name, descriptor) {
  let oldValue = descriptor.value;

  descriptor.value = function () {
    console.log(`before calling ${name} with`, arguments);
    return oldValue.apply(this, arguments);
  };

  return descriptor;
}

在按钮点击后执行showDialog前,可以执行我们想要的切面操作,我们可以将埋点,数据上报相关代码封装在这个装饰器里面来实现 AOP。

前置装饰和后置装饰

上面的send这个装饰器其实是一个前置装饰器,我们可以将它再封装一下使它可以前置执行任意函数。

function before(beforeFn = function () { }) {
  return function (target, name, descriptor) {
    let oldValue = descriptor.value;

    descriptor.value = function () {
      beforeFn.apply(this, arguments);
      return oldValue.apply(this, arguments);
    };

    return descriptor;
  }
}

这样我们就可以使用@before装饰器在一个原型方法前切入任意的非业务代码。

function beforeLog() {
  console.log(`before calling ${name} with`, arguments);
}
class Dialog {
  ...
  @before(beforeLog)
  showDialog(content) {
    // do things
  }
  ...
}

和@before装饰器类似,可以实现一个@after后置装饰器,只是函数的执行顺序不一样。

function after(afterFn = function () { }) {
  return function (target, name, descriptor) {
    let oldValue = descriptor.value;

    descriptor.value = function () {
      let ret = oldValue.apply(this, arguments);
      afterFn.apply(this, arguments);
      return ret;
    };

    return descriptor;
  }
}

性能分析

有时候我们想统计一段代码在用户侧的执行时间,但是又不想将打点代码嵌入到业务代码中,同样可以利用装饰器来做 AOP。

function measure(target, name, descriptor) {
  let oldValue = descriptor.value;

  descriptor.value = function () {
    let ret = oldValue.apply(this, arguments);
    performance.mark("startWork");
    afterFn.apply(this, arguments);
    performance.mark("endWork");
    performance.measure("work", "startWork", "endWork");
    performance
     .getEntries()
     .map(entry => JSON.stringify(entry, null, 2))
     .forEach(json => console.log(json));
    return ret;
  };

  return descriptor;
}

在要统计执行时间的类方法前面加上@measure就行了,这样做性能统计的代码就不会侵入到业务代码中。

class Dialog {
  ...
  @measure
  showDialog(content) {
    // do things
  }
  ...
}

小结

面向切面编程的重点就是将核心关注面分离出横切关注面,前端可以用 AOP 优雅的来组织数据上报、性能分析、统计函数的执行时间、动态改变函数参数、插件式的表单验证等代码。

以上所述是小编给大家介绍的Javascript面向切面编程详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
基于jquery的跨域调用文件
Nov 19 Javascript
Jquery Uploadify多文件上传带进度条且传递自己的参数
Aug 28 Javascript
jquery中radio checked问题
Mar 16 Javascript
JS实现页面进入和返回定位到具体位置
Dec 08 Javascript
JS库之Three.js 简易入门教程(详解之一)
Sep 13 Javascript
JS+H5 Canvas实现时钟效果
Jul 20 Javascript
Vue.js实现表格渲染的方法
Sep 07 Javascript
JS获取月的第几周和年的第几周实例代码
Dec 05 Javascript
vue路由中前进后退的一些事儿
May 18 Javascript
在Vue中获取自定义属性方法:data-id的实例
Sep 09 Javascript
Vue页面跳转传递参数及接收方式
Sep 09 Javascript
微信小程序实现日历签到
Sep 21 Javascript
js比较两个单独的数组或对象是否相等的实例代码
Apr 28 #Javascript
详解在HTTPS 项目中使用百度地图 API
Apr 26 #Javascript
vue操作动画的记录animate.css实例代码
Apr 26 #Javascript
JS原生瀑布流效果实现
Apr 26 #Javascript
Vue基于vuex、axios拦截器实现loading效果及axios的安装配置
Apr 26 #Javascript
详解auto-vue-file:一个自动创建vue组件的包
Apr 26 #Javascript
vue组件间的参数传递实例详解
Apr 26 #Javascript
You might like
php中常用的预定义变量小结
2012/05/09 PHP
php常见的魔术方法详解
2014/12/25 PHP
总结PHP代码规范、流程规范、git规范
2018/06/18 PHP
一个刚完成的layout(拖动流畅,不受iframe影响)
2007/08/17 Javascript
jquery.tmpl JQuery模板插件
2011/10/10 Javascript
JavaScript获取元素尺寸和大小操作总结
2015/02/27 Javascript
JavaScript知识点总结(四)之逻辑OR运算符详解
2016/05/31 Javascript
JavaScript学习笔记整理_简单实现枚举类型,扑克牌应用
2016/09/19 Javascript
详解Angualr 组件间通信
2017/01/21 Javascript
JavaScript箭头(arrow)函数详解
2017/06/04 Javascript
js 事件的传播机制(实例讲解)
2017/07/20 Javascript
Javascript实现异步编程的过程
2018/06/18 Javascript
详解微信小程序canvas圆角矩形的绘制的方法
2018/08/22 Javascript
Vuex 快速入门(简单易懂)
2018/09/20 Javascript
微信小程序控制台提示warning:Now you can provide attr &quot;wx:key&quot; for a &quot;wx:for&quot; to improve performance解决方法
2019/02/21 Javascript
vue axios封装及API统一管理的方法
2019/04/18 Javascript
Vue 权限控制的两种方法(路由验证)
2019/08/16 Javascript
分享Angular http interceptors 拦截器使用(推荐)
2019/11/10 Javascript
js实现类选择器和name属性选择器的示例步骤
2021/02/07 Javascript
[02:40]DOTA2超级联赛专访430 从小就爱玩对抗性游戏
2013/06/18 DOTA
[46:47]完美世界DOTA2联赛PWL S2 FTD vs Magma 第二场 11.20
2020/11/23 DOTA
从零学python系列之新版本导入httplib模块报ImportError解决方案
2014/05/23 Python
python opencv3实现人脸识别(windows)
2018/05/25 Python
Python使用wget实现下载网络文件功能示例
2018/05/31 Python
Django 路由系统URLconf的使用
2018/10/11 Python
PySide2出现“ImportError: DLL load failed: 找不到指定的模块”的问题及解决方法
2020/06/10 Python
Pandas缺失值2种处理方式代码实例
2020/06/13 Python
Django web自定义通用权限控制实现方法
2020/11/24 Python
CSS3 简写animation
2012/05/10 HTML / CSS
Douglas意大利官网:购买香水和化妆品
2020/05/27 全球购物
高中毕业生自我鉴定
2013/11/03 职场文书
工业学校毕业生自荐书
2014/01/03 职场文书
教师岗位聘任书范文
2014/03/29 职场文书
2014年母亲节寄语
2014/05/07 职场文书
利用Selenium添加cookie实现自动登录的示例代码(fofa)
2021/05/08 Python
Python中npy和mat文件的保存与读取
2022/04/24 Python