Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Вывести имя полученной функции (https://javascript.ru/forum/misc/69454-vyvesti-imya-poluchennojj-funkcii.html)

Teamur 24.06.2017 16:14

Вывести имя полученной функции
 
Есть функция ( не работает ):
function showName(f){
  var fName=f.name;
  alert(fName); // 'Rain'
}

function Rain(){
  showName(this); // отправим себя
}

Rain()


Дополнение
Мне кажется туповато писать имя функции и обращаться к нему:

https://developer.mozilla.org/ru/doc.../Function/name

Понимаю, если имя функции не известно и мы хотим узнать его, как в моем вопросе, а тут получается типа:

showName.name // showName
или
Тимур.имя // Тимур

Мда...

А Function.caller не стандартно. Эх...

ksa 26.06.2017 09:10

Teamur, ты никогда не узнаешь имя функции, передавая ее как параметр в другую функцию... :no:

Teamur 26.06.2017 18:06

ksa,
А если так:
function showName(f){
alert(f+'');
}

Возможно так получится, но не могу проверить, с планшета

Teamur 26.06.2017 18:13

ksa,
слушай, я в регулярках вообще не шарю, как мне получить содержимое первой скобки, дс как получить параметры из строки-функции?
Смотрел варианты в сети, но там какие-то длинющие регулярки для простого получения содержимого между скобками.

ksa 27.06.2017 08:22

Цитата:

Сообщение от Teamur (Сообщение 456576)
ksa,
А если так:
function showName(f){
alert(f+'');
}

Имя функции тут "f"...

ksa 27.06.2017 08:23

Цитата:

Сообщение от Teamur
как мне получить содержимое первой скобки, дс как получить параметры из строки-функции?

Приведи пример той строки... Потом расскажи чего из нее хочешь получить...

EmperioAf 27.06.2017 17:29

function getFunctionName(func) {
	if(typeof func === 'function') {
  	const funcNameArr = /^\s*function\s*([^\(]*).*/i.exec(func.toString());
    if(funcNameArr && funcNameArr[1]) {
    	return funcNameArr[1];
    } else {
    	return 'anonymous';
    }
  } else {
  	throw new Error('That\'s not function');
  }
}

function example(a) {
	console.log('example function', a);
}
var d = () => {};

console.log(getFunctionName(example));
console.log(getFunctionName(d));


Правда с точки зрения практики, в этом особого смысла нет. Если вы конечно не собираетесь делать всякие разные проверки на имя входящей функции. Ну и изначальная конструкция с this работать не будет. Так как this это контекст функции, и он обычно не совпадает с самой функцией.

Teamur 27.06.2017 19:04

ksa,

function showName(f){alert(f+'')};

function MyFn(a,b=1,c=7){};

showName(MyFn);


Поучил строку:
"function MyFn(a,b=1,c=7){}"

Какую надо придумать регулярку, чтобы:
1) получить всё что после первого пробела (после "function ")
2) отсечь всё что после первой закрывающей скобки
3) разбить "MyFn(a,b=1,c=7)" на две части: "MyFn" и "(a,b=1,c=7)"
4) из второй части убрать скобки, нап. так: "(a,b=1,c=7)".slice(1,-1)
5) вернуть массив ["MyFn","a,b=1,c=7"] или объект {fn:"MyFn",args:"a,b=1,c=7"}

Аргументы я потом получу через split(',').
Вот такие мысли.

EmperioAf 27.06.2017 19:58

function getFunctionName(func) {
	if(typeof func === 'function') {
  	var funcNameArr = /^\s*(?:function)?\s*([^\(]*)\s*\((.*?)\)(?:\s*\{|\s*\=\>\s*\{?).*/gmi.exec(func.toString());
    if(funcNameArr) {
    	return {
							funcName: funcNameArr[1] || 'anonymous',
							params: funcNameArr[2]
			}
    }
  } else {
  	throw new Error('That\'s not function');
  }
}

function example(a = {1:2, 2:3}, b=6, {d: d}, c) {
	console.log('example function', a);
}
var d = (c = 2, {a}) => {};
function  p(a, b=')', d = '{') { function o(){console.log('albert')}}
function MyFn(a,b=1,c=7){};
function s(a=')')
{
}

console.log(getFunctionName(example)); // {funcName: "example", params: "a = {1:2, 2:3}, b=6, {d: d}, c"}
console.log(getFunctionName(d)); // {funcName: "anonymous", params: "c = 2, {a}"}
console.log(getFunctionName(MyFn)); // {funcName: "MyFn", params: "a,b=1,c=7"}
console.log(getFunctionName(p)); // {funcName: "p", params: "a, b=')', d = '{'"}
console.log(getFunctionName(s)); // {funcName: "s", params: "a=')'"}

Teamur 27.06.2017 23:56

console.log(EmperioAf.isMaster()); // -> true

Спасибо!

ksa 28.06.2017 10:52

Цитата:

Сообщение от Teamur
Поучил строку:
"function MyFn(a,b=1,c=7){}"

Какую надо придумать регулярку, чтобы:

Уже лучше... :)

Цитата:

Сообщение от Teamur
1) получить всё что после первого пробела (после "function ")

var str="function MyFn(a,b=1,c=7){}";
alert(str.substr(9));



Цитата:

Сообщение от Teamur
2) отсечь всё что после первой закрывающей скобки

var str="function MyFn(a,b=1,c=7){}";
alert(str.split(')')[0]+')');


Цитата:

Сообщение от Teamur
3) разбить "MyFn(a,b=1,c=7)" на две части: "MyFn" и "(a,b=1,c=7)"

var str="MyFn(a,b=1,c=7)";
var a=str.split('(');
alert(a[0]+' и ('+a[1]);


Цитата:

Сообщение от Teamur
4) из второй части убрать скобки, нап. так: "(a,b=1,c=7)".slice(1,-1)

var str="(a,b=1,c=7)";
alert(str.match(/\((.+)\)/)[1]);


Цитата:

Сообщение от Teamur
5) вернуть массив ["MyFn","a,b=1,c=7"] или объект {fn:"MyFn",args:"a,b=1,c=7"}

var str="MyFn(a,b=1,c=7)";
var o={};
alert(str.match(/.*(?=\()/));
o.fn=str.match(/.*(?=\()/);
alert(str.match(/\((.+)\)/)[1]);
o.args=str.match(/\((.+)\)/)[1];

ruslan_mart 28.06.2017 11:02

На деле таких задач никогда не должно возникать, а если и возникают - значит Вы делаете что-то не так.

Teamur 28.06.2017 20:22

ksa,
обалдеть, спасибо!
Ruslan_xDD,
если только вы не проводите различные опыты )

Teamur 28.06.2017 20:43

ksa,
'(a,b=1,c=7)'.match(/\((.+)\)/)[1]

Это самый короткий способ получить регуляркой то, что внутри скобок?

Добавлю, что все функции, которые отправляются в MyFn, будут предварительно сжаты, следовательно обработку пробелов в регулярке можно исключить.

Конечная цель получить функцию, которая бы разбивала входящие данные на куски по типам: имя_функции, группа_параметров, свойство, метод. В итоге для каждой полученной функции должен быть создан объект с разложенными по типу кусками.

По сути речь идет о Деконструкторе. Вместо функции можно передать любой валидный код и Деконструктор должен разложить ее на части: свойства, методы, циклы и тд. Останется потом только обернуть в теги и раскрасить.

Отправляем строку коду, а на выходе красивая сборка из типовых блоков кода.

EmperioAf 28.06.2017 21:08

Teamur,
Рекомендую изучить исходный код готово парсера js в AST. https://github.com/babel/babylon например.

'(a =")", bc)'.replace(/^\((.*)\)$/, '$1') // a =")", bc

Более безопасный способ, чтобы у вас код не упал с ошибкой, когда пришла строка без скобок.

ksa 29.06.2017 08:28

Цитата:

Сообщение от Teamur
Это самый короткий способ получить регуляркой то, что внутри скобок?

Можно и не регуляркой...

var str="(a,b=1,c=7)";
alert(str.substr(1,str.length-2));

Teamur 29.06.2017 18:44

ksa,
я думаю через слайс будет короче и проще:
var result = "(lorem ipsum dolor)".slice(1,-1)


А какая самая простая и короткая регулярка для этого?

ksa 30.06.2017 08:50

Цитата:

Сообщение от Teamur
А какая самая простая и короткая регулярка для этого?

Так я же показал ее выше
Цитата:

Сообщение от ksa
alert(str.match(/\((.+)\)/)[1]);


Teamur 30.06.2017 18:43

ksa,
ага, понял, поверю, спасибо! Удачи!

Malleys 01.07.2017 05:44

Имя функций, которая не является ни геттером ни сеттером и не является результатом функций bind можно узнать вызвав свойство name у функций.

Что же касается способов получения имени функций, и её параметров при помощи регулярного выражения, которые описаны выше в этой теме, то там не учтены некоторые особенности объекта функция (и в частности её метода toString) Поэтому чудовищно хромают.

Для начала нужно узнать, является ли объект object функцией. Для этого подойдёт...
typeof object === "function" или
object instanceof Function

function getFunctionData(object) {
	if(object instanceof Function === false) throw new TypeError("First argument to getFunctionData function must be an Function");
	
	// удаляем из начала имени указания того, что это сеттер,
	// геттер или получено из функций bind
	// имя функций объявленное при помощи символа будет в квадратных скобках
	let name = object.name.replace(/^(?:set|get)\s|(?:bound\s+)+/, "");
	
	return {
		name
	}
}

// помощник
function test(fn) {
	let functionData = getFunctionData(fn);
	
	console.group(`Функция — ${functionData.name ? functionData.name : "безымянная"}`);
	console.log(fn);
	console.groupEnd();
}

// примеры (результаты видны в консоли, F12)
test(Function);
test(function Awesome() {});
test(class PolyRange extends HTMLInputElement {});
test(new Function("/* I'm really anonymous */"));
test(({ id: a }) => () => a);
test(async (url) => await fetch(url).then(response => response.json()));
test(function* counter() {});
test(class { baz(...nums) { return [...new Set(nums)]; } });
test((class { baz(...nums) { return [...new Set(nums)]; } }).prototype.baz);
test(((x = "function you have got confused() {}") => ({[x](x=x){}})[x])());
test(Object.getOwnPropertyDescriptor({set foo({mode} = { mode: "normal" }) {}}, "foo").set);
test(function* range({ mapper = function(x) { return x }, start = 0, end = Infinity, step = 1 } = {}) { let value = start; while(end > value) yield mapper(value += step); });
test((function save() {}).bind({}));
test((function save() {}).bind({}).bind({}).bind({}));
test(({[Symbol.for("Hello")](x){}})[Symbol.for("Hello")]);


Теперь давайте получим имена всех параметров функций. При помощи какого-то одного регулярного выражений это не получится сделать, поскольку невозможно отследить правильный порядок скобок.
И аргументы и тело могут содержать последовательности символов "=>", ")", "{" или "}". Например, вот какие строки может возвращать метод toString у функций:

function() {}

(a = a => a) => {}

function* (...args) {}

class {}

(){} // получается путем ({[""](){}})[""];

async *function({url, options: { mode }}) {}

function (callback = function(callback = async function(callback) {}) {}) {}


Можно использовать парсер, например, Esprima — стандартно-совместимый анализатор ECMAScript — чтобы найти все имена параметров, которые будут доступны после того, как функция будет вызвана.
Вот окончательный вариант функций getFunctionData, которая возвращает имя и имена параметров функций...

<!doctype html>
<html lang="en" style="background: #f06; ">
<head>
	<meta charset="utf-8">
</head>
<body>
	<script src="https://unpkg.com/esprima@4.0.0/dist/esprima.js"></script>
	<script>
		
function getFunctionData(object) {
	if(object instanceof Function === false) throw new TypeError("First argument to getFunctionData function must be an Function");
	
	// удаляем из начала имени указания того, что это сеттер,
	// геттер или получено из функций bind
	// имя функций объявленное при помощи символа будет в квадратных скобках
	let name = object.name.replace(/^(?:set|get)\s|(?:bound\s+)+/, "");
	
	// теперь используем Esprima
	// встроенные функций и wasm всегда возвратят []
	// поскольку имеют вид "function() { [native code] }"
	// и поскольку можно `Function.prototype.toString = () => "function() { [native code] }";`
	// таким образом не гарантируется получение имён параметров, если toString у
	// функций был переопределён
	// вместо этого можно таким же образом разобрать исходный код...
	let body = object.toString();
	
	let ast = tryThese(
		() => esprima.parse("(" + body + ");"),
		() => esprima.parse("(function " + body + ");"),
		() => esprima.parse("(function " + body.replace(/^function set /, "") + ");"),
		() => esprima.parse("(function " + body.replace(/^function get /, "") + ");"),
		() => esprima.parse("(" + body.replace("[native code]", "") + ");"),
	);
	
	let firstBody = ast.body[0] || {};
	let params = firstBody.type === "ExpressionStatement" ? firstBody.expression.type === "ClassExpression" ? firstBody.expression.body.body : firstBody.expression.params : firstBody.params;
	
	// возвращает массив всех переменных, которые будут
	// доступны, когда функция будет вызвана
	function getParams(ctx, paramNames = []) {
		if(!ctx) return paramNames;
		
		if("type" in ctx === false) {
			ctx.forEach(param => getParams(param, paramNames));
		} else {
			// рекурсивный обход дерева — поиск узлов типа "Identifier"
			switch(ctx.type) {
				case "AssignmentPattern":
					paramNames.push(...getParams(ctx.left));
					break;
					
				case "ArrayPattern":
					paramNames.push(...getParams(ctx.elements));
					break;
					
				case "FunctionExpression":
					paramNames.push(...getParams(ctx.params));
					break;
					
				case "Identifier":
					paramNames.push(ctx.name);
					break;
					
				case "ObjectPattern":
					paramNames.push(...getParams(ctx.properties));
					break;
					
				case "MethodDefinition":
					if(ctx.kind !== "constructor") break;
					
				case "Property":
					paramNames.push(...getParams(ctx.value));
					break;
					
				case "RestElement":
					paramNames.push(...getParams(ctx.argument));
					break;
			}
		}
		
		return paramNames;
	}
	
	
	// возвращает результат той функций, которая не улетает
	function tryThese(...fns) {
		let fn, error;
		
		while(fn = fns.shift()) {
			try {
				return fn();
			} catch (_) {
				error = _;
				continue;
			}
		}
		
		throw error;
	}
	
	return {
		name,
		params: getParams(params)
	}
}

// помощник
function test(fn) {
	let functionData = getFunctionData(fn);
	
	console.group(`Функция — ${functionData.name ? functionData.name : "безымянная"}`);
	console.log(fn);
	console.log("Параметры: ", JSON.stringify(functionData.params));
	console.groupEnd();
}
		
// примеры (результаты видны в консоли, F12)
test(Function);
test(function Awesome() {});
test(function Point(x, y) {});
test(class Point { constructor(x, y) {}});
test(class PolyRange extends HTMLInputElement {});
test(new Function("[a, b = \"\", ...c]", "/* I'm really anonymous */"));
test(({ id: a }) => () => a);
test(async (url) => await fetch(url).then(response => response.json()));
test(function* counter() {});
test(class { baz(...nums) { return [...new Set(nums)]; } });
test((class { baz(...nums) { return [...new Set(nums)]; } }).prototype.baz);
test(((x = "function you have got confused() {}") => ({[x](x){}})[x])());
test(class extends (class extends (class extends (class extends (function ({ href } = location) {}) {}) {}) {}) {});
test(function whois({age, displayName: displayName, fullName: {firstName: name}}){});
test(Object.getOwnPropertyDescriptor({set foo({mode} = { mode: "normal" }) {}}, "foo").set);
test(Object.getOwnPropertyDescriptor({get foo() {}}, "foo").get);
test(function* range({ mapper = function(x) { return x }, start = 0, end = Infinity, step = 1 } = {}) { let value = start; while(end > value) yield mapper(value += step); });
test((function save() {}).bind({}));
test((function save() {}).bind({}).bind({}).bind({}));
test(({[Symbol.for("Hello")](x){}})[Symbol.for("Hello")]);
test(({a1: a2 = a3 => a4}) => {});

	</script>
	<p style="font: 2.5em / 1.5 Menlo, monospace; text-align: center; text-shadow: 0 0.1em 0.1em #7b3755; ">результаты видны в консоли, F12</p>
</body>
</html>


Esprima http://esprima.org/demo/parse.html Конечно же Вы можете анализировать всё, что угодно, а не только параметры функции...

Teamur 01.07.2017 09:11

Malleys,
мда, Esprima - серъезная штука, спасибо!
Теперь я всё смогу и я победю ))


Часовой пояс GMT +3, время: 12:58.