javascript 字符串长度-用JS解释JS!AST解读及其应用

2023-08-26 0 7,851 百度已收录

什么是 AST?

1AST:AbstractSyntaxTree-具体句子树

当我们查看当前主流项目中的devDependency时,我们会发现各种各样的模块工具。 总结起来有:JavaScript翻译、css预处理器、elint、pretiier等。我们在生产环境中不会使用这类模块,但是它们在我们的开发过程中发挥着重要作用,以上工具都是构建的以 AST 为基础。

2AST工作流程

3AST树预览

AST辅助开发工具:

2.从简单的需求开始

代码压缩的伪需求:简化square函数的参数和引用,将变量从num转换为n:

解决方案一:使用replace暴力转换

const sourceText = `function square(num) {  return num * num;}`;

sourceText.replace(/num/g, 'n');

以上操作相当暴力,很容易造成bug而无法投入使用。 如果字符串“num”存在,它也会被转换:

javascript 字符串长度-用JS解释JS!AST解读及其应用

// 转换前function square(num) {  return num * num;}console.log('param 2 result num is ' + square(2));
// 转换后function square(n) { return n * n;}console.log('param 2 result n is ' + square(2));

方案二:使用babel进行AST操作

module.exports = () => {  return {    visitor: {      // 定义 visitor, 遍历 Identifier      Identifier(path) {        if (path.node.name === 'num') {          path.node.name = 'n'; // 转换变量名        }      }    }  }};

通过定义Identifiervisitor,遍历Identifier(变量),如果Identifier名称为“num”,则进行转换。 上面的代码解决了num为字符串时也进行转换的问题,但是仍然存在潜在的问题。 如果代码如下,会导致错误:

// 转换前function square(num) {  return num * num;}console.log('global num is ' + window.num);
// 转换后function square(n) { return n * n;}console.log('global num is ' + window.n); // 出错了

因为window.num也会被上面的访问者迭代器匹配并转换,所以转换后的代码将是window.n,这会导致错误。 分析需求“简化平方函数的参数和引用,并将变量从num转换为n”,提取出三个关键字“平方函数、参数、引用”,进一步优化代码。

解决方案2升级:查找引用关系

module.exports = () => {  return {    visitor: {      Identifier(path,) {        // 三个前置判断        if (path.node.name !== 'num') { // 变量需要为 num          return;        }        if (path.parent.type !== 'FunctionDeclaration') { // 父级需要为函数          return;        }        if (path.parent.id.name !== 'square') { // 函数名需要为 square          return;        }        const referencePaths = path.scope.bindings['num'].referencePaths; // 找到对应的引用        referencePaths.forEach(path => path.node.name = 'n'); // 修改引用值        path.node.name = 'n'; // 修改自身的值      },    }  }};

上面的代码可以将这个过程描述为:

转换结果:

// 转换前function square(num) {  return num * num;}console.log('global num is ' + window.num);
// 转换后function square(n) { return n * n;}console.log('global num is ' + window.num);

在面向业务的AST操作中,需要将“人”的判断可视化,并进行合理的转换。

三巴别林AST

1 API概述

// 三剑客const parser = require('@babel/parser').parse;const traverse = require('@babel/traverse').default;const generate = require('@babel/generator').default;
// 配套包const types = require('@babel/types');
// 模板包const template = require('@babel/template').default;

2@babel/解析器

通过babel/parser将源码转换为AST,简单镜像。

const ast = parser(rawSource, {  sourceType: 'module',  plugins: [    "jsx",  ],});

3@babel/遍历

AST开发的核心,95%以上的代码是@babel/traverse编写的。

const ast = parse(`function square(num) {  return num * num;}`);
traverse(ast, { // 进行 ast 转换 Identifier(path) { // 遍历变量的visitor // ... }, // 其他的visitor遍历器 } )

Visitor的第一个参数是path,path不直接等于node(节点),path的属性和重要方法如下:

4@babel/发电机

使用@babel/generator从操作的AST生成相应的源代码,简单直观。

const output = generate(ast, { /* options */ });

5@babel/类型

@babel/types 用于创建ast节点和确定ast节点,在实际开发中经常用到。

// is开头的用于判断节点types.isObjectProperty(node);types.isObjectMethod(node);
// 创建 null 节点const nullNode = types.nullLiteral();// 创建 square 变量节点const squareNode = types.identifier('square');

6@babel/模板

@bable/types 可以创建ast节点,但过于冗长,而@babel/template可以快速创建整个ast节点。 下面对比两种获取importReactfrom'react'ast节点的形式:

// @babel/types// 创建节点需要查找对应的 API,传参需要匹配方法const types = require('@babel/types');const ast = types.importDeclaration(  [ types.importDefaultSpecifier(types.identifier('React')) ],   types.stringLiteral('react'));
// path.replaceWith(ast) // 节点替换

javascript 字符串长度-用JS解释JS!AST解读及其应用

// 使用 @babel/template// 创建节点输入源代码即可,清晰易懂const template = require('@babel/template').default;const ast = template.ast(`import React from 'react'`);
// path.replaceWith(ast) // 节点替换

7 定义一个通用的babelplugin

定义一个通用的babelplugin可以方便Webpack集成,示例如下:

// 定义插件const { declare } = require('@babel/helper-plugin-utils');
module.exports = declare((api, options) => { return { name: 'your-plugin', // 定义插件名 visitor: { // 编写业务 visitor Identifier(path,) { // ... }, } }});

// 配置 babel.config.jsmodule.exports = {    presets: [        require('@babel/preset-env'), // 可配合通用的 present    ],    plugins: [        require('your-plugin'),        // require('./your-plugin') 也可以为相对目录    ]};

在babelplugin的开发中,可以说是在写asttransformcallback,不需要直接接触“@babel/parser、@babel/traverse、@babel/generator”等babel内部调用的模块。

当需要使用@babel/types能力时,建议直接使用@babel/core,从源码[1]可以看出,@babel/core直接暴露了上面的babel模块。

const core = require('@babel/core');const types = core.types; // const types = require('@babel/types');

四个ESLintinAST

掌握了 AST 的核心原理后,直接在代码上自定义 ESlint 规则就变得更加容易:

// eslint-plugin-my-eslint-pluginmodule.exports.rules = {   "var-length": context => ({ // 定义 var-length 规则,对变量长度进行检测    VariableDeclarator: (node) => {       if (node.id.name.length <= 1){         context.report(node, '变量名长度需要大于1');      }    }  })};

// .eslintrc.jsmodule.exports = {  root: true,  parserOptions: { ecmaVersion: 6 },  plugins: [   "my-eslint-plugin"  ],  rules: {    "my-eslint-plugin/var-length": "warn"   }};

体验疗效

IDE正确提示:

执行eslint命令的警告:

更多ESLint API请参见官方文档[2]。

5. 获取您需要的JSX解释权

第一次接触 JSX 句型大多是在学习 React 的时候,React 将 JSX 的能力推广给了中信[3]。 但 JSX 并不等于 React,也不是由 React 创建的。

// 使用 react 编写的源码const name = 'John';const element = <div>Hello, {name}</div>;

// 通过 @babel/preset-react 转换后的代码const name = 'John';const element = React.createElement("div", null, "Hello, ", name);

JSX 作为标签句子既不是字符串也不是 HTML,而是 JavaScript 句子扩展,它可以很好地描述 UI 呈现应有交互的本质方式。 JSX 让人想起模板语言,它也具有 JavaScript 的全部功能。 接下来我们自己写一个babelplugin来获取JSX必要的解释。

1JSXBabel插件

我们知道HTML是描述网页的语言,axml或者vxml是描述小程序页面的语言,不同的容器之间是不兼容的。 但相同点是javascript 字符串长度,它们都是基于JavaScript技术栈,那么未来是否可以通过定义一套JSX规范来产生相同的页面性能呢?

2个进球

export default (  <view>    hello <text style={{ fontWeight: 'bold' }}>world</text>  </view>);

<div>  hello <span style="font-weight: bold;">world</span></div>

<view>  hello <text style="font-weight: bold;">world</text></view>

目前的疑问是:AST只能作为JavaScript的转换,那么如何转换HTML、axml等文本标记语言呢? 你不妨换一种思路:将上面的JSX代码转换为JS代码,并提供Web端和小程序端的组件消费。 这是AST提出的设计思想。 AST工具只编译代码,具体消耗由上层操作。 @babel/preset-react 和react 就是这种模式。

// jsx 源码module.exports = function () {  return (    <view      visible      onTap={e => console.log('clicked')}    >ABC<button>login</button></view>  );};
// 目标:转后为更通用的 JavaScript 代码module.exports = function () { return { "type": "view", "visible": true, "children": [ "ABC", { "type": "button", "children": [ "login1" ] } ] };};

明确了目标之后,我们要做的是:

1、将jsx标签转换为Object,标签名称为type属性,如转换为{type:'view'}

2、标签上的属性转换为Object的属性,如{}}/>转换为{type:'view',onTap:e=>{}}

3、将jsx中的子元素移植到children属性中,该属性是一个字段,如{type:'view',style,children:[...]}

4、面对子元素,重复上面3步的工作。

下面是实现的示例代码:

const { declare } = require('@babel/helper-plugin-utils');const jsx = require('@babel/plugin-syntax-jsx').default;const core = require('@babel/core');const t = core.types;
/* 遍历 JSX 标签,约定 node 为 JSXElement,如 node = console.log('clicked')} visible>ABC*/const handleJSXElement = (node) => { const tag = node.openingElement; const type = tag.name.name; // 获得表情名为 View const propertyes = []; // 储存对象的属性 propertyes.push( // 获得属性 type = 'ABC' t.objectProperty( t.identifier('type'), t.stringLiteral(type) ) ); const attributes = tag.attributes || []; // 标签上的属性 attributes.forEach(jsxAttr => { // 遍历标签上的属性 switch (jsxAttr.type) { case 'JSXAttribute': { // 处理 JSX 属性 const key = t.identifier(jsxAttr.name.name); // 得到属性 onTap、visible const convertAttributeValue = (node) => { if (t.isJSXExpressionContainer(node)) { // 属性的值为表达式(如函数) return node.expression; // 返回表达式 } // 空值转化为 true, 如将 转化为 { type: 'view', visible: true } if (node === null) { return t.booleanLiteral(true); } return node; } const value = convertAttributeValue(jsxAttr.value); propertyes.push( // 获得 { type: 'view', onTap: e => console.log('clicked'), visible: true } t.objectProperty(key, value) ); break; } } }); const children = node.children.map((e) => { switch(e.type) { case 'JSXElement': { return handleJSXElement(e); // 如果子元素有 JSX,便利 handleJSXElement 自身 } case 'JSXText': { return t.stringLiteral(e.value); // 将字符串转化为字符 } } return e; }); propertyes.push( // 将 JSX 内的子元素转化为对象的 children 属性 t.objectProperty(t.identifier('children'), t.arrayExpression(children)) ); const objectNode = t.objectExpression(propertyes); // 转化为 Object Node /* 最终转化为 { "type": "view", "visible": true, "children": [ "ABC", { "type": "button", "children": [ "login" ] } ] } */ return objectNode;}
module.exports = declare((api, options) => { return { inherits: jsx, // 继承 Babel 提供的 jsx 解析基础 visitor: { JSXElement(path) { // 遍历 JSX 标签,如: // 将 JSX 标签转化为 Object path.replaceWith(handleJSXElement(path.node)); }, } }});

六总结

我们介绍了AST是什么,AST的工作模式,也体验了借助AST所达到的震撼能力。 现在我们来思考一下AST更多的业务场景有哪些? 当用户:

AST将成为你强大的装备。

注:本文演示的代码片段和测试方法都在这里,有兴趣的读者可以去学习体验。

紧急招聘

作者就职于阿里云-人工智能实验室-应用开发部。 目前,我部已累计近20万开发者和企业用户,为数亿设备提供联通服务。 目前团队正在紧急招聘后端(后端、iOS、Android等)、Java开发、数据算法等各个领域的工程师。 方向是联通Devops平台、移动中间件、Serverless、低代码平台、小程序云、云渲染应用平台、新零售/教育行业数字化转型等javascript 字符串长度,感兴趣可以详聊:changwen.tcw@alibaba-inc .com

参考

[1]#L10-L14

[2]

[3]

技术公开课

《React介绍与实战》

React 是一个用于构建用户界面的 JavaScript 库。 本课程共54学时,将带您全面深入地学习React基础知识,并通过案例掌握相关应用。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 javascript javascript 字符串长度-用JS解释JS!AST解读及其应用 https://www.wkzy.net/game/152609.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务