Technicalarticles
比如javascript/python等都是解释型语言(但是javascript是先编译完成后,再进行解释的)。
2. 它是一种非常低的语言,它只针对特定的体系结构和处理器进行优化。
3. 开发效率低。容易出现bug,不好调试。Mozilla浏览器 -----> 解析引擎为 Spidermonkey(由c语言实现的)Chrome浏览器 ------> 解析引擎为 V8(它是由c++实现的)Safari浏览器 ------> 解析引擎为 JavaScriptCore(c/c++)IE and Edge ------> 解析引擎为 Chakra(c++)Node.js ------> 解析引擎为 V8

比如上面词义分析后结果变成如下:[ { "type": "Keyword", "value": "var" }, { "type": "Identifier", "value": "a" }, { "type": "Punctuator", "value": "=" }, { "type": "Numeric", "value": "1" }]<!DOCTYPE html><html><head> <title></title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"></head><body> <div id="app"> </div> <script type="text/javascript"> function tokenizer(input) { // 记录当前解析到词的位置 var current = 0; // tokens 用来保存我们解析的token var tokens = []; // 利用循环进行解析 while(current < input.length) { // 提取出当前要解析的字符 var char = input[current]; // 处理符号: 检查是否是符号 var PUNCTUATOR = /[`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]/im; if (PUNCTUATOR.test(char)) { // 创建变量用于保存匹配的符号 var punctuators = char; // 判断是否是箭头函数的符号 if (char === '=' && input[current + 1] === '>') { punctuators += input[++current]; } current++; // 最后把数据存入到tokens中 tokens.push({ type: 'Punctuator', value: punctuators }); // 进入下一次循环 continue; } // 下面是处理空格,如果是空格的话,则直接进入下一个循环 var WHITESPACE = /\s/; if (WHITESPACE.test(char)) { current++; continue; } // 处理数字,检查是否是数字 var NUMBERS = /[0-9]/; if (NUMBERS.test(char)) { // 创建变量,用于保存匹配的数字 var number = ''; // 循环当前的字符及下一个字符,直到不是数字为止 while(NUMBERS.test(char)) { number += char; char = input[++current]; } // 最后我们把数据更新到tokens中 tokens.push({ type: 'Numeric', value: number }); // 进入下一个循环 continue; } // 检查是否是字符 var LETTERS = /[a-z]/i; if (LETTERS.test(char)) { // 创建一个临时变量保存该字符 var value = ''; // 循环遍历所有的字母 while(LETTERS.test(char)) { value += char; char = input[++current]; } // 判断当前的字符串是否是关键字 var KEYWORD = /function|var|return|let|const|if|for/; if (KEYWORD.test(value)) { // 标记关键字 tokens.push({ type: 'Keyword', value: value }) } else { // 标记变量 tokens.push({ type: 'Identifier', value: value }) } // 进入下一次循环 continue; } // 如果我们没有匹配上任何类型的token; 那么就抛出一个错误 throw new TypeError('I dont konw what this character is:' + char); } // 最后我们返回词法单元数组 return tokens; } var str = 'var a = 1'; console.log(tokenizer(str));</script></body></html>

可以看到,和上面的打印效果是一样的。
代码我们可以简单的分析下如下:
首先代码调用如下:
var str = 'var a = 1';console.log(tokenizer(str));
tokens.push({ type: 'Keyword', value: value });因此 tokens = [{ type: 'Keyword', value: 'var' }];// 标记变量tokens.push({ type: 'Identifier', value: value});tokens = [ { type: 'Keyword', value: 'var' }, { type: 'Identifier', value: 'a' }, { type: 'Punctuator', value: '=' }];tokens = [ { type: 'Keyword', value: 'var' }, { type: 'Identifier', value: 'a' }, { type: 'Punctuator', value: '=' }, { type: 'Numeric', value: '1' }];
我们也可以使用这个在线的网址转换(https://esprima.org/demo/parse.html),结果变为如下所示: { "type": "Program", "body": [ { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "a" }, "init": { "type": "Literal", "value": 1, "raw": "1" } } ], "kind": "var" } ], "sourceType": "script"}// 接收tokens作为参数, 生成抽象语法树ASTfunction parser(tokens) { // 记录当前解析到词的位置 var current = 0; // 通过遍历来解析token节点 function walk() { // 从token中第一项进行解析 var token = tokens[current]; // 检查是不是数字类型 if (token.type === 'Numeric') { // 如果是数字类型的话,把current当前的指针移动到下一个位置 current++; // 然后返回一个新的AST节点 return { type: 'Literal', value: Number(token.value), row: token.value } } // 检查是不是变量类型 if (token.type === 'Identifier') { // 如果是,把current当前的指针移动到下一个位置 current++; // 然后返回我们一个新的AST节点 return { type: 'Identifier', name: token.value } } // 检查是不是运输符类型 if (token.type === 'Punctuator') { // 如果是,current自增 current++; // 判断运算符类型,根据类型返回新的AST节点 if (/[\+\-\*/]/im.test(token.value)) { return { type: 'BinaryExpression', operator: token.value } } if (/\=/.test(token.value)) { return { type: 'AssignmentExpression', operator: token.value } } } // 检查是不是关键字 if (token.type === 'Keyword') { var value = token.value; // 检查是不是定义的语句 if (value === 'var' || value === 'let' || value === 'const') { current++; // 获取定义的变量 var variable = walk(); // 判断是否是赋值符号 var equal = walk(); var rightVar; if (equal.operator === '=') { // 获取所赋予的值 rightVar = walk(); } else { // 不是赋值符号, 说明只是定义的变量 rightVar = null; current--; } // 定义声明 var declaration = { type: 'VariableDeclarator', id: variable, // 定义的变量 init: rightVar }; // 定义要返回的节点 return { type: 'VariableDeclaration', declarations: [declaration], kind: value } } } // 遇到一个未知类型就抛出一个错误 throw new TypeError(token.type); } // 现在,我们创建一个AST,根节点是一个类型为 'Program' 的节点 var ast = { type: 'Program', body: [], sourceType: 'script' }; // 循环执行walk函数,把节点放入到ast.body中 while(current < tokens.length) { ast.body.push(walk()); } // 最后返回我们的AST return ast;}var tokens = [ { type: 'Keyword', value: 'var' }, { type: 'Identifier', value: 'a' }, { type: 'Punctuator', value: '=' }, { type: 'Numeric', value: '1' }];console.log(parser(tokens));

因此我们需要对AST树节点进行转换操作。/* 为了修改AST抽象树,我们首先要对节点进行遍历 @param AST语法树 @param visitor定义转换函数,也可以使用visitor函数进行转换*/function traverser(ast, visitor) { // 遍历树中的每个节点 function traverseArray(array, parent) { if (typeof array.forEach === 'function') { array.forEach(function(child) { traverseNode(child, parent); }); } } function traverseNode(node, parent) { // 看下 vistory中有没有对应的type处理函数 var method = visitor[node.type]; if (method) { method(node, parent); } switch(node.type) { // 从顶层的Program开始 case 'Program': traverseArray(node.body, node); break; // 如下的是不需要转换的 case 'VariableDeclaration': case 'VariableDeclarator': case 'AssignmentExpression': case 'Identifier': case 'Literal': break; default: throw new TypeError(node.type) } } traverseNode(ast, null)}/* 下面是转换器,它用于遍历过程中转换数据, 我们接收之前的AST树作为参数,最后会生成一个新的AST抽象树*/function transformer(ast) { // 创建新的ast抽象树 var newAst = { type: 'Program', body: [], sourceType: 'script' }; ast._context = newAst.body; // 我们把AST 和 vistor 作为参数传入进去 traverser(ast, { VariableDeclaration: function(node, parent) { var variableDeclaration = { type: 'VariableDeclaration', declarations: node.declarations, kind: 'var' }; // 把新的 VariableDeclaration 放入到context中 parent._context.push(variableDeclaration); } }); // 最后返回创建号的新AST return newAst;}var ast = {"type": "Program","body": [ { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "a" }, "init": { "type": "Literal", "value": 1, "raw": "1" } } ], "kind": "const" }],"sourceType": "script"}console.log(ast);console.log('转换后的-------');console.log(transformer(ast));

var newAst = { "type": "Program", "body": [ { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "a" }, "init": { "type": "Literal", "value": 1, "raw": "1" } } ], "kind": "var" } ], "sourceType": "script"}function codeGenerator(node) { console.log(node.type); // 对于不同类型的节点分开处理\ switch (node.type) { // 如果是Program节点,我们会遍历它的body属性中的每一个节点 case 'Program': return node.body.map(codeGenerator).join('\n'); // VariableDeclaration节点 case 'VariableDeclaration': return node.kind + ' ' + node.declarations.map(codeGenerator); // VariableDeclarator 节点 case "VariableDeclarator": return codeGenerator(node.id) + ' = ' + codeGenerator(node.init); // 处理变量 case 'Identifier': return node.name; // case 'Literal': return node.value; default: throw new TypeError(node.type); } }console.log(codeGenerator(newAst));var a = 1;function abc() { console.log(a); var a = 2;}abc();var a = 1;function abc() { var a; console.log(a); a = 2; }abc();
2. 只有var 和 function 声明会被提升。
3. 在所在的作用域会被提升,不会扩展到其他的作用域。
4. 预编译后会顺序执行代码。
文章来源 | https://www.cnblogs.com/tugenhua0707/p/11980566.html
DO U LIKE?