箭头函数eval预处理器

Arrow function eval preprocessor

本文关键字:预处理 处理器 eval 函数      更新时间:2024-01-15

浏览器通过evalnew Function支持动态JavaScript评估。这对于将作为字符串提供的小型数据绑定表达式编译为JavaScript函数非常方便。

例如

var add2 = new Function('x', 'return x + 2');
var y = add2(5); //7

我想预处理这些表达式,以支持ES6箭头函数语法,而不使用babel或任何其他包含数百行JavaScript的库。

var selectId = new Function('x', 'return x.map(a=>a.id)');

不幸的是,即使是最新的IE版本,这也不起作用。

函数应该接受一个字符串并返回另一个字符串。例如

resolveArrows('return x.map(a=>a.id)') 

应返回

'return x.map(function(a) { return a.id })'

关于如何实施这样一件事,有什么想法吗?

正如其他人已经解释的那样,这样的实用程序非常脆弱,并且在非常复杂的代码中是不可信的。

然而,对于简单的情况,可以实现这一点。以下是指向Fat Arrow功能扩展的链接。

https://github.com/ConsciousObserver/stackoverflow/blob/master/Es6FatArrowExpansion/fatArrowUtil.js

导入fatArrowUtil.js并对代码调用expandFatArrow(code)

以下是的使用示例

expandFatArrow("()=>'test me';");

下面是的结果

(function (){return 'test me';}).bind(this)

以下是您建议的测试用例的输出

//actual
var selectId = new Function('x', 'return x.map(a=>a.id)');
//after expansion
var selectId = new Function('x', 'return x.map((function (a){return a.id}).bind(this))');

注意:此实用程序使用Function的bind()来保留"This"上下文。它不会试图编译你的代码,原始代码中的任何错误都会出现在扩展代码中

以下是带有测试和结果的工作样本。

//start of fat arrow utility
'use strict';
function expandFatArrow(code) {
	var arrowHeadRegex = RegExp(/('((?:'w+,)*'w+')|'(')|'w+)['r't ]*=>'s*/);
	var arrowHeadMatch = arrowHeadRegex.exec(code);
	
	if(arrowHeadMatch) {//if no match return as it is
		var params = arrowHeadMatch[1];
		if(params.charAt(0) !== "(") {
			params = "(" + params + ")";
		}
		var index = arrowHeadMatch.index;
		var startCode = code.substring(0, index);
		
		var bodyAndNext = code.substring(index + arrowHeadMatch[0].length);
		
		var curlyCount = 0;
		var curlyPresent = false;
		var singleLineBodyEnd = 0;
		var bodyEnd = 0;
		var openingQuote = null;
		
		for(var i = 0; i < bodyAndNext.length; i++) {
			var ch = bodyAndNext[i];
			if(ch === '"' || ch === "'") {
				openingQuote = ch;
				i = skipQuotedString(bodyAndNext, openingQuote, i);
				ch = bodyAndNext[i];
			}
			
			if(ch === '{'){
				curlyPresent = true;
				curlyCount++;
			} else if(ch === '}') {
					curlyCount--;
			} else if(!curlyPresent) {
				//any character other than { or }
				singleLineBodyEnd = getSingeLineBodyEnd(bodyAndNext, i);
				break;
			}
			if(curlyPresent && curlyCount === 0) {
				bodyEnd = i;
				break;
			}
		}
		var body = null;
		if(curlyPresent) {
			if(curlyCount !== 0) {
				throw Error("Could not match curly braces for function at : " + index);
			}
			body = bodyAndNext.substring(0, bodyEnd+1);
			
			var restCode = bodyAndNext.substring(bodyEnd + 1);
			var expandedFun = "(function " + params + body + ").bind(this)";
			code = startCode + expandedFun + restCode;
		} else {
			if(singleLineBodyEnd <=0) {
				throw Error("could not get function body at : " + index);
			}
			
			body = bodyAndNext.substring(0, singleLineBodyEnd+1);
			
			restCode = bodyAndNext.substring(singleLineBodyEnd + 1);
			expandedFun = "(function " + params + "{return " + body + "}).bind(this)";
			code = startCode + expandedFun + restCode;
		}
		return expandFatArrow(code);//recursive call
	}
	return code;
}
function getSingeLineBodyEnd(bodyCode, startI) {
	var braceCount = 0;
	var openingQuote = null;
	
	for(var i = startI; i < bodyCode.length; i++) {
		var ch = bodyCode[i];
		var lastCh = null;
		if(ch === '"' || ch === "'") {
			openingQuote = ch;
			i = skipQuotedString(bodyCode, openingQuote, i);
			ch = bodyCode[i];
		}
		
		if(i !== 0 && !bodyCode[i-1].match(/['t'r ]/)) {
			lastCh = bodyCode[i-1];
		}
		if(ch === '{' || ch === '(') {
			braceCount++;
		} else if(ch === '}' || ch === ')') {
			braceCount--;
		}
		
		if(braceCount < 0 || (lastCh !== '.' && ch === ''n')) {
			return i-1;
		}
	}
	
	return bodyCode.length;
}
function skipQuotedString(bodyAndNext, openingQuote, i) {
	var matchFound = false;//matching quote
	var openingQuoteI = i;
	i++;
	for(; i < bodyAndNext.length; i++) {
		var ch = bodyAndNext[i];
		var lastCh = (i !== 0) ? bodyAndNext[i-1] : null;
		
		if(ch !== openingQuote || (ch === openingQuote && lastCh === '''' ) ) {
			continue;//skip quoted string
		} else if(ch === openingQuote) {//matched closing quote
			matchFound = false;
			break;
		}
	}
	if(matchFound) {
		throw new Error("Could not find closing quote for quote at : " + openingQuoteI);
	}
	return i;
}
//end of fat arrow utility
//validation of test cases
(function () {
	var tests = document.querySelectorAll('.test');
	var currentExpansionNode = null;
	var currentLogNode = null;
	for(var i = 0; i < tests.length; i++) {
		var currentNode = tests[i];
		addTitle("Test " + (i+1), currentNode);
		createExpansionAndLogNode(currentNode);
		
		var testCode = currentNode.innerText;
		var expandedCode = expandFatArrow(testCode);
		logDom(expandedCode, 'expanded');
		
		eval(expandedCode);
		
	};
	function createExpansionAndLogNode(node) {
		var expansionNode = document.createElement('pre');
		expansionNode.classList.add('expanded');
		currentExpansionNode = expansionNode;
		
		var logNode = document.createElement('div');
		logNode.classList.add('log');
		currentLogNode = logNode;
		
		appendAfter(node,expansionNode);
		addTitle("Expansion Result", expansionNode);
		appendAfter(expansionNode, logNode);
		addTitle("Output", logNode);
	}
	function appendAfter(afterNode, newNode) {
		afterNode.parentNode.insertBefore(newNode, afterNode.nextSibling);
	}
	//logs to expansion node or log node
	function logDom(str, cssClass) {
		console.log(str);
		var node = null;
		if(cssClass === 'expanded') {
			node = currentExpansionNode;
		} else {
			node = currentLogNode;
		}
		
		var newNode = document.createElement("pre");
		
		newNode.innerText = str;
		node.appendChild(newNode);
	}
	function addTitle(title, onNode) {
		var titleNode = document.createElement('h3');
		titleNode.innerText = title;
		onNode.parentNode.insertBefore(titleNode, onNode);
	}
})();
pre {
	padding: 5px;
}
* {
	margin: 2px;
}
.test-unit{
	border: 2px solid black;
	padding: 5px;
}
.test{
	border: 1px solid gray;
	background-color: #eef;
	margin-top: 5px;
}
.expanded{
	border: 1px solid gray;
	background-color: #ffe;
}
.log{
	border: 1px solid gray;
	background-color: #ddd;
}
.error {
	border: 1px solid gray;
	background-color: #fff;
	color: red;
}
<html>
	<head>
		<link rel='stylesheet' href='style.css'>
	</head>
	<body>
<div class='test-unit'>
<pre class='test'>
	//skip braces in string, with curly braces
	var fun = ()=> {
		return "test me {{{{{{} {{{}";
	};
	logDom( fun());
	var fun1 = ()=> logDom('test1: ' + 'test me again{ { {}{{ }}}}}}}}}}}}}}');
	fun1();
</pre>
</div>
<div class='test-unit'>
<pre class='test'>
	var selectId = new Function('x', 'return x.map(a=>a.id)');;
	var mappedArr = selectId([{id:'test'},{id:'test1'}]);
	console.log("test2: " + JSON.stringify(mappedArr));
	logDom("test2: " + JSON.stringify(mappedArr), 'log');
</pre>
</div>
<div class='test-unit'>
<pre class='test'>
	//with surrounding code
	var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
	var es6OddNumbers = numbers.filter(number => number % 2);
	logDom("test3 : " + es6OddNumbers, 'log');
</pre>
</div>
<div class='test-unit'>
<pre class='test'>
	//standalone fat arrow
	var square = x => x * x;
	logDom("test4: " + square(10), 'log');
</pre>
</div>
<div class='test-unit'>
<pre class='test'>
	//with mutiple parameters, single line
	var add = (a, b) => a + b;
	logDom("test5: " + add(3, 4), 'log');
</pre>
</div>
<div class='test-unit'>
<pre class='test'>
	//test with surrounding like test1
	var developers = [{name: 'Rob'}, {name: 'Jake'}];
	var es6Output = developers.map(developer => developer.name);
	logDom("test6: " + es6Output, 'log');
</pre>
</div>
<div class='test-unit'>
<pre class='test'>
	//empty braces, returns undefined
	logDom("test7: " + ( ()=>{} )(), 'log');
</pre>
</div>
<div class='test-unit'>
<pre class='test'>
	//return empty object
	logDom("test8: " + ( ()=>{return {}} )(), 'log');
</pre>
</div>
<div class='test-unit'>
<pre class='test'>
	//working with the 'this' scope and multiline
	function CounterES6() {
	  this.seconds = 0;
	  var intervalCounter = 0;
	  var intervalId = null;
	  intervalId = window.setInterval(() => {
			this.seconds++;
			logDom("test9: interval seconds: " + this.seconds, 'log');
			if(++intervalCounter > 9) {
				clearInterval(intervalId);
				logDom("Clearing interval", 'log');
			}
		}, 1000);
	}
	var counterB = new CounterES6();
	window.setTimeout(() => {
		var seconds = counterB.seconds;
		logDom("test9:   timeout seconds: " +counterB.seconds, 'log');
	}, 1200);
</pre>
</div>
		
	</body>
</html>

当我偶然发现这篇文章时,我在网上搜索了与OP相同的问题。然而,幸运的是,我意外地使用eval执行了一个lambda表达式,而anno 2019,它确实有效!我测试了最新的Chrome&Edge,这对我来说已经足够好了。

以下是我所做的:

var lambda = '(a, b) => a + b';
var fun = eval(lambda);
var sum = fun(40, 2);
document.write(`Sum: ${sum}`);