Babel插件指南

news2024/9/23 1:27:59

Babel插件指南

文章目录

    • Babel插件指南
      • Babel简介
      • AST(Abstract syntax tree)简介
      • ESTree AST Node
      • Visitors(访问者)
      • Babel API
        • babylon
        • babel-traverse
        • babel generator
      • 项目中实践
        • 引用自定义的babel插件
        • 新增插件js文件
        • 确定要实现的功能,编译成AST进行分析
        • 编写和调试插件
        • 注意
      • 额外说明polyfill
      • 其他插件案例
      • 附录
      • 参考资料地址

Babel简介

Babel 是一个通用的多功能的 JavaScript 编译器。此外它还拥有众多模块可用于不同形式的静态分析。

静态分析是在不需要执行代码的前提下对代码进行分析的处理过程。静态分析的目的是多种多样的, 它可用于语法检查,编译,代码高亮,代码转换,优化,压缩等等场景。

  • 通过图进一步理解:

浏览器编译你的js代码,需要把js转化成ast。2015年es6语法发布,但浏览器还普遍不支持es6语法。于是催生出babel模块,默认支持js到ast的转化,并通过修改ast,将es6的特性代码转化为同效用的es5代码,同时也提供了ast的操作函数。

Babel操作流程:js代码 -> 原AST -> babel处理 -> 修改后的AST -> 修改后的js代码 -> 交给浏览器编译

即: 解析(parse)转换(transform)生成(generate)

实现上述功能的插件见小节:Babel API

Babel 更确切地说是源码到源码的编译器,通常也叫做“转换编译器(transpiler)”。 意思是说你为 Babel 提供一些 js代码,Babel 更改这些代码,然后返回给你新生成的js代码。

由于babel提供了操作AST的函数,所以开发者可以由此做各种各样的静态分析。

AST(Abstract syntax tree)简介

常见编译型语言(例如:Java)编译程序一般步骤分为:词法分析->语法分析->语义检查->代码优化和字节码生成。具体的编译流程如下图:

js的AST生成和java略有不同,java是走上图所有流程,通过本地jdk将**.java文件编译成.class后缀的文件,然后交给jvm(虚拟机)处理。js默认是通过浏览器**编译,流程到上图的语义检查器为止,生成最终的AST

  • AST解析示例

js代码

let a = 1;

解析后AST

{
  "type": "Program",
  "start": 0,
  "end": 10,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 10,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 4,
          "end": 9,
          "id": {
            "type": "Identifier",
            "start": 4,
            "end": 5,
            "name": "a"
          },
          "init": {
            "type": "Literal",
            "start": 8,
            "end": 9,
            "value": 1,
            "raw": "1"
          }
        }
      ],
      "kind": "let"
    }
  ],
  "sourceType": "module"
}

在线js转换到AST工具:https://astexplorer.net/#/

JS生态里,基于 AST 实现功能的工具有很多,babel只是其中一个工具。其他例如:

  • ESlint: 代码错误或风格的检查,发现一些潜在的错误
  • IDE 的错误提示、格式化、高亮、自动补全等
  • UglifyJS 压缩代码
  • 代码打包工具 webpack

ESTree AST Node

babel完整的流程,最后一步是将修改后的AST生成为js代码,即深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。

Visitors(访问者)

当谈及“进入”一个节点,实际上是说我们在访问它们, 之所以使用这样的术语是因为有一个**访问者模式(visitor)**的概念。

visitor是一个用于 AST 遍历的跨语言的模式。 简单的说visitor就是一个对象,定义了用于在一个树状结构中获取具体节点的方法。例如:

visitor: {
  // 变量声明函数
  VariableDeclarator(path, state) {
		// todo
  }
}

上面AST小节示例有 “type”: “VariableDeclarator” ,诸如此类的树节点在访问时,就会进入visitor对象声明的对应类型的成员方法。此时你访问到的不是节点本身,而是一个Path,所以可以追踪树的父节点等其它信息。

常用写法是取path.node,如上即取到type = VariableDeclarator 的对象

举个栗子:

// 设定一个访问者对象
const visitor = {
  Identifier(path) {
    console.log("Visiting: " + path.node.name);
  }
};
// 声明js代码
a + b + c;
// js编译成ast
{
  "type": "Program",
  "start": 0,
  "end": 10,
  "body": [
    {
      "type": "ExpressionStatement",
      "start": 0,
      "end": 10,
      "expression": {
        "type": "BinaryExpression",
        "start": 0,
        "end": 9,
        "left": {
          "type": "BinaryExpression",
          "start": 0,
          "end": 5,
          "left": {
            "type": "Identifier",
            "start": 0,
            "end": 1,
            "name": "a"
          },
          "operator": "+",
          "right": {
            "type": "Identifier",
            "start": 4,
            "end": 5,
            "name": "b"
          }
        },
        "operator": "+",
        "right": {
          "type": "Identifier",
          "start": 8,
          "end": 9,
          "name": "c"
        }
      }
    }
  ],
  "sourceType": "module"
}
// 运行后打印结果:
Visiting: a
Visiting: b
Visiting: c

js声明后被编译成AST,AST被访问时,Identifier类型有3次,执行visitor对象的Identifier成员方法3次,打印对应日志

Babel API

babylon

Babylon 是 Babel 的解析器,作用是把js字符串解析成AST

$ npm install --save babylon
import * as babylon from "babylon";
const code = `function square(n) {
  return n * n;
}`;
babylon.parse(code);
// Node {
//   type: "File",
//   start: 0,
//   end: 38,
//   loc: SourceLocation {...},
//   program: Node {...},
//   comments: [],
//   tokens: [...]
// }

babel-traverse

Babel Traverse(遍历)模块维护了整棵树的状态,并且负责替换、移除和添加节点。就是触发访问者(Visitors)的步骤

$ npm install --save babel-traverse

遍历更新节点:

// 把变量 n 改成 x
import * as babylon from "babylon";
import traverse from "babel-traverse";
const code = `function square(n) {
  return n * n;
}`;
const ast = babylon.parse(code);
traverse(ast, {
  enter(path) {
    if (
      path.node.type === "Identifier" &&
      path.node.name === "n"
    ) {
      path.node.name = "x";
    }
  }
});

babel generator

Babel Generator模块是 Babel 的代码生成器,它读取AST并将其转换为代码和源码映射(sourcemaps)

$ npm install --save babel-generator

通过visitors处理后的AST再转成js

import * as babylon from "babylon";
import generate from "babel-generator";
import traverse from "babel-traverse";
const code = `function square(n) {
  return n * n;
}`;
const ast = babylon.parse(code);
traverse(ast, {
  enter(path) {
    if (
      path.node.type === "Identifier" &&
      path.node.name === "n"
    ) {
      path.node.name = "x";
    }
  }
});
generate(ast, {}, code);
// {
//   code: "...",
//   map: "..."
// }

项目中实践

引用自定义的babel插件

babel-core版本是6.x的,vue项目有.babelrc文件,在plugins属性里直接配置路径即可,如下babelPluginConsole:

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime", "./src/plugins/babel/babelPluginConsole"]
}

其他版本的babel也一个思路,找到plugins配置位置,对应加入即可。

新增插件js文件

如上配置,我们在 /src/plugins/babel/ 路径下新建 babelPluginConsole.js

module.exports = function(babel) {
  let t = babel.types;
  return {
    visitor: {
  		// todo      
    }
  };
};

确定要实现的功能,编译成AST进行分析

这里我们对console.log()进行处理,默认打印变量名,如下:

let test = 'hi babel'
console.log(test);
//打印: test hi babel

所以我们将console.log(test)console.log('test',test); 解析成AST进行比较

{
  "type": "Program",
  "start": 0,
  "end": 67,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 21,
      "declarations": [
       // 变量定义的AST,忽略
      ],
      "kind": "let"
    },
    // console.log(test) 的AST
    {
      "type": "ExpressionStatement",
      "start": 22,
      "end": 40,
      "expression": {
        "type": "CallExpression",
        "start": 22,
        "end": 39,
        "callee": {
          "type": "MemberExpression",
          "start": 22,
          "end": 33,
          "object": {
            "type": "Identifier",
            "start": 22,
            "end": 29,
            "name": "console"
          },
          "property": {
            "type": "Identifier",
            "start": 30,
            "end": 33,
            "name": "log"
          },
          "computed": false,
          "optional": false
        },
        "arguments": [
          {
            "type": "Identifier",
            "start": 34,
            "end": 38,
            "name": "test"
          }
        ],
        "optional": false
      }
    },
    // console.log('test', test) 的AST
    {
      "type": "ExpressionStatement",
      "start": 41,
      "end": 67,
      "expression": {
        "type": "CallExpression",
        "start": 41,
        "end": 66,
        "callee": {
          "type": "MemberExpression",
          "start": 41,
          "end": 52,
          "object": {
            "type": "Identifier",
            "start": 41,
            "end": 48,
            "name": "console"
          },
          "property": {
            "type": "Identifier",
            "start": 49,
            "end": 52,
            "name": "log"
          },
          "computed": false,
          "optional": false
        },
        "arguments": [
          {
            "type": "Literal",
            "start": 53,
            "end": 59,
            "value": "test",
            "raw": "'test'"
          },
          {
            "type": "Identifier",
            "start": 61,
            "end": 65,
            "name": "test"
          }
        ],
        "optional": false
      }
    }
  ],
  "sourceType": "module"
}

比较后发现,arguments属性多了一个字符串对象。所以我们要做的就是,获取console.log(test)的AST然后定位到arguments数组,给它添加一个对象。

编写和调试插件

按上述分析,得到如下一种解决逻辑

module.exports = function(babel) {
  let t = babel.types;
  return {
    visitor: {
      ExpressionStatement(path) {
        if (path.node && path.node.expression && path.node.expression.callee
            && path.node.expression.callee.object && path.node.expression.callee.property
            && path.node.expression.arguments 
            && path.node.expression.callee.object.name === 'console' 
            && path.node.expression.callee.property.name === 'log') {
          if (path.node.expression.arguments[0].type === 'Identifier') {
            path.node.expression.arguments = [t.stringLiteral(path.node.expression.arguments[0].name), ...path.node.expression.arguments]
            console.log('path.node.expression.arguments2', path.node.expression.arguments);
          }
        }
      },      
    }
  };
};

注意

  • 插件实现的逻辑非常灵活,可以使用自带的方法处理节点,也可以使用推荐的babel插件处理节点,如babel-template
  • visitor访问的是所有的节点,vue框架下,npm run后每次修改逻辑,热响应都会执行visitor的成员方法
  • 插件本身的逻辑修改,需要重新npm run才能响应

额外说明polyfill

首先我们来理清楚这三个概念:

  • 最新ES语法,比如:箭头函数,let/const
  • 最新ES Api,比如Promise
  • 最新ES实例/静态方法,比如String.prototype.include

babel-prest-env仅仅只会转化最新的es语法,并不会转化对应的Api和实例方法,比如说ES 6中的Array.from静态方法。

一些内置方法模块,仅仅通过preset-env的语法转化是无法进行识别转化的,所以就需要一系列类似”垫片“的工具进行补充实现这部分内容的低版本代码实现。这就是所谓的polyfill的作用。

其他插件案例

  • 箭头函数转换插件
const babelTypes = require('@babel/types');

function ArrowFunctionExpression(path) {
  const node = path.node;
  hoistFunctionEnvironment(path);
  node.type = 'FunctionDeclaration';
}

/**
 * @param {*} nodePath 当前节点路径
 */
function hoistFunctionEnvironment(nodePath) {
  // 往上查找 直到找到最近顶部非箭头函数的this p.isFunction() && !p.isArrowFunctionExpression()
  // 或者找到跟节点 p.isProgram()
  const thisEnvFn = nodePath.findParent((p) => {
    return (p.isFunction() && !p.isArrowFunctionExpression()) || p.isProgram();
  });
  // 接下来查找当前作用域中那些地方用到了this的节点路径
  const thisPaths = getScopeInfoInformation(thisEnvFn);
  const thisBindingsName = generateBindName(thisEnvFn);
  // thisEnvFn中添加一个变量 变量名为 thisBindingsName 变量值为 this
  // 相当于 const _this = this
  thisEnvFn.scope.push({
    // 调用babelTypes中生成对应节点
    // 详细你可以在这里查阅到 https://babeljs.io/docs/en/babel-types
    id: babelTypes.Identifier(thisBindingsName),
    init: babelTypes.thisExpression(),
  });
  thisPaths.forEach((thisPath) => {
    // 将this替换称为_this
    const replaceNode = babelTypes.Identifier(thisBindingsName);
    thisPath.replaceWith(replaceNode);
  });
}

/**
 * 查找当前作用域内this使用的地方
 * @param {*} nodePath 节点路径
 */
function getScopeInfoInformation(nodePath) {
  const thisPaths = [];
  // 调用nodePath中的traverse方法进行便利
  // 你可以在这里查阅到  https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
  nodePath.traverse({
    // 深度遍历节点路径 找到内部this语句
    ThisExpression(thisPath) {
      thisPaths.push(thisPath);
    },
  });
  return thisPaths;
}

/**
 * 判断之前是否存在 _this 这里简单处理下
 * 直接返回固定的值
 * @param {*} path 节点路径
 * @returns
 */
function generateBindName(path, name = '_this', n = '') {
  if (path.scope.hasBinding(name)) {
    generateBindName(path, '_this' + n, parseInt(n) + 1);
  }
  return name;
}

module.exports = {
  hoistFunctionEnvironment,
  arrowFunctionPlugin: {
    visitor: {
      ArrowFunctionExpression,
    },
  },
};

附录

类型原名称中文名称描述
Program程序主体整段代码的主体
VariableDeclaration变量声明声明一个变量,例如 var let const
FunctionDeclaration函数声明声明一个函数,例如 function
ExpressionStatement表达式语句通常是调用一个函数,例如 console.log()
BlockStatement块语句包裹在 {} 块内的代码,例如 if (condition){var a = 1;}
BreakStatement中断语句通常指 break
ContinueStatement持续语句通常指 continue
ReturnStatement返回语句通常指 return
SwitchStatementSwitch 语句通常指 Switch Case 语句中的 Switch
IfStatementIf 控制流语句控制流语句,通常指 if(condition){}else{}
Identifier标识符标识,例如声明变量时 var identi = 5 中的 identi
CallExpression调用表达式通常指调用一个函数,例如 console.log()
BinaryExpression二进制表达式通常指运算,例如 1+2
MemberExpression成员表达式通常指调用对象的成员,例如 console 对象的 log 成员
ArrayExpression数组表达式通常指一个数组,例如 [1, 3, 5]
FunctionExpression函数表达式例如const func = function () {}
ArrowFunctionExpression箭头函数表达式例如const func = ()=> {}
AwaitExpressionawait表达式例如let val = await f()
ObjectMethod对象中定义的方法例如 let obj = { fn () {} }
NewExpressionNew 表达式通常指使用 New 关键词
AssignmentExpression赋值表达式通常指将函数的返回值赋值给变量
UpdateExpression更新表达式通常指更新成员值,例如 i++
Literal字面量字面量
BooleanLiteral布尔型字面量布尔值,例如 true false
NumericLiteral数字型字面量数字,例如 100
StringLiteral字符型字面量字符串,例如 vansenb
SwitchCaseCase 语句通常指 Switch 语句中的 Case
  • babel-type api:https://www.npmjs.com/package/babel-types

参考资料地址

在线js转换到AST工具:https://astexplorer.net/#/

babel官网:https://www.babeljs.cn/docs/

AST说明:https://zhaomenghuan.js.org/blog/js-ast-principle-reveals.html

babel指南手册:https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-introduction

babel插件开发相关文章:https://juejin.cn/post/7155434131831128094#heading-7

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/12590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

绘制花朵-第13届蓝桥杯Scratch选拔赛真题精选

[导读]&#xff1a;超平老师计划推出Scratch蓝桥杯真题解析100讲&#xff0c;这是超平老师解读Scratch蓝桥真题系列的第78讲。 蓝桥杯选拔赛每一届都要举行4~5次&#xff0c;和省赛、国赛相比&#xff0c;题目要简单不少&#xff0c;再加上篇幅有限&#xff0c;因此我精挑细选…

OpenYurt v1.1.0: 新增 DaemonSet 的 OTA 和 Auto 升级策略

作者&#xff1a;昌蒲、侯雪城 边缘计算云原生平台、CNCF SandBox 项目 - OpenYurt [1 ] &#xff0c;近期发布了 v1.1.0 版本。 OpenYurt 作为边缘云原生领域的开源项目&#xff0c;采用云管边的云边一体化架构&#xff0c;致力于解决云原生落地边缘计算场景的痛点问题。针对…

扩充antd的Icon图标库

一、功能介绍 项目中有个菜单图标支持配置的功能&#xff0c;如下 二、遇到的问题 上面的图标都是antdIcon组件自带的&#xff0c;只需要给Icon传不同的type就可以显示出来不同的图标&#xff0c;但是我现在需要将自己的图标也放到这个里面&#xff0c;而且实现通过传个type…

asp.net+sqlserver个人简历生成系统C#项目

目 录 1 项目来源 1 1.1 项目背景 1 1.2目的和意义 1 1.3研究成果 2 2 系统开发环境 3 2.1 Visual Studio.NET开发平台 3 2.2 ASP.NET 2.0开发技术 3 2.3 ADO.NET数据访问技术 4 2.4 Microsoft SQL Server简介 4 2.5 B/S结构 5 3 需求分析 6…

服务端Skynet(五)——如何搭建一个实例

服务端Skynet(五)——如何搭建一个实例 文章目录服务端Skynet(五)——如何搭建一个实例1、配置文件2、服务消息分发与回应(call/send)3、通信(server/client)4、Mysql连接1、配置文件 ​ 搭建一个实例 主要看 config 文件的设置&#xff0c;如下&#xff1a; --config inclu…

RK3399驱动开发 | 15 - RTC实时时钟芯片HYM8563S调试(基于linux5.4.32内核)

文章目录 一、Linux RTC设备驱动框架二、HYM8563实时时钟芯片1. 简介2. 引脚图3. 连接原理图三、设备驱动调试1. 设备树节点描述2. 使能内核驱动3. 测试四、hym8563驱动实现分析1. i2c设备驱动框架2. rtc设备注册流程3. 通过i2c驱动操作硬件一、Linux RTC设备驱动框架 Linux内…

宝塔防火墙必要的快速操作指令

重新启动、禁止固定ip等 重启firewall-cmd --reload 禁止固定ip&#xff1a;firewall-cmd --permanent --add-rich-rulerule family"ipv4" source address"192.168.1.1" reject 取消富规则&#xff1a;firewall-cmd --list-rich-rules 删除富规则&#…

Java#9(文字格斗游戏和对象数组练习)

目录 一.文字格斗游戏 二.对象数组 三.键盘录入练习 四.复杂对象数组练习 题目要求: 一.文字格斗游戏 Role类的代码 package Game;import java.util.Random;public class Role {String name;int blood;public Role() {}public Role(String name, int blood) {this.name na…

Node.js 流 Stream【详解】

什么是流&#xff1f; 流是一种将整体数据分割成多个小块依次进行处理的方式。 举个形象的例子&#xff1a; 山上有1000颗拳头大的小石子&#xff0c;需要搬下山。 传统的处理方式&#xff1a;安排一辆大卡车&#xff0c;一次性将石子全部运下山。流的处理方式&#xff1a;修…

Nginx制作下载站点

nginx使用的是模块ngx_http_autoindex_module来实现的&#xff0c;该模块处理以斜杠(“/”)结尾的请求&#xff0c;并生成目录列表。 nginx编译的时候会自动加载该模块&#xff0c;但是该模块默认是关闭的&#xff0c;使用下来指令来完成对应的配置 autoindex 启用或禁用目录…

医疗器械许可证怎么办理

医疗器械经营许可证申请条件 1.有两个与业务规模和业务范围相适应的质量管理机构或大专以上学历的质量管理人员。质量管理人员应具有国家认可的相关专业资格或职称&#xff1b; 2.具有与经营规模和范围相适应的相对独立的经营场所&#xff1b; 3.具备与经营规模和经营范围相…

解读OpenShift的逻辑架构和技术架构

01 OpenShift的逻辑架构 OpenShift的逻辑架构图如图2-6所示。 ▲图2-6 OpenShift逻辑架构 图2-6中的关键组件介绍如下。 底层基础设施:OpenShift可以运行在公有云(AWS、Azure、Google等)、私有云(OpenStack)、虚拟机(vSphere、RHV、红帽KVM)、X86、IBM Power/Z服务器上。…

跨域及cors解决跨域

1.什么是跨域 出于浏览器的同源策略限制。同源策略&#xff08;Sameoriginpolicy&#xff09;是一种约定&#xff0c;它是浏览器最核心也最基本的安全功能&#xff0c;如果缺少了同源策略&#xff0c;则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的…

DJ11 8086系列处理器(第二节课)

目录 一、8088CPU的系统总线 1. 最小模式 2. 最大模式 二、8086/8088 CPU 的功能结构 1. 8086/8088 CPU 的内部结构 2. 8086/8088 CPU 的内部寄存器 1&#xff09;通用寄存器 2&#xff09;段寄存器 3&#xff09;控制寄存器 三、8086/8088 CPU 的存储器组织 1. 物…

超级账本Fabric的世界状态操作与账本操作

在 Hyperledger Fabric 中&#xff0c;账本由两个不同但相关的部分组成 - 世界状态和区块链。 世界状态&#xff1a; 一个数据库&#xff0c;其中存储了一组帐本状态的当前值的缓存。世界状态使程序可以轻松地直接访问状态的当前值&#xff0c;而不必通过遍历整个交易日志来计…

PROTAC与抗体偶联药物的结合

PROTAC 的靶点真核生物的蛋白降解途径主要分为溶酶体途径、泛素蛋白酶体途径、胞液蛋白酶水解途径和线粒体蛋白酶途径等四种 (图1)。其中&#xff0c;PROTAC 所依赖的蛋白酶体途径主要针对细胞周期蛋白、转录因子、细胞表面受体以及胞内变性蛋白等进行降解。 图 1. 不同蛋白降…

《安富莱嵌入式周报》第291期:分分钟设计数字芯片,单片机版JS,神经网络DSP,microPLC,FatFS升级至V0.15,微软Arm64 VS正式版发布

往期周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; https://www.bilibili.com/video/BV1Dd4y1b74x 《安富莱嵌入式周报》第291期&#xff1a;分分…

分享几个小技巧教你图片怎么加边框

大家平时出去玩的时候&#xff0c;肯定没少拍摄照片吧&#xff1f;那你们都是怎么对图片进行修饰的呢&#xff1f;我比较喜欢给图片加上一些边框线条&#xff0c;这样子的图片会比较有意境&#xff0c;能凸显我想要表达的意思。那么大家知道怎么在图片里加边框吗&#xff1f;今…

【Vue.js设计与实现】第4章 响应系统的作用与实现

前言&#xff1a; 本文是我看的Vue.js设计与实现这本书第二篇 响应系统 的第4章 响应系统的作用与实现的一些总结与收获。 第4章从宏观视角讲述了Vue.js 3.0中响应系统的实现机制。从副作用函数开始&#xff0c;逐步实现一个完善的响应系统&#xff0c;还讲述了计算属性和watch…

java计算机毕业设计基于安卓Android的在线心理咨询与健康App

项目介绍 本文介绍了心理咨询与健康App软件开发建设的意义和国内外发展现状,然后详细描述了所开发手机APP的可行性分析,并分析了手机APP所要实现的功能。因为心里咨询设施较多,而且人口密集,不能更好的管理健康问题,造成需要时人员不必要的身心伤害,所以采用比较方便的、容易便…