Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   В чем сходство Function и Eval? (https://javascript.ru/forum/misc/48492-v-chem-skhodstvo-function-i-eval.html)

foo 06.07.2014 11:59

В чем сходство Function и Eval?
 
У крокфорда в good parts есть утверждение, что Function -- это, почти то же самое что eval. Я копаюсь уже битый час, и не могу понять вообще, что у них общего. Насколько я понял, Function сразу компилируется в обычную анонимную ф-цию. И в дальнейшем, она ведет себя как обычная ф-ция, даже с сохранением лексического скопа в замыканиях.
Код:

fu=Function("var a=1; return function(){return(a)}")
console.log(""+fu)
//
//out:
//function anonymous() {
//var a=1; return function(){return(a)}
//}

inner_fu=fu()
console.log(inner_fu())
//out:
//1

Что же общего нашел крокфорд между ними? Покажите пример эквивалентного поведения, пожалуйста.

kobezzza 06.07.2014 12:27

Общее то, что они работают со строками, т.е. компилируют строки в JS. Function всегда создаёт глобальные функции, т.е.

var a = 1;

(function () {
    var a = 2;
    new Function('alert(a)')(); // 1
})();


A eval работает в том контексте, в котором он был вызван.

var a = 1;

(function () {
    var a = 2;
    eval('alert(a)'); // 2
})();


В 99% случаев лучше использовать Function, т.к. для такого кода также используется JIT компилятор VM, а вот для eval конструкций нет и это может серьёзно сказаться на производительности, а самое страшное то, что вызов eval внутри блока также отменяет использование JIT для всего блока:

// Очень быстрый цикл, т.к. используется JIT
for (var i = 0; i < 1e6; i++) {
    console.log(el);
}

// Всё будет очень тормозить, т.к. вызов eval внутри цикла отменил использование JIT для всей конструкции.
for (var i = 0; i < 1e6; i++) {
    eval('console.log(el);');
}

kobezzza 06.07.2014 12:31

Цитата:

Покажите пример эквивалентного поведения, пожалуйста.
// Парсинг строки как литерала объекта
var a = '({a: 1, b: 2})';

eval(a); // {a: 1, b: 2}

new Function('return ' + a)(); // {a: 1, b: 2}


// Создание функции из строки (однако следует помнить про то, что у функций будет разный контекст)

eval('(function (a, b) { return a + b; })')(1, 2) // 3

new Function('a', 'b', 'return a + b')(1, 2) // 3

Aetae 06.07.2014 12:38

var a = '{a: 1, b: 2}';
 
eval(a); // {a: 1, b: 2}
kobezzza, ёшкин кот, этож классический пример.
eval(a); //syntax error

kobezzza 06.07.2014 12:40

Цитата:

Сообщение от Aetae (Сообщение 319660)
var a = '{a: 1, b: 2}';
 
eval(a); // {a: 1, b: 2}
kobezzza, ёшкин кот, этож классический пример.
eval(a); //syntax error

Поправил, вечно забываю про метки.

foo 06.07.2014 13:56

Цитата:

Сообщение от kobezzza
В 99% случаев лучше использовать Function

Провел небольшой тестик на ноде, который мне дает основание усомниться:
Код:


test1=function(){
var i=1000000
while(i){
        a=Function("test")
        i--
}
}

test2=function(){
var i=1000000
while(i){
        a=function(){"test"}
        i--
}
}

console.time(1)
        test1()
console.timeEnd(1)

console.time(2)
        test2()
console.timeEnd(2)
1: 2133ms
2: 60ms

с эвалом даже получше дела обстоят:
Код:

a=1
test3=function(){
var i=1000000
while(i){
        a=eval("a")
        i--
}
}


console.time(3)
        test3()
console.timeEnd(3)
3: 1362ms


kobezzza 06.07.2014 14:01

Цитата:

В 99% случаев лучше использовать Function
Ты троллишь чтоли? Я имел ввиду Function предпочтительнее eval в 99%.

Цитата:

Провел небольшой тестик на ноде, который мне дает основание усомниться.
Ну как бы очевидно, что компиляция в рантайме без предварительного кеширования априори медленее, т.к. бедная VM на каждой итерации должна поднимать транслятор.

Цитата:

с эвалом даже получше дела обстоят:
У тебя разные вещи происходят в eval и Function, или ты не заметил?

ЗЫ: а вообще ты мне надоел, можешь гавнокодить как тебе захочется.

foo 06.07.2014 14:07

Цитата:

Сообщение от kobezzza
У тебя разные вещи происходят в eval и Function, или ты не заметил?

Это не столь важно
a=eval("(function(){'test'})")
3: 1267ms

foo 06.07.2014 14:08

Цитата:

Сообщение от kobezzza
можешь гавнокодить как тебе захочется.

Дело не в этом. Важно докопаться до истины.

kobezzza 06.07.2014 14:14

Т.е. ты не видешь, что

Function("test")


и

function(){"test"}


Это разные по функционалу функции?

alert(Function("test").toString())


Более того, на таких убогий тестах вообще нельзя делать никаких выводов.

foo 06.07.2014 14:23

Цитата:

Сообщение от kobezzza
Это разные функции?

пардон, я там ошибся, правильно
Код:

a=eval("(function(){'test'})")
Тут суть в том, что она тут тоже компилиться на каждой итерации, поэтому получается, что эвал быстрей

kobezzza 06.07.2014 14:35

Цитата:

Тут суть в том, что она тут тоже компилиться на каждой итерации, поэтому получается, что эвал быстрей
Ещё раз: на Function используется JIT, а на eval нет.

Для тестов:

var a = [], 
     b = [];

var f1 = eval('(function (val) { a.push(val); })');
var f2 = new Function('val', 'b.push(val)');

for (var i = 0; i < 1e6; i++) {
    f1(i);
}

for (var i = 0; i < 1e6; i++) {
    f2(i);
}


Результат будет одинаков, т.к. в обоих случаях элементарное содержимое функций, а если у тебя в теле функций будет сложная логика, вызовы других функций и т.д., то велика вероятность, что встроенный JIT даст значительный профит на варианте с Function, в то время как eval никак не будет обрабатываться.

Т.е. твои тесты - убоги и не несут никакой полезной информации.

foo 06.07.2014 14:37

Цитата:

Сообщение от kobezzza
Это разные по функционалу функции?

ваш пример как раз доказывает, что
Код:


a=eval("(function(){'test'})")
b=Function("'test'")
console.log(""+a)
console.log(""+b)

out:
function (){'test'}
function anonymous() {
'test'
}

эквивалентны.

kobezzza 06.07.2014 14:47

Цитата:

эквивалентны
Да, только

// У тебя было
Function("test") // поиск идентификатора test

// А не
Function("'test'") // создание литерала строки 'test'

foo 06.07.2014 14:58

Цитата:

Сообщение от kobezzza
Результат будет одинаков

Кто бы сомневался, в цикле то выполняются уже скомпилированные ф-ции. Это все равно что написать
Код:

f1=function(){"test1"}
f2=function(){"test2"}

while(...)f1()
while(...)f2()

Это уж 100% тесты ни о чем. У тебя разница (мизерная) только на этапе создания(однократного). Просто феерический идиотизм.

kobezzza 06.07.2014 15:03

foo, слишком толсто, удаляюсь из темы.

foo 06.07.2014 15:06

Цитата:

Сообщение от kobezzza
поиск идентификатора test

А какая разница в данном случае? Там функция только создается, разница была бы только в том случае, если бы она вызывалась.

foo 06.07.2014 15:07

Цитата:

Сообщение от kobezzza
слишком толсто

Толстым был как раз предложенный Вами тест. Я до сих пор не понял, то ли это шутка была, то-ли серьезно.

foo 06.07.2014 15:10

kobezzza,
Код:


        var f1 = eval('(function (val) { a.push(val); })');
        var f2 = new Function('val', 'b.push(val)');

        console.log(f1)
        console.log(f2)
[Function]
[Function]

Если чо.

kobezzza 06.07.2014 18:04

Цитата:

Сообщение от foo (Сообщение 319681)
Толстым был как раз предложенный Вами тест. Я до сих пор не понял, то ли это шутка была, то-ли серьезно.

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

Никто и никогда не будет делать так:

for (...) {
    Function('console.log(...)')()
}


Поэтому я и говорю, что твои тесты бессмысленны. Что они тестируют? Скорость компиляции - это не узкое место.

Я же говорю про скорость работы полученной функции и привёл пример, где эта скорость одинакова, но пояснил, что она одинакова только в этом частном случае, т.к. у функций слишком простое тело.

Я сказал, что во всех современных VM JS используются JIT компиляторы и что конструкции eval не обрабатываются этим самым компилятором, а вот Function обрабатываются. Гугли что такое JIT компилятор и как он работает, может станет понятно.

Т.е. чем сложнее тело функции, тем очевиднее разница в скорости функции полученной через Function и eval.

global.a = [];
global.b = [];

function test() {
	var f1 = eval('(function (val) { global.a.push(val); })');
	var f2 = new Function('val', 'global.b.push(val)');
	
	console.time('start');
	for (var i = 0; i < 1e6; i++) {
		f1(i);
	}
	console.timeEnd('start');
	
	console.time('start2');
	for (var i = 0; i < 1e6; i++) {
		f2(i);
	}
	console.timeEnd('start2');
}

test();


Вот уже чуть более сложный пример у меня Function при тесте в V8 быстрее в 3 раза.

Теперь ты понял?

foo 06.07.2014 19:03

Цитата:

Сообщение от kobezzza
быстрее в 3 раза.

странно, а у меня 180/170 примерно, разница мизерная. У тебя какой v8? Я в ноде тестирую, а ты?

kobezzza 06.07.2014 19:12

Цитата:

Сообщение от foo (Сообщение 319715)
странно, а у меня 180/170 примерно, разница мизерная. У тебя какой v8? Я в ноде тестирую, а ты?

node 0.11.13

Чем сложнее тело функции - тем потенциальна выше разница. JIT - это не 100% гарантия, но очень сильная возможность, а eval её полностью исключает.

В других VM такой же результат.

Пруф видео

foo 06.07.2014 19:37

Цитата:

Сообщение от kobezzza
node 0.11.13

Я в 0.10.7
Кстати, я заметил, что второй цикл раз от раза быстрей работает, заметно невооруженным глазом. Там кэширование наверное, на всю катушку работает. Не знаешь, как там кэш сбросить или отключить?

kobezzza 06.07.2014 19:43

Цитата:

Я в 0.10.7
Там уже очень старый V8 юзается, обнови.

Цитата:

Не знаешь, как там кэш сбросить или отключить?
Имеешь ввиду как сбросить работу JIT? Внести изменение в код, которое вынудит генерировать новый машинный код для участка байт кода. А вообще в каждой VM свой JIT-компилятор со своими плюсами, минусами и фишками. Например для Odin Monkey (JIT Spider Monkey в FF) я видел отдельный отладчик, который позволяет управлять работой JIT.


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