箭头函数 eval 预处理器
Arrow function eval preprocessor
浏览器通过 eval
或 new 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 版本也无法正常工作。
该函数应该接受一个字符串和 return 另一个字符串。例如
resolveArrows('return x.map(a=>a.id)')
应该return
'return x.map(function(a) { return a.id })'
关于如何实现这样的事情有什么想法吗?
正如其他人已经解释的那样,这样的实用程序将非常脆弱,并且不能信任非常复杂的代码。
但是对于简单的情况,可以实现这一点。
以下是 link 到 Fat Arrow
函数扩展。
https://github.com/ConsciousObserver/Whosebug/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>
当我偶然发现这个 post 时,我在网上搜索了与 OP 相同的问题。然而,幸运的是,我不小心使用 eval
执行了一个 lambda 表达式,并且在 2019 年,它正常工作!我测试了最新的 Chrome & Edge,这对我来说已经足够好了。
这是我所做的:
var lambda = '(a, b) => a + b';
var fun = eval(lambda);
var sum = fun(40, 2);
document.write(`Sum: ${sum}`);
浏览器通过 eval
或 new 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 版本也无法正常工作。
该函数应该接受一个字符串和 return 另一个字符串。例如
resolveArrows('return x.map(a=>a.id)')
应该return
'return x.map(function(a) { return a.id })'
关于如何实现这样的事情有什么想法吗?
正如其他人已经解释的那样,这样的实用程序将非常脆弱,并且不能信任非常复杂的代码。
但是对于简单的情况,可以实现这一点。
以下是 link 到 Fat Arrow
函数扩展。
https://github.com/ConsciousObserver/Whosebug/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>
当我偶然发现这个 post 时,我在网上搜索了与 OP 相同的问题。然而,幸运的是,我不小心使用 eval
执行了一个 lambda 表达式,并且在 2019 年,它正常工作!我测试了最新的 Chrome & Edge,这对我来说已经足够好了。
这是我所做的:
var lambda = '(a, b) => a + b';
var fun = eval(lambda);
var sum = fun(40, 2);
document.write(`Sum: ${sum}`);