前言

Babel为当前最流行的代码JavaScript编译器了,其使用的JavaScript解析器为babel-parser,最初是从Acorn 项目fork出来的。Acorn 非常快,易于使用,并且针对非标准特性(以及那些未来的标准特性) 设计了一个基于插件的架构。本文主要介绍esprima解析生成的抽象语法树节点,esprima的实现也是基于Acorn的。

原文地址

解析器 Parser

JavaScript Parser 是把js源码转化为抽象语法树(AST)的解析器。这个步骤分为两个阶段:词法分析(Lexical Analysis) 和 语法分析(Syntactic Analysis)。

常用的JavaScript Parser:

  • esprima

  • uglifyJS2

  • traceur

  • acorn

  • espree

  • @babel/parser

词法分析

词法分析阶段把字符串形式的代码转换为 令牌(tokens)流。你可以把令牌看作是一个扁平的语法片段数组。

n * n;
复制代码

例如上面n*n的词法分析得到结果如下:

[{ type: { ... }, value: "n", start: 0, end: 1, loc: { ... } },{ type: { ... }, value: "*", start: 2, end: 3, loc: { ... } },{ type: { ... }, value: "n", start: 4, end: 5, loc: { ... } },
]
复制代码

每一个 type 有一组属性来描述该令牌:

{type: {label: 'name',keyword: undefined,beforeExpr: false,startsExpr: true,rightAssociative: false,isLoop: false,isAssign: false,prefix: false,postfix: false,binop: null,updateContext: null},...
}
复制代码

和 AST 节点一样它们也有 start,end,loc 属性。

语法分析

语法分析就是根据词法分析的结果,也就是令牌tokens,将其转换成AST。

function square(n) {return n * n;
}
复制代码

如上面代码,生成的AST结构如下:

{type: "FunctionDeclaration",id: {type: "Identifier",name: "square"},params: [{type: "Identifier",name: "n"}],body: {type: "BlockStatement",body: [{type: "ReturnStatement",argument: {type: "BinaryExpression",operator: "*",left: {type: "Identifier",name: "n"},right: {type: "Identifier",name: "n"}}}]}
}
复制代码

下文将对AST各个类型节点做解释。更多AST生成,入口如下:

  • eslint

  • AST Explorer

  • esprima

结合可视化工具,举个例子

如下代码:

var a = 42;
var b = 5;
function addA(d) {return a + d;
}
var c = addA(2) + b;
复制代码

第一步词法分析之后长成如下图所示:

语法分析,生产抽象语法树,生成的抽象语法树如下图所示

Base

Node

所有节点类型都实现以下接口:

interface Node {type: string;range?: [number, number];loc?: SourceLocation;
}
复制代码

该type字段是表示AST变体类型的字符串。该loc字段表示节点的源位置信息。如果解析器没有生成有关节点源位置的信息,则该字段为null;否则它是一个对象,包括一个起始位置(被解析的源区域的第一个字符的位置)和一个结束位置.

interface SourceLocation {start: Position;end: Position;source?: string | null;
}
复制代码

每个Position对象由一个line数字(1索引)和一个column数字(0索引)组成:

interface Position {line: uint32 >= 1;column: uint32 >= 0;
}
复制代码

Programs

interface Program <: Node {type: "Program";sourceType: 'script' | 'module';body: StatementListItem[] | ModuleItem[];
}
复制代码

表示一个完整的源代码树。

Scripts and Modules

源代码数的来源包括两种,一种是script脚本,一种是modules模块

当为script时,body为StatementListItem。 当为modules时,body为ModuleItem

类型StatementListItemModuleItem类型如下。

type StatementListItem = Declaration | Statement;
type ModuleItem = ImportDeclaration | ExportDeclaration | StatementListItem;
复制代码

ImportDeclaration

import语法,导入模块

type ImportDeclaration {type: 'ImportDeclaration';specifiers: ImportSpecifier[];source: Literal;
}
复制代码

ImportSpecifier类型如下:

interface ImportSpecifier {type: 'ImportSpecifier' | 'ImportDefaultSpecifier' | 'ImportNamespaceSpecifier';local: Identifier;imported?: Identifier;
}
复制代码

ImportSpecifier语法如下:

import { foo } from './foo';
复制代码

ImportDefaultSpecifier语法如下:

import foo from './foo';
复制代码

ImportNamespaceSpecifier语法如下

import * as foo from './foo';
复制代码

ExportDeclaration

export类型如下

type ExportDeclaration = ExportAllDeclaration | ExportDefaultDeclaration | ExportNamedDeclaration;
复制代码

ExportAllDeclaration从指定模块中导出

interface ExportAllDeclaration {type: 'ExportAllDeclaration';source: Literal;
}
复制代码

语法如下:

export * from './foo';
复制代码

ExportDefaultDeclaration导出默认模块

interface ExportDefaultDeclaration {type: 'ExportDefaultDeclaration';declaration: Identifier | BindingPattern | ClassDeclaration | Expression | FunctionDeclaration;
}
复制代码

语法如下:

export default 'foo';
复制代码

ExportNamedDeclaration导出部分模块

interface ExportNamedDeclaration {type: 'ExportNamedDeclaration';declaration: ClassDeclaration | FunctionDeclaration | VariableDeclaration;specifiers: ExportSpecifier[];source: Literal;
}
复制代码

语法如下:

export const foo = 'foo';
复制代码

Declarations and Statements

declaration,即声明,类型如下:

type Declaration = VariableDeclaration | FunctionDeclaration | ClassDeclaration;
复制代码

statements,即语句,类型如下:

type Statement = BlockStatement | BreakStatement | ContinueStatement |DebuggerStatement | DoWhileStatement | EmptyStatement |ExpressionStatement | ForStatement | ForInStatement |ForOfStatement | FunctionDeclaration | IfStatement |LabeledStatement | ReturnStatement | SwitchStatement |ThrowStatement | TryStatement | VariableDeclaration |WhileStatement | WithStatement;
复制代码

VariableDeclarator

变量声明,kind 属性表示是什么类型的声明,因为 ES6 引入了 const/let。

interface VariableDeclaration <: Declaration {type: "VariableDeclaration";declarations: [ VariableDeclarator ];kind: "var" | "let" | "const";
}
复制代码

FunctionDeclaration

函数声明(非函数表达式)

interface FunctionDeclaration {type: 'FunctionDeclaration';id: Identifier | null;params: FunctionParameter[];body: BlockStatement;generator: boolean;async: boolean;expression: false;
}
复制代码

例如:

function foo() {}function *bar() { yield "44"; }async function noop() { await new Promise(function(resolve, reject) { resolve('55'); }) }
复制代码

ClassDeclaration

类声明(非类表达式)

interface ClassDeclaration {type: 'ClassDeclaration';id: Identifier | null;superClass: Identifier | null;body: ClassBody;
}
复制代码

ClassBody声明如下:

interface ClassBody {type: 'ClassBody';body: MethodDefinition[];
}
复制代码

MethodDefinition表示方法声明;

interface MethodDefinition {type: 'MethodDefinition';key: Expression | null;computed: boolean;value: FunctionExpression | null;kind: 'method' | 'constructor';static: boolean;
}
复制代码
class foo {constructor() {}method() {}
};
复制代码

ContinueStatement

continue语句

interface ContinueStatement {type: 'ContinueStatement';label: Identifier | null;
}
复制代码

例如:

for (var i = 0; i < 10; i++) {if (i === 0) {continue;}
}
复制代码

DebuggerStatement

debugger语句

interface DebuggerStatement {type: 'DebuggerStatement';
}
复制代码

例如

while(true) {debugger;
}
复制代码

DoWhileStatement

do-while语句

interface DoWhileStatement {type: 'DoWhileStatement';body: Statement;test: Expression;
}
复制代码

test表示while条件

例如:

var i = 0;
do {i++;
} while(i = 2)
复制代码

EmptyStatement

空语句

interface EmptyStatement {type: 'EmptyStatement';
}
复制代码

例如:

if(true);var a = [];
for(i = 0; i < a.length; a[i++] = 0);
复制代码

ExpressionStatement

表达式语句,即,由单个表达式组成的语句。

interface ExpressionStatement {type: 'ExpressionStatement';expression: Expression;directive?: string;
}
复制代码

当表达式语句表示一个指令(例如“use strict”)时,directive属性将包含该指令字符串。

例如:

(function(){});
复制代码

ForStatement

for语句

interface ForStatement {type: 'ForStatement';init: Expression | VariableDeclaration | null;test: Expression | null;update: Expression | null;body: Statement;
}
复制代码

ForInStatement

for...in语句

interface ForInStatement {type: 'ForInStatement';left: Expression;right: Expression;body: Statement;each: false;
}
复制代码

ForOfStatement

for...of语句

interface ForOfStatement {type: 'ForOfStatement';left: Expression;right: Expression;body: Statement;
}
复制代码

IfStatement

if 语句

interface IfStatement {type: 'IfStatement';test: Expression;consequent: Statement;alternate?: Statement;
}
复制代码

consequent表示if命中后内容,alternate表示else或者else if的内容。

LabeledStatement

label语句,多用于精确的使用嵌套循环中的continue和break。

interface LabeledStatement {type: 'LabeledStatement';label: Identifier;body: Statement;
}
复制代码

如:

var num = 0;
outPoint:
for (var i = 0 ; i < 10 ; i++){for (var j = 0 ; j < 10 ; j++){if( i == 5 && j == 5 ){break outPoint;}num++;}
}
复制代码

ReturnStatement

return 语句

interface ReturnStatement {type: 'ReturnStatement';argument: Expression | null;
}
复制代码

SwitchStatement

Switch语句

interface SwitchStatement {type: 'SwitchStatement';discriminant: Expression;cases: SwitchCase[];
}
复制代码

discriminant表示switch的变量。

SwitchCase类型如下

interface SwitchCase {type: 'SwitchCase';test: Expression | null;consequent: Statement[];
}
复制代码

ThrowStatement

throw语句

interface ThrowStatement {type: 'ThrowStatement';argument: Expression;
}
复制代码

TryStatement

try...catch语句

interface TryStatement {type: 'TryStatement';block: BlockStatement;handler: CatchClause | null;finalizer: BlockStatement | null;
}
复制代码

handler为catch处理声明内容,finalizer为finally内容。

CatchClaus 类型如下

interface CatchClause {type: 'CatchClause';param: Identifier | BindingPattern;body: BlockStatement;
}
复制代码

例如:

try {foo();
} catch (e) {console.erroe(e);
} finally {bar();
}
复制代码

WhileStatement

while语句

interface WhileStatement {type: 'WhileStatement';test: Expression;body: Statement;
}
复制代码

test为判定表达式

WithStatement

with语句(指定块语句的作用域的作用域)

interface WithStatement {type: 'WithStatement';object: Expression;body: Statement;
}
复制代码

如:

var a = {};with(a) {name = 'xiao.ming';
}console.log(a); // {name: 'xiao.ming'}
复制代码

Expressions and Patterns

Expressions可用类型如下:

type Expression = ThisExpression | Identifier | Literal |ArrayExpression | ObjectExpression | FunctionExpression | ArrowFunctionExpression | ClassExpression |TaggedTemplateExpression | MemberExpression | Super | MetaProperty |NewExpression | CallExpression | UpdateExpression | AwaitExpression | UnaryExpression |BinaryExpression | LogicalExpression | ConditionalExpression |YieldExpression | AssignmentExpression | SequenceExpression;
复制代码

Patterns可用有两种类型,函数模式和对象模式如下:

type BindingPattern = ArrayPattern | ObjectPattern;
复制代码

ThisExpression

this 表达式

interface ThisExpression {type: 'ThisExpression';
}
复制代码

Identifier

标识符,就是我们写 JS 时自定义的名称,如变量名,函数名,属性名,都归为标识符。相应的接口是这样的:

interface Identifier {type: 'Identifier';name: string;
}
复制代码

Literal

字面量,这里不是指 [] 或者 {} 这些,而是本身语义就代表了一个值的字面量,如 1,“hello”, true 这些,还有正则表达式(有一个扩展的 Node 来表示正则表达式),如 /\d?/。

interface Literal {type: 'Literal';value: boolean | number | string | RegExp | null;raw: string;regex?: { pattern: string, flags: string };
}
复制代码

例如:

var a = 1;
var b = 'b';
var c = false;
var d = /\d/;
复制代码

ArrayExpression

数组表达式

interface ArrayExpression {type: 'ArrayExpression';elements: ArrayExpressionElement[];
}
复制代码

例:

[1, 2, 3, 4];
复制代码

ArrayExpressionElement

数组表达式的节点,类型如下

type ArrayExpressionElement = Expression | SpreadElement;
复制代码

Expression包含所有表达式,SpreadElement为扩展运算符语法。

SpreadElement

扩展运算符

interface SpreadElement {type: 'SpreadElement';argument: Expression;
}
复制代码

如:

var a = [3, 4];
var b = [1, 2, ...a];var c = {foo: 1};
var b = {bar: 2, ...c};
复制代码

ObjectExpression

对象表达式

interface ObjectExpression {type: 'ObjectExpression';properties: Property[];
}
复制代码

Property代表为对象的属性描述

类型如下

interface Property {type: 'Property';key: Expression;computed: boolean;value: Expression | null;kind: 'get' | 'set' | 'init';method: false;shorthand: boolean;
}
复制代码

kind用来表示是普通的初始化,或者是 get/set。

例如:

var obj = {foo: 'foo',bar: function() {},noop() {}, // method 为 true['computed']: 'computed'  // computed 为 true
}
复制代码

FunctionExpression

函数表达式

interface FunctionExpression {type: 'FunctionExpression';id: Identifier | null;params: FunctionParameter[];body: BlockStatement;generator: boolean;async: boolean;expression: boolean;
}
复制代码

例如:

var foo = function () {}
复制代码

ArrowFunctionExpression

箭头函数表达式

interface ArrowFunctionExpression {type: 'ArrowFunctionExpression';id: Identifier | null;params: FunctionParameter[];body: BlockStatement | Expression;generator: boolean;async: boolean;expression: false;
}
复制代码

generator表示是否为generator函数,async表示是否为async/await函数,params为参数定义。

FunctionParameter类型如下

type FunctionParameter = AssignmentPattern | Identifier | BindingPattern;
复制代码

例:

var foo = () => {};
复制代码

ClassExpression

类表达式

interface ClassExpression {type: 'ClassExpression';id: Identifier | null;superClass: Identifier | null;body: ClassBody;
}
复制代码

例如:

var foo = class {constructor() {}method() {}
};
复制代码

TaggedTemplateExpression

标记模板文字函数

interface TaggedTemplateExpression {type: 'TaggedTemplateExpression';readonly tag: Expression;readonly quasi: TemplateLiteral;
}
复制代码

TemplateLiteral类型如下

interface TemplateLiteral {type: 'TemplateLiteral';quasis: TemplateElement[];expressions: Expression[];
}
复制代码

TemplateElement类型如下

interface TemplateElement {type: 'TemplateElement';value: { cooked: string; raw: string };tail: boolean;
}
复制代码

例如

var foo = function(a){ console.log(a); }
foo`test`;
复制代码

MemberExpression

属性成员表达式

interface MemberExpression {type: 'MemberExpression';computed: boolean;object: Expression;property: Expression;
}
复制代码

例如:

const foo = {bar: 'bar'};
foo.bar;
foo['bar']; // computed 为 true
复制代码

Super

父类关键字

interface Super {type: 'Super';
}
复制代码

例如:

class foo {};
class bar extends foo {constructor() {super();}
}
复制代码

MetaProperty

(这个不知道干嘛用的)

interface MetaProperty {type: 'MetaProperty';meta: Identifier;property: Identifier;
}
复制代码

例如:

new.target  // 通过new 声明的对象,new.target会存在import.meta
复制代码

CallExpression

函数执行表达式

interface CallExpression {type: 'CallExpression';callee: Expression | Import;arguments: ArgumentListElement[];
}
复制代码

Import类型,没搞懂。

interface Import {type: 'Import'
}
复制代码

ArgumentListElement类型

type ArgumentListElement = Expression | SpreadElement;
复制代码

如:

var foo = function (){};
foo();
复制代码

NewExpression

new 表达式

interface NewExpression {type: 'NewExpression';callee: Expression;arguments: ArgumentListElement[];
}
复制代码

UpdateExpression

更新操作符表达式,如++--;

interface UpdateExpression {type: "UpdateExpression";operator: '++' | '--';argument: Expression;prefix: boolean;
}
复制代码

如:

var i = 0;
i++;
++i; // prefix为true
复制代码

AwaitExpression

await表达式,会与async连用。

interface AwaitExpression {type: 'AwaitExpression';argument: Expression;
}
复制代码

async function foo() {var bar = function() {new Primise(function(resolve, reject) {setTimeout(function() {resove('foo')}, 1000);});}return await bar();
}foo() // foo
复制代码

UnaryExpression

一元操作符表达式

interface UnaryExpression {type: "UnaryExpression";operator: UnaryOperator;prefix: boolean;argument: Expression;
}
复制代码

枚举UnaryOperator

enum UnaryOperator {"-" | "+" | "!" | "~" | "typeof" | "void" | "delete" | "throw"
}
复制代码

BinaryExpression

二元操作符表达式

interface BinaryExpression {type: 'BinaryExpression';operator: BinaryOperator;left: Expression;right: Expression;
}
复制代码

枚举BinaryOperator

enum BinaryOperator {"==" | "!=" | "===" | "!=="| "<" | "<=" | ">" | ">="| "<<" | ">>" | ">>>"| "+" | "-" | "*" | "/" | "%"| "**" | "|" | "^" | "&" | "in"| "instanceof"| "|>"
}
复制代码

LogicalExpression

逻辑运算符表达式

interface LogicalExpression {type: 'LogicalExpression';operator: '||' | '&&';left: Expression;right: Expression;
}
复制代码

如:

var a = '-';
var b = a || '-';if (a && b) {}
复制代码

ConditionalExpression

条件运算符

interface ConditionalExpression {type: 'ConditionalExpression';test: Expression;consequent: Expression;alternate: Expression;
}
复制代码

例如:

var a = true;
var b = a ? 'consequent' : 'alternate';
复制代码

YieldExpression

yield表达式

interface YieldExpression {type: 'YieldExpression';argument: Expression | null;delegate: boolean;
}
复制代码

例如:

function* gen(x) {var y = yield x + 2;return y;
}
复制代码

AssignmentExpression

赋值表达式。

interface AssignmentExpression {type: 'AssignmentExpression';operator: '=' | '*=' | '**=' | '/=' | '%=' | '+=' | '-=' |'<<=' | '>>=' | '>>>=' | '&=' | '^=' | '|=';left: Expression;right: Expression;
}
复制代码

operator属性表示一个赋值运算符,leftright是赋值运算符左右的表达式。

SequenceExpression

序列表达式(使用逗号)。

interface SequenceExpression {type: 'SequenceExpression';expressions: Expression[];
}
复制代码
var a, b;
a = 1, b = 2
复制代码

ArrayPattern

数组解析模式

interface ArrayPattern {type: 'ArrayPattern';elements: ArrayPatternElement[];
}
复制代码

例:

const [a, b] = [1,3];
复制代码

elements代表数组节点

ArrayPatternElement如下

type ArrayPatternElement = AssignmentPattern | Identifier | BindingPattern | RestElement | null;
复制代码

AssignmentPattern

默认赋值模式,数组解析、对象解析、函数参数默认值使用。

interface AssignmentPattern {type: 'AssignmentPattern';left: Identifier | BindingPattern;right: Expression;
}
复制代码

例:

const [a, b = 4] = [1,3];
复制代码

RestElement

剩余参数模式,语法与扩展运算符相近。

interface RestElement {type: 'RestElement';argument: Identifier | BindingPattern;
}
复制代码

例:

const [a, b, ...c] = [1, 2, 3, 4];
复制代码

ObjectPatterns

对象解析模式

interface ObjectPattern {type: 'ObjectPattern';properties: Property[];
}
复制代码

例:

const object = {a: 1, b: 2};
const { a, b } = object;
复制代码

结束

AST的作用大致分为几类

  1. IDE使用,如代码风格检测(eslint等)、代码的格式化,代码高亮,代码错误等等

  2. 代码的混淆压缩

  3. 转换代码的工具。如webpack,rollup,各种代码规范之间的转换,ts,jsx等转换为原生js

了解AST,最终还是为了让我们了解我们使用的工具,当然也让我们更了解JavaScript,更靠近JavaScript。

参考文献

  • 前端进阶之 Javascript 抽象语法树

  • 抽象语法树(Abstract Syntax Tree)

转载于:https://juejin.im/post/5c8d3c48f265da2d8763bdaf

高级前端基础-JavaScript抽象语法树AST相关推荐

  1. 理解Babel是如何编译JS代码的及理解抽象语法树(AST)

    Babel是如何编译JS代码的及理解抽象语法树(AST) 1. Babel的作用是?    很多浏览器目前还不支持ES6的代码,但是我们可以通过Babel将ES6的代码转译成ES5代码,让所有的浏览器 ...

  2. 什么是抽象语法树(AST)

    原创: 弗拉 码农翻身 作者: 弗拉@重度前端 https://segmentfault.com/a/1190000017992387 已经获得作者独家授权发布,老刘做了改编. 张大胖一上班,领导就扔 ...

  3. java抽象语法树(ast),【你应该了解的】抽象语法树AST

    团队:skFeTeam  本文作者:李世伟 作为前端程序员,webpack,rollup,babel,eslint这些是不是经常用到?他们是打包工具,代码编译工具,语法检查工具.他们是如何实现的呢?本 ...

  4. 抽象语法树AST以及babel原理

    什么是AST? 借用一下百度百科的解释: 在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示.它以树 ...

  5. php7 ast,PHP7新特性之抽象语法树(AST)带来的变化详解

    本文分析了PHP7新特性之抽象语法树(AST)带来的变化.分享给大家供大家参考,具体如下: 这里大部分内容参照 AST 的 RFC 文档而成:https://wiki.php.net/rfc/abst ...

  6. JS抽象语法树AST基础学习

    点击上方"Python学习开发",选择"加为星标" 第一时间关注Python技术干货! 原文:http://www.goyth.com/2018/12/23/A ...

  7. JavaScript中的 抽象语法树 AST

    AST 抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构.也就是说,一种编程语言的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一 ...

  8. js最小化浏览器_「译」解析、抽象语法树(ast) +如何最小化解析时间的5个技巧...

    前言 该系列课程会在本周陆续更新完毕,主要讲解的都是工作中可能会遇到的真实开发中比较重要的问题以及相应的解决方法.通过本系列的课程学习,希望能对你日常的工作带来些许变化.当然,欢迎大家关注我,我将持续 ...

  9. php ast 抽象语法树,抽象语法树(AST)

    抽象语法树入门到放弃? 抽象语法树(Abstract syntax tree AST)在计算机科学中,抽象语法和抽象语法树其实是源代码的抽象语法结构的树状表现形式 为什么是抽象的? 前端工程化,离不了 ...

最新文章

  1. matplotlib 使用 plt.savefig() 输出图片去除旁边的空白区域
  2. SWT ScrolledComposite解释
  3. 内存不足:杀死进程或牺牲孩子
  4. 【2019icpc南京站网络赛 - H】Holy Grail(最短路,spfa判负环)
  5. SimpleDateFormat线程不安全及解决方案
  6. AcWing1081.度的数量(数位DP)题解
  7. java jdbc 表存在_JDBC / Java – 如何检查数据库中是否存在表和列?
  8. AbiWord 中Piece Table 数据结构的实现----AbiWord Documentation
  9. 登录图片滑动验证实现
  10. Java编写五线谱上的音符_新手读懂五线谱
  11. (端到端多尺度去雾算法)FAMED-Net: A Fast and Accurate Multi-scale End-to-end Dehazing Network
  12. java开发工程师p2级别_java开发工程师p2级别_Java程序员等级怎么划分?
  13. php音频对比技术,HIFIDIY论坛-三种音频功放的音质特点比较 供参考 - Powered by Discuz!...
  14. 安恒月赛2020元旦场Writeup
  15. 万由u-nas系统用上Let’s Encrypt 免费Https安全证书
  16. 学习使用iptables
  17. OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)
  18. webM文件解析工具
  19. 基于skyline的城市三维建模研究
  20. 将cl-home的磁盘空间压缩分配给cl-root

热门文章

  1. php5.2 nts 下载,php_xdebug-2.1.0-5.2-vc6-nts.dll
  2. SQL Server 查询分解
  3. 英语语法汇总(8.动词)
  4. Docker搭建持续集成平台jira
  5. ODI KM二次开发手册
  6. 色色的互联网名词解释整理
  7. 投影串口测试程序_关于串口控制投影机的操作方法的几个步骤
  8. u盘名称霸气_皓影改装点点滴滴之记忆U盘详细解说!
  9. 安卓Palette原理分析
  10. 相对于石墨烯来说,氧化石墨烯中大量含氧官能团的存在使之具有优异的亲水性和高度的化学可协调性