Por que meus nomes de função JavaScript estão conflitantes?

97

Eu escrevi o seguinte script apenas para ver o que acontece quando uma variável e uma função que tem uma função atribuída a ela têm seus nomes conflitantes:

var f = function() {
    console.log("Me original.");
}

function f() {
    console.log("Me duplicate.");
}

f();

A saída que estou recebendo é "Me original". Por que a outra função não foi chamada?

Além disso, se eu alterar minha atribuição original para var f = new function() {, recebo "Me original", seguido por uma mensagem TypeError object is not a function. Alguém pode explicar?

ankush981
fonte
26
@ Dean.DePue - Não há confusão por parte do JavaScript. As regras para lidar com eles são bastante claras (e explicadas por Benjamin em sua resposta).
Quentin de
4
Curiosidade, ainda é a melhor maneira de aprender um idioma. :-D
Cerbrus
2
Além disso, imagino que seja impossível para algo tão imaterial como "JavaScript" "sentir-se" confuso (ou qualquer emoção, por
falar nisso
2
Por que o içamento deveria inverter a ordem no segundo exemplo?
Cerbrus
5
Etapas para aumentar o conhecimento de javascript: 1) Use 'use strict' 2) Sempre use jslint ou jshint 3) Procure as coisas que reclamam jslint ou jshint 4) Enxágüe e repita
steve-er-rino

Respostas:

170

As declarações de função são içadas (movidas para o topo) em JavaScript. Embora incorreto em termos de ordem de análise, o código que você possui é semanticamente igual ao seguinte, já que as declarações de função são suspensas:

function f() {
    console.log("Me duplicate.");
}
var f = function() {
    console.log("Me original.");
}


f();

Que por sua vez, com exceção do nome da função é o mesmo que:

var f = function() {
    console.log("Me duplicate.");
}
var f = function() {
    console.log("Me original.");
}


f();

Que por sua vez, por causa do içamento variável é o mesmo que:

var f;
f = function() {
    console.log("Me duplicate.");
}
f = function() {
    console.log("Me original.");
}

f();

O que explica o que você está recebendo, você está substituindo a função. De forma mais geral, várias vardeclarações são permitidas em JavaScript -var x = 3; var x = 5 é perfeitamente legal. No novo padrão ECMAScript 6, as letdeclarações proíbem isso.

Este artigo de @kangax faz um trabalho fantástico em desmistificar funções em javascript

Benjamin Gruenbaum
fonte
2
Você pode realmente simplificar function f()a var f = function()tanto? Os nomes de içamento e função são realmente a única diferença?
Djechlin
6
@djechlin no contexto desta questão - sim. Geralmente, é mais sutil - consulte stackoverflow.com/questions/336859/… . Do ponto de vista do compilador, eles são diferentes - mas do ponto de vista do programador - estamos próximos o suficiente para afirmar isso. É por isso que adicionei esse longo "embora incorreto em termos de ordem de análise, o código que você tem é semanticamente o mesmo que" em vez de dizer "é o mesmo que". Bom ponto.
Benjamin Gruenbaum
1
@dotslash, por favor, não edite sua pergunta original e altere-a, isso é considerado falta de educação aqui - também, misturar várias perguntas em uma também é considerado falta de educação aqui. Em vez disso, você pode fazer uma nova pergunta ou, se achar que é muito pequena, pedir esclarecimentos nos comentários (de qualquer maneira, é para isso que servem). No código acima, ambas as versões de fsão içadas, e a "Me Original"versão é içada mais tarde , cada uma é movida para o topo, mas na mesma ordem. Gostaria apenas de acrescentar que, em geral, você não deve nomear várias funções da mesma maneira :)
Benjamin Gruenbaum
5
No modo estrito, você não pode ter varo mesmo nome duas vezes no mesmo escopo.
Hoffmann
4
"Isso deveria ser óbvio" - para você talvez, mas não era óbvio para mim em um ponto, e não era óbvio para OP quando ele perguntou, nomenclatura e, de maneira mais geral, como o ambiente léxico é gerenciado em JavaScript era um deles das coisas mais difíceis de entender ao aprender JavaScript para mim. Eu não seria tão rápido em insultar pessoas que não entendem isso.
Benjamin Gruenbaum
10

Se não parecer que alguém respondeu à sua pergunta de acompanhamento, vou respondê-la aqui, embora você deva fazer perguntas de acompanhamento como perguntas separadas.

Você perguntou por que isso:

var f = new function() {
    console.log("Me original.");
}

function f() {
    console.log("Me duplicate.");
}

f();

imprime "Me original". e então um erro.

O que está acontecendo aqui é que newfaz com que a função seja usada como um construtor. Portanto, isso é equivalente ao seguinte:

function myConstructor() {
    console.log("Me original.");
}
var f = new myConstructor();

function f() {
    console.log("Me duplicate.");
}

f();

E graças à função de içamento que Benjamin explicou, o acima é essencialmente equivalente a este:

var myConstructor = function() {
    console.log("Me original.");
};
var f = function() {
    console.log("Me duplicate.");
};

f = new myConstructor();

f();

Esta expressão:

var f = new function() {
    console.log("Me original.");
}

faz com que um novo objeto seja construído e atribuído a ele f, usando uma função anônima como o construtor. "Eu original." é impresso conforme o construtor é executado. Mas o objeto que é construído não é em si uma função, então, quando isso eventualmente for executado:

f();

você obtém um erro, porque fnão é uma função.

JLRishe
fonte
Oh maravilhoso! Muito obrigado por se dar ao trabalho de responder! :) :)
ankush981
2

Perdoe-me se esta é a maneira errada de abordar a adição de um ponto. Não tenho estado muito por aqui e gostaria de receber orientações e / ou críticas construtivas.

A resposta de Benjamin aborda a pergunta do OP de maneira excelente, mas eu gostaria de adicionar um ajuste que nos dará um tour completo de içamento e suas esquisitices.

Se começarmos o código original com uma chamada para f, assim:

f();

var f = function() {
   console.log("Me original.");
};

function f() {
   console.log("Me duplicate.");
}

f();

A saída será:

Me duplicate.
Me original.

A razão é que varefunction declarações são levantadas de maneiras ligeiramente diferentes.

Para vara declaração é movido para o topo do escopo atual *, mas qualquer atribuição não é içada. No que diz respeito ao valor do var declarado, ele é indefinido até que a linha de atribuição original seja alcançada.

Para functiondeclarações , tanto a declaração quanto a definição são suspensas. Expressões de função , conforme usadas novar f = function() {... construção, não são içadas.

Portanto, após o içamento, a execução é como se o código fosse:

var f; // declares var f, but does not assign it.

// name and define function f, shadowing the variable
function f() { 
  console.log("Me duplicate.");
}

// call the currently defined function f
f(); 

// assigns the result of a function expression to the var f,
// which shadows the hoisted function definition once past this point lexically
f = function() { 
  console.log("Me original."); 
}

// calls the function referenced by the var f
f();

* Todo escopo do JavaScript é léxico, ou função, escopo, mas parecia que apenas confundiria as coisas usar a palavra f naquele ponto.

codelahoma
fonte