欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-10-30 14:35:16
面向切面编程(aspect-oriented programming,aop)是一种编程范式。做后端 java web 的同学,特别是用过 spring 的同学肯定对它非...

面向切面编程(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面向切面编程详解整合,希望对大家有所帮助