详解在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 post方式传递多个参数值后台以数组的方式进行接收
Jan 11 Javascript
js根据给定的日期计算当月有多少天实现思路及代码
Feb 25 Javascript
javascript自然分类法算法实现代码
Oct 11 Javascript
Javascript前端UI框架Kit使用指南之kitjs的对话框组件
Nov 28 Javascript
JavaScript 学习笔记之变量及其作用域
Jan 14 Javascript
无需 Flash 使用 jQuery 复制文字到剪贴板
Apr 26 Javascript
vue.js学习之vue-cli定制脚手架详解
Jul 02 Javascript
jQuery制作全屏宽度固定高度轮播图(实例讲解)
Jul 08 jQuery
Gulp实现静态网页模块化的方法详解
Jan 09 Javascript
layer弹出层取消遮罩的方法
Sep 25 Javascript
mpvue网易云短信接口实现小程序短信登录的示例代码
Apr 03 Javascript
js实现网页随机验证码
Oct 19 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实现的css文件背景图片下载器代码
2014/11/11 PHP
PHP实现ASCII码与字符串相互转换的方法
2017/04/29 PHP
laravel-admin select框默认选中的方法
2019/10/03 PHP
统一接口:为FireFox添加IE的方法和属性的js代码
2007/03/25 Javascript
jQuery源码分析之Event事件分析
2010/06/07 Javascript
jquery为页面增加快捷键示例
2014/01/31 Javascript
jQuery制作拼图小游戏
2015/01/12 Javascript
Jquery实现动态切换图片的方法
2015/05/18 Javascript
BootStrap的弹出框(Popover)支持鼠标移到弹出层上弹窗层不隐藏的原因及解决办法
2016/04/03 Javascript
ichart.js绘制虚线、平均分虚线效果的实现代码
2016/05/05 Javascript
Three.js快速入门教程
2016/09/09 Javascript
vue之nextTick全面解析
2017/05/17 Javascript
webpack踩坑之路图片的路径与打包
2017/09/05 Javascript
原生JS 实现的input输入时表格过滤操作示例
2019/08/03 Javascript
JavaScript闭包相关知识解析
2019/10/19 Javascript
js键盘事件实现人物的行走
2020/01/17 Javascript
关于JavaScript中异步/等待的用法与理解
2020/11/18 Javascript
[04:11]DOTA2亚洲邀请赛小组赛第一日 TOP10精彩集锦
2015/01/30 DOTA
[42:32]VP vs RNG 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.21.mp4
2020/07/19 DOTA
[01:03:18]DOTA2-DPC中国联赛 正赛 RNG vs Dynasty BO3 第一场 1月29日
2021/03/11 DOTA
Python随机生成数模块random使用实例
2015/04/13 Python
Python实现求数列和的方法示例
2018/01/12 Python
Django 多语言教程的实现(i18n)
2018/07/07 Python
python实现名片管理系统
2018/11/29 Python
python使用opencv对图像mask处理的方法
2019/07/05 Python
Python3 io文本及原始流I/O工具用法详解
2020/03/23 Python
Python+Selenium随机生成手机验证码并检查页面上是否弹出重复手机号码提示框
2020/09/21 Python
Html5 postMessage实现跨域消息传递
2016/03/11 HTML / CSS
6PM官网:折扣鞋、服装及配饰
2018/08/03 全球购物
巴西最大的运动品牌:Olympikus
2020/07/14 全球购物
舞蹈比赛获奖感言
2014/02/04 职场文书
师德师风自我剖析材料
2014/09/27 职场文书
企业群众路线教育实践活动心得体会
2014/11/03 职场文书
基层工作经历证明
2015/06/19 职场文书
入门学习Go的基本语法
2021/07/07 Golang
openEuler 搭建java开发环境的详细过程
2022/06/10 Servers