(1, eval) ('this') vs eval ('this') em JavaScript?

85

Começo a ler JavaScript Patterns , alguns códigos me confundem.

var global = (function () {
    return this || (1, eval)('this');
}());

Aqui estão minhas perguntas:

Q1:

(1, eval) === eval?

Por que e como isso funciona?

P2: Por que não apenas

var global = (function () {
    return this || eval('this');
}());

ou

 var global = (function () {
    return this;
}());
Shawjia
fonte
Eu atualizei o título porque este é um caso específico. Além disso, parênteses para o tipo específico de colchetes : [] e {} são totalmente diferentes :)

Respostas:

104

A diferença entre (1,eval)e simples evalé que o primeiro é um valor e o último é um lvalue. Seria mais óbvio se fosse algum outro identificador:

var x;
x = 1;
(1, x) = 1; //  syntax error, of course!

Essa é (1,eval)uma expressão que produz eval(como dizer, (true && eval)ou (0 ? 0 : eval)faria), mas não é uma referência a eval.

Por quê você se importa?

Bem, a Ecma especificação considera uma referência a evalser uma "chamada eval direta", mas uma expressão que apenas produz evala ser um indireto - e chamadas eval indiretos são garantidos para executar no âmbito global.

Coisas que ainda não sei:

  1. Em que circunstâncias uma chamada de avaliação direta não é executada no escopo global?
  2. Em que circunstância o resultado thisde uma função no escopo global não pode produzir o objeto global?

Mais algumas informações podem ser obtidas aqui .

EDITAR

Aparentemente, a resposta à minha primeira pergunta é "quase sempre". Um direto é evalexecutado a partir do escopo atual . Considere o seguinte código:

var x = 'outer';
(function() {
  var x = 'inner';
  eval('console.log("direct call: " + x)'); 
  (1,eval)('console.log("indirect call: " + x)'); 
})();

Não surpreendentemente (heh-heh), isso imprime:

direct call: inner
indirect call: outer

EDITAR

Após mais experiências, direi provisoriamente que thisnão pode ser definido como nullou undefined. Ela pode ser configurada para valores Falsas (0, '', NaN, false), mas só muito deliberadamente.

Vou dizer que sua fonte está sofrendo de uma inversão cranio-retal leve e reversível e pode querer passar uma semana programando em Haskell.

Malvolio
fonte
3
Uau, não sabia tudo sobre o valuevs lvalue(bem, talvez na prática, mas não em palavras). Nem as regras de avaliação ES5 (não que eu deva razoavelmente precisar usar evalsempre). Obrigado!
Stoive
Sim, evaltem muitas arestas afiadas e deve ser usado apenas como último recurso e então, com muito, muito cuidado.
Malvolio
Apenas uma vez encontrei um uso válido - avaliando uma tag de script que foi adicionada ao DOM viainnerHtml
Stoive
1
lvalue tem pouco a ver com a determinação de eval direta, visto que geralmente se refere à expressão que pode aparecer no lado esquerdo de uma atribuição, daí o nome lvalue em oposição a rvalue. Uma chamada para eval é direta apenas sob as condições listadas em 15.1.2.1.1 da especificação que diz que o identificador deve ser evale ser a parte MemberExpression de uma CallExpression e referir-se à evalfunção padrão .
chuckj
1
@Malvolio Você parece estar sugerindo que valores têm algo a ver com avaliação direta versus avaliação indireta, o que não é verdade. O uso de um identificador chamado evalcomo destino de uma expressão de chamada é especial. Você afirma que o ECMA trata a referência a evalespecial, o que não é. É o posicionamento na expressão de chamada que é especial e que a expressão avalia para a evalfunção padrão . Por exemplo, var eval = window.eval; eval('1');ainda é um eval direto e window.eval('1')não é, embora eval seja um lvalue neste caso também.
chuckj
33

O fragmento,

var global = (function () {  
    return this || (1, eval)('this');  
}());  

será avaliado corretamente para o objeto global, mesmo no modo estrito. No modo não estrito, o valor de thisé o objeto global, mas no modo estrito é undefined. A expressão (1, eval)('this')sempre será o objeto global. A razão para isso envolve as regras em torno dos versos indiretos diretos eval. Chamadas diretas para evaltêm o escopo do chamador e a string thisseria avaliada como o valor de thisno encerramento. Os indiretos evalsão avaliados em escopo global como se fossem executados dentro de uma função em escopo global. Uma vez que essa função não é ela própria uma função de modo estrito, o objeto global é passado como thise, em seguida, a expressão é 'this'avaliada como o objeto global. A expressão (1, eval)é apenas uma maneira elegante de forçar oeval para ser indireto e retornar o objeto global.

A1: (1, eval)('this')não é o mesmo por eval('this')causa das regras especiais em torno do verso indireto chamadas diretas para eval.

A2: O original funciona em modo estrito, as versões modificadas não.

chuckj
fonte
12

Para Q1:

Acho que este é um bom exemplo de operador vírgula em JS. Gosto da explicação para o operador vírgula neste artigo: http://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/

O operador vírgula avalia seus dois operandos (da esquerda para a direita) e retorna o valor do segundo operando.

Para Q2:

(1, eval)('this')é considerada uma chamada de avaliação indireta, que no ES5 executa o código globalmente. Portanto, o resultado será o contexto global.

Veja http://perfectionkills.com/global-eval-what-are-the-options/#evaling_in_global_scope

Grace Shao
fonte
7

P1: Várias instruções javascript consecutivas separadas por uma vírgula assumem o valor da última instrução. Então:

(1, eval)pega o valor do último que é uma referência de função para a eval()função. Aparentemente faz isso desta forma para transformar a eval()chamada em uma chamada de avaliação indireta que será avaliada no âmbito global no ES5. Detalhes explicados aqui .

P2: Deve haver algum ambiente que não define um global this, mas define eval('this'). Essa é a única razão que posso pensar para isso.

jfriend00
fonte
Talvez alguém esteja tentando se esquivar de um gancho de check-in que não permite /eval\(/g?
Stoive
@Stoive - sim, também me perguntei sobre algo assim. Se não for um gancho de check-in, algum filtro em algum lugar do processo (talvez minimização).
jfriend00
7
Tem algo a ver com o modo estrito ES5. AFAIK no modo estrito ES5, qualquer evalcódigo é executado em seu próprio contexto, e não no contexto global ou no contexto envolvente. Uma maneira de contornar isso é referenciá-lo indiretamente como faz o código em questão.
Cristian Sanchez
1
Dê uma olhada em " Avaliação global. Quais são as opções? ".
Saxoier
Atualizei minha resposta para incluir as informações de CDSanchez e @Saxoier. THX.
jfriend00