Создать вспомогательную функцию для запуска функции в изолированной области
Этот код работает:
it.cb(h => {
console.log(h);
h.ctn();
});
it.cb(new Function(
'h', [
'console.log(h)',
'h.ctn()'
]
.join(';')
));
эти два теста в основном идентичны. Но создавать строку с таким массивом довольно сложно, и вы не можете получить статический анализ. Так что я думал о том, чтобы сделать что-то вроде этого:
it.cb(isolated(h => {
console.log(h);
h.ctn();
}));
где изолированные - это вспомогательная функция, которая выглядит примерно так:
const isolated = function(fn){
const str = fn.toString();
const paramNames = getParamNames(str);
return new Function(...paramNames.concat(str));
};
самая большая проблема в том, что Function.prototype.toString()
дает вам всю функцию. Кто-нибудь знает хороший способ просто получить тело функции из строкового представления функции?
Обновление: PRoberts спрашивал, какова цель этого, цель просто:
const foo = 3;
it.cb(isolated(h => {
console.log(foo); // this will throw "ReferenceError: foo is not defined"
h.ctn();
}));
3 ответа
Я написал версию isolated()
который обрабатывает любые bind
Редактирует пользовательское выражение функции и генерирует пользовательские ошибки для доступа по областям:
function isolated (fn) {
return new Function(`
with (new Proxy({}, {
has() { return true; },
get(target, property) {
if (typeof property !== 'string') return target[property];
throw new ReferenceError(property + ' accessed from isolated scope');
}
})) return ${Function.prototype.toString.call(fn)}
`).call(new Proxy(function(){}, new Proxy({}, {
get() { throw new ReferenceError('this accessed from isolated scope'); }
})));
}
// test functions
[
() => arguments, // fail
() => this.foo = 'bar', // fail
() => this.foo, // fail
() => this(), // fail
() => new this, // fail
function (h) { i }, // fail
(a, b) => b > a ? b : a, // pass
function () { this } // pass
].forEach(fn => {
const isolate = isolated(fn);
console.log(isolate.toString());
try {
isolate();
console.log('passed');
} catch (error) {
console.log(`${error.name}: ${error.message}`);
}
})
Эта реализация несколько проще и, следовательно, гораздо менее подвержена ошибкам, чем попытка анализа параметров и тела пользовательской функции.
with
Это сравнительно упрощенный способ перехвата любых ссылок в пределах видимости внутри принудительно изолированной функции и выдачи ReferenceError
, Это делается путем вставки Proxy
промежуточный в область с ловушкой get, которая перехватывает имя переменной области, к которой был получен доступ.
Proxy
это было передано, поскольку контекст функции был единственной частью, которую было немного сложно реализовать. Это было необходимо, потому что Proxy
предоставляется with
оператор не перехватывает доступ к области this
ключевое слово, поэтому контекст должен быть явно упакован, чтобы перехватывать и использовать this
внутри изолированной функции стрелки.
Старый ответ:
Я написал версию isolated()
который обрабатывает все типы выражений функций, включая параметры со структурой по умолчанию и параметры по умолчанию:
function parseArguments(str, offset = 0, depth = 0) {
const openIndex = str.indexOf('(', offset);
const closeIndex = str.indexOf(')', offset);
return (openIndex >= 0 && openIndex < closeIndex)
? parseArguments(str, openIndex + 1, depth + 1)
: (depth > 0)
? parseArguments(str, closeIndex + 1, depth - 1)
: str.slice(0, closeIndex)
}
function isolated (fn) {
let str = Function.prototype.toString.call(fn);
let args, body;
if (/^(?:function\s*\S*\s*)?\(/.test(str)) {
const offset = str.indexOf('(') + 1;
args = parseArguments(str.slice(offset));
str = str.slice(offset + args.length + 1);
} else {
const offset = str.indexOf('=');
args = str.slice(0, offset).trim();
str = str.slice(offset);
}
if (/^\s*(?:=>)?\s*{/.test(str)) {
body = str.slice(str.indexOf('{') + 1, str.lastIndexOf('}'));
} else {
body = 'return ' + str.slice(str.indexOf('>') + 1).trim()
}
// 'use strict' directive prevents leaking scope
return eval(`'use strict';(function(${args}){${body}})`);
}
let processed = [
isolated(function named ({ h }, i = () => {}) {
console.log(h);
return i;
}),
isolated(({ h }, i = () => {}) => {
console.log(h);
return i;
}),
isolated(h => {
console.log(h);
}),
isolated(({ h }, i = () => {}) => (
console.log(h),
i
)),
isolated(h =>
console.log(h)
)
];
processed.forEach((fn) => console.log(fn.toString()))
Я бы просто использовал indexOf('{')
а также lastIndexOf('}')
,
const yourFunction = h => {
console.log(h);
h.ctn();
};
const fnText = yourFunction.toString();
const body = fnText.substring(fnText.indexOf('{') + 1, fnText.lastIndexOf('}'));
console.log(body);
Зная, что это не будет охватывать функции стрелок без тела:
const fn = k => k + 1
Хорошо, это работает, это не было слишком сложно. Мы просто предполагаем, что первый и последний парены являются контуром тела функции.
const isolated = function(fn){
const str = fn.toString();
const first = str.indexOf('{') + 1;
const last = str.lastIndexOf('}');
const body = str.substr(first, last-first);
const paramNames = ['h'];
return new Function(...paramNames.concat(body));
};
Выше мы предполагаем, что единственный аргумент называется "h", но вам нужно будет найти парсер аргументов функции. я использовал require('function-arguments')
в прошлом.