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?


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


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.


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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.