理解Javascript執(zhí)行過(guò)程

比如javascript/python等都是解釋型語(yǔ)言(但是javascript是先編譯完成后,再進(jìn)行解釋的)。
2. 它是一種非常低的語(yǔ)言,它只針對(duì)特定的體系結(jié)構(gòu)和處理器進(jìn)行優(yōu)化。
3. 開(kāi)發(fā)效率低。容易出現(xiàn)bug,不好調(diào)試。
為什么會(huì)有那么多引擎,那是因?yàn)樗麄兠總€(gè)都被設(shè)計(jì)到不同的web瀏覽器或者像Node.js那樣的運(yùn)行環(huán)境當(dāng)中。
Mozilla瀏覽器 -----> 解析引擎為 Spidermonkey(由c語(yǔ)言實(shí)現(xiàn)的)
Chrome瀏覽器 ------> 解析引擎為 V8(它是由c++實(shí)現(xiàn)的)
Safari瀏覽器 ------> 解析引擎為 JavaScriptCore(c/c++)
IE and Edge ------> 解析引擎為 Chakra(c++)
Node.js ------> 解析引擎為 V8

運(yùn)行階段分為:預(yù)解析 和 運(yùn)行階段。
比如上面詞義分析后結(jié)果變成如下:
[{"type": "Keyword","value": "var"},{"type": "Identifier","value": "a"},{"type": "Punctuator","value": "="},{"type": "Numeric","value": "1"}]

代碼我們可以簡(jiǎn)單的分析下如下:
首先代碼調(diào)用如下:
var str = 'var a = 1';console.log(tokenizer(str));
然后會(huì)判斷該字符串是否為關(guān)鍵字,如關(guān)鍵字: var KEYWORD = /function|var|return|let|const|if|for/;這些其中的一個(gè),如果是的話,直接標(biāo)記為關(guān)鍵字,存入tokens數(shù)組中,如下代碼:
tokens.push({type: 'Keyword',value: value});
tokens = [{ type: 'Keyword', value: 'var' }];
// 標(biāo)記變量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' }];
我們也可以使用這個(gè)在線的網(wǎng)址轉(zhuǎn)換(https://esprima.org/demo/parse.html),結(jié)果變?yōu)槿缦滤荆?
{"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作為參數(shù), 生成抽象語(yǔ)法樹ASTfunction parser(tokens) {// 記錄當(dāng)前解析到詞的位置var current = 0;// 通過(guò)遍歷來(lái)解析token節(jié)點(diǎn)function walk() {// 從token中第一項(xiàng)進(jìn)行解析var token = tokens[current];// 檢查是不是數(shù)字類型if (token.type === 'Numeric') {// 如果是數(shù)字類型的話,把current當(dāng)前的指針移動(dòng)到下一個(gè)位置current++;// 然后返回一個(gè)新的AST節(jié)點(diǎn)return {type: 'Literal',value: Number(token.value),row: token.value}}// 檢查是不是變量類型if (token.type === 'Identifier') {// 如果是,把current當(dāng)前的指針移動(dòng)到下一個(gè)位置current++;// 然后返回我們一個(gè)新的AST節(jié)點(diǎn)return {type: 'Identifier',name: token.value}}// 檢查是不是運(yùn)輸符類型if (token.type === 'Punctuator') {// 如果是,current自增current++;// 判斷運(yùn)算符類型,根據(jù)類型返回新的AST節(jié)點(diǎn)if (/[\+\-\*/]/im.test(token.value)) {return {type: 'BinaryExpression',operator: token.value}}if (/\=/.test(token.value)) {return {type: 'AssignmentExpression',operator: token.value}}}// 檢查是不是關(guān)鍵字if (token.type === 'Keyword') {var value = token.value;// 檢查是不是定義的語(yǔ)句if (value === 'var' || value === 'let' || value === 'const') {current++;// 獲取定義的變量var variable = walk();// 判斷是否是賦值符號(hào)var equal = walk();var rightVar;if (equal.operator === '=') {// 獲取所賦予的值rightVar = walk();} else {// 不是賦值符號(hào), 說(shuō)明只是定義的變量rightVar = null;current--;}// 定義聲明var declaration = {type: 'VariableDeclarator',id: variable, // 定義的變量init: rightVar};// 定義要返回的節(jié)點(diǎn)return {type: 'VariableDeclaration',declarations: [declaration],kind: value}}}// 遇到一個(gè)未知類型就拋出一個(gè)錯(cuò)誤throw new TypeError(token.type);}// 現(xiàn)在,我們創(chuàng)建一個(gè)AST,根節(jié)點(diǎn)是一個(gè)類型為 'Program' 的節(jié)點(diǎn)var ast = {type: 'Program',body: [],sourceType: 'script'};// 循環(huán)執(zhí)行walk函數(shù),把節(jié)點(diǎn)放入到ast.body中while(current < tokens.length) {ast.body.push(walk());}// 最后返回我們的ASTreturn ast;}var tokens = [{ type: 'Keyword', value: 'var' },{ type: 'Identifier', value: 'a' },{ type: 'Punctuator', value: '=' },{ type: 'Numeric', value: '1' }];console.log(parser(tokens));

因此我們需要對(duì)AST樹節(jié)點(diǎn)進(jìn)行轉(zhuǎn)換操作。
/*為了修改AST抽象樹,我們首先要對(duì)節(jié)點(diǎn)進(jìn)行遍歷@param AST語(yǔ)法樹@param visitor定義轉(zhuǎn)換函數(shù),也可以使用visitor函數(shù)進(jìn)行轉(zhuǎn)換*/function traverser(ast, visitor) {// 遍歷樹中的每個(gè)節(jié)點(diǎn)function traverseArray(array, parent) {if (typeof array.forEach === 'function') {array.forEach(function(child) {traverseNode(child, parent);});}}function traverseNode(node, parent) {// 看下 vistory中有沒(méi)有對(duì)應(yīng)的type處理函數(shù)var method = visitor[node.type];if (method) {method(node, parent);}switch(node.type) {// 從頂層的Program開(kāi)始case 'Program':traverseArray(node.body, node);break;// 如下的是不需要轉(zhuǎn)換的case 'VariableDeclaration':case 'VariableDeclarator':case 'AssignmentExpression':case 'Identifier':case 'Literal':break;default:throw new TypeError(node.type)}}traverseNode(ast, null)}/*下面是轉(zhuǎn)換器,它用于遍歷過(guò)程中轉(zhuǎn)換數(shù)據(jù),我們接收之前的AST樹作為參數(shù),最后會(huì)生成一個(gè)新的AST抽象樹*/function transformer(ast) {// 創(chuàng)建新的ast抽象樹var newAst = {type: 'Program',body: [],sourceType: 'script'};ast._context = newAst.body;// 我們把AST 和 vistor 作為參數(shù)傳入進(jìn)去traverser(ast, {VariableDeclaration: function(node, parent) {var variableDeclaration = {type: 'VariableDeclaration',declarations: node.declarations,kind: 'var'};// 把新的 VariableDeclaration 放入到context中parent._context.push(variableDeclaration);}});// 最后返回創(chuàng)建號(hào)的新ASTreturn 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('轉(zhuǎn)換后的-------');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);// 對(duì)于不同類型的節(jié)點(diǎn)分開(kāi)處理\switch (node.type) {// 如果是Program節(jié)點(diǎn),我們會(huì)遍歷它的body屬性中的每一個(gè)節(jié)點(diǎn)case 'Program':return node.body.map(codeGenerator).join('\n');// VariableDeclaration節(jié)點(diǎn)case 'VariableDeclaration':return node.kind + ' ' + node.declarations.map(codeGenerator);// VariableDeclarator 節(jié)點(diǎn)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 聲明會(huì)被提升。
3. 在所在的作用域會(huì)被提升,不會(huì)擴(kuò)展到其他的作用域。
4. 預(yù)編譯后會(huì)順序執(zhí)行代碼。
