Não foi possível resolver o mistério das funções em Javascript

16

Estou tentando entender os bastidores do Javascript e meio que entendi a criação de objetos embutidos, especialmente Objeto e Função e a relação entre eles.

Quando li que todos os objetos incorporados, como Array, String etc., são extensão (herdada) do Object, assumi que Object é o primeiro objeto incorporado que é criado e o restante dos objetos herda. Mas não faz sentido quando você descobre que os Objetos só podem ser criados por funções, mas as funções também não passam de objetos da Função. Começou a soar como um dilema de galinha e galinha.

A outra coisa extremamente confusa é que, se eu console.log(Function.prototype)imprime uma função, mas quando imprimo console.log(Object.prototype), imprime um objeto. Por que Function.prototypeuma função quando deveria ser um objeto?

Além disso, de acordo com a documentação da Mozilla, todo javascript functioné extensão de Functionobjeto, mas quando você console.log(Function.prototype.constructor)é novamente uma função. Agora, como você pode usar algo para criar a si mesmo (Mente = queimado).

Última coisa, Function.prototypeé uma função, mas eu posso acessar a constructorfunção usando Function.prototype.constructorisso significa que Function.prototypeé uma função que retorna o prototypeobjeto

Umair Abid
fonte
como uma função é um objeto, isso significa que ela Function.prototypepode ser uma função e ter campos internos. Portanto, não, você não executa a função prototype ao passar por sua estrutura. Por fim, lembre-se de que existe um mecanismo para interpretar Javascript; portanto, Objeto e Função provavelmente são criados dentro do mecanismo e não a partir de Javascript e referências especiais como Function.prototypee Object.prototypepodem ser interpretados de maneira especial pelo mecanismo.
Walfrat 19/04
1
A menos que você esteja procurando implementar um compilador JavaScript compatível com os padrões, você realmente não precisa se preocupar com isso. Se você deseja obter algo útil, está fora do curso.
21418 Jared Smith
5
Para sua informação, a frase usual em inglês para "o dilema da galinha e da galinha" é "o problema do ovo e da galinha", a saber "o que veio primeiro, a galinha ou o ovo?" (Claro que a resposta é o ovo animais ovíparos foram em torno de milhões de anos antes de galinhas..)
Eric Lippert

Respostas:

32

Estou tentando entender os bastidores do Javascript e meio que entendi a criação de objetos incorporados, especialmente Objeto e Função e a relação entre eles.

É complicado, é fácil entender mal, e muitos livros Javascript para iniciantes entendem errado, portanto, não confie em tudo que lê.

Eu fui um dos implementadores do mecanismo JS da Microsoft na década de 90 e no comitê de padronização, e cometi vários erros ao montar essa resposta. (Embora eu não trabalhe nisso há mais de 15 anos, talvez eu possa ser perdoado.) É uma coisa complicada. Mas depois que você entende a herança do protótipo, tudo faz sentido.

Quando li que todos os objetos incorporados, como Array, String etc., são extensão (herdada) do Object, assumi que Object é o primeiro objeto incorporado que é criado e o restante dos objetos herda.

Comece jogando fora tudo o que você sabe sobre herança baseada em classe. JS usa herança baseada em protótipo.

Em seguida, verifique se você tem uma definição muito clara do que "herança" significa. As pessoas acostumadas a linguagens OO como C # ou Java ou C ++ acham que herança significa subtipagem, mas herança não significa subtipagem. Herança significa que os membros de uma coisa também são membros de outra coisa . Isso não significa necessariamente que exista uma relação de subtipagem entre essas coisas! Muitos mal-entendidos na teoria dos tipos são o resultado de pessoas que não percebem que há uma diferença.

Mas não faz sentido quando você descobre que os Objetos só podem ser criados por funções, mas as funções também não passam de objetos da Função.

Isto é simplesmente falso. Alguns objetos não são criados chamando new Fpor alguma função F. Alguns objetos são criados pelo tempo de execução JS do nada. Existem ovos que não foram postos por nenhuma galinha . Eles foram criados apenas pelo tempo de execução quando ele foi iniciado.

Digamos quais são as regras e talvez isso ajude.

  • Toda instância de objeto possui um objeto de protótipo.
  • Em alguns casos, esse protótipo pode ser null.
  • Se você acessar um membro em uma instância de objeto e o objeto não tiver esse membro, o objeto será diferido para seu protótipo ou será interrompido se o protótipo for nulo.
  • O prototypemembro de um objeto normalmente não é o protótipo do objeto.
  • Em vez disso, o prototypemembro de um objeto de função F é o objeto que se tornará o protótipo do objeto criado pornew F() .
  • Em algumas implementações, as instâncias obtêm um __proto__membro que realmente fornece seu protótipo. (Isso agora está obsoleto. Não confie nele.)
  • Os objetos de função recebem um novo objeto padrão atribuído ao prototypeserem criados.
  • O protótipo de um objeto de função é, é claro Function.prototype.

Vamos resumir.

  • O protótipo de ObjectéFunction.prototype
  • Object.prototype é o objeto de protótipo de objeto.
  • O protótipo de Object.prototypeénull
  • O protótipo de Functioné Function.prototype- esta é uma das raras situações em que Function.prototypeé realmente o protótipo de Function!
  • Function.prototype é o objeto de protótipo de função.
  • O protótipo de Function.prototypeéObject.prototype

Vamos supor que façamos uma função Foo.

  • O protótipo de Fooé Function.prototype.
  • Foo.prototype é o objeto de protótipo Foo.
  • O protótipo de Foo.prototypeé Object.prototype.

Vamos supor que dizemos new Foo()

  • O protótipo do novo objeto é Foo.prototype

Certifique-se de que faz sentido. Vamos desenhar. Ovais são instâncias de objetos. Arestas __proto__significam "o protótipo de" ou prototypesignificam "a prototypepropriedade de".

insira a descrição da imagem aqui

Tudo o que o tempo de execução precisa fazer é criar todos esses objetos e atribuir suas várias propriedades de acordo. Tenho certeza que você pode ver como isso seria feito.

Agora vamos ver um exemplo que testa seu conhecimento.

function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);

O que isso imprime?

Bem, o que instanceofsignifica? honda instanceof Carsignifica "é Car.prototypeigual a qualquer objeto emhonda cadeia de protótipos?"

Sim, ele é. hondaO protótipo deCar.prototype , então terminamos. Isso é verdadeiro.

E o segundo?

honda.constructornão existe, então consultamos o protótipo, que é Car.prototype. Quando o Car.prototypeobjeto foi criado, recebeu automaticamente uma propriedade constructorigual a Car, portanto isso é verdade.

Agora e quanto a isso?

var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);

O que esse programa imprime?

Novamente, lizard instanceof Reptilesignifica "é Reptile.prototypeigual a qualquer objeto na lizardcadeia de protótipos?"

Sim, ele é. lizardO protótipo éReptile.prototype , então terminamos. Isso é verdadeiro.

Agora, que tal

print(lizard.constructor == Reptile);

Você pode pensar que isso também é verdadeiro, pois lizardfoi construído com, new Reptilemas você estaria errado. Razão disso.

  • Tem lizarduma constructorpropriedade? Não. Portanto, olhamos para o protótipo.
  • O protótipo de lizardé Reptile.prototype, que é Animal.
  • Tem Animalumconstructor propriedade? Não. Então, olhamos para o seu protótipo.
  • O protótipo de Animalé Object.prototypee Object.prototype.constructoré criado pelo tempo de execução e igual a Object.
  • Então isso é falso.

Deveríamos ter dito Reptile.prototype.constructor = Reptile;em algum momento, mas não nos lembramos!

Certifique-se de que tudo faça sentido para você. Desenhe algumas caixas e setas se ainda estiver confuso.

A outra coisa extremamente confusa é que, se eu console.log(Function.prototype)imprime uma função, mas quando imprimo console.log(Object.prototype), imprime um objeto. Por que Function.prototypeuma função quando deveria ser um objeto?

O protótipo de função é definido como uma função que, quando chamada, retorna undefined. Já sabemos que esse Function.prototypeé o Functionprotótipo, por incrível que pareça. Portanto, Function.prototype()é legal, e quando você faz isso, você undefinedvolta. Então é uma função.

O Objectprotótipo não possui essa propriedade; não é exigível. É apenas um objeto.

quando você console.log(Function.prototype.constructor)é novamente uma função.

Function.prototype.constructoré apenas Function, obviamente. E Functioné uma função.

Agora, como você pode usar algo para criar a si mesmo (Mente = queimado).

Você está pensando demais nisso . Tudo o que é necessário é que o tempo de execução crie vários objetos ao iniciar. Objetos são apenas tabelas de pesquisa que associam seqüências de caracteres a objetos. Quando o tempo de execução inicia-se, tudo o que tem a fazer é criar alguns objetos dúzia em branco e, em seguida, começar a atribuir a prototype, __proto__, constructor, e assim por diante propriedades de cada objeto até que eles fazem o gráfico que eles precisam fazer.

Será útil se você pegar o diagrama que eu lhe dei acima e adicionar constructorarestas a ele. Você verá rapidamente que este é um gráfico de objeto muito simples e que o tempo de execução não terá problemas para criá-lo.

Um bom exercício seria fazer você mesmo. Aqui, eu vou começar você. Usaremos my__proto__para significar "o objeto protótipo de" e myprototypesignificar "a propriedade protótipo de".

var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;

E assim por diante. Você pode preencher o restante do programa para construir um conjunto de objetos com a mesma topologia dos objetos internos "reais" do Javascript? Se você fizer isso, verá que é extremamente fácil.

Objetos em JavaScript são apenas tabelas de pesquisa que associam seqüências de caracteres a outros objetos . É isso aí! Não há mágica aqui. Você está se amarrando porque está imaginando restrições que realmente não existem, como se todo objeto tivesse que ser criado por um construtor.

Funções são apenas objetos que possuem uma capacidade adicional: a serem chamados. Portanto, siga seu pequeno programa de simulação e adicione uma .mycallablepropriedade a cada objeto que indique se é possível chamar ou não. É simples assim.

Eric Lippert
fonte
9
Finalmente, uma explicação curta, concisa e fácil de entender do JavaScript! Excelente! Como algum de nós poderia estar confuso? :) Embora com toda a seriedade, a última parte sobre os objetos serem tabelas de pesquisa é realmente a chave. Há um método para a loucura --- mas ainda é loucura ...
Greg Burghardt
4
@ GregBurghardt: Concordo que parece complexo no começo, mas a complexidade é a consequência de regras simples. Todo objeto tem um __proto__. O __proto__protótipo do objeto é nulo. O __proto__de new X()é X.prototype. Todos os objetos de função possuem o protótipo de função, __proto__exceto o próprio protótipo de função. Objecte Functione o protótipo da função são funções. Essas regras são todas diretas e determinam a topologia do gráfico dos objetos iniciais.
precisa
6

Você já tem muitas respostas excelentes, mas só quero dar uma resposta curta e clara à sua resposta sobre como tudo isso funciona, e essa resposta é:

MAGIA!!!

Realmente é isso.

As pessoas que implementam os mecanismos de execução do ECMAScript precisam implementar as regras do ECMAScript, mas não cumpri- las em sua implementação.

A especificação ECMAScript diz que A herda de B, mas B é uma instância de A? Sem problemas! Crie A primeiro com um ponteiro de protótipo de NULL, crie B como uma instância de A e, em seguida, corrija o ponteiro de protótipo de A para apontar para B depois. Mole-mole.

Você diz, mas espere, não há como alterar o ponteiro do protótipo no ECMAScript! Mas, eis o seguinte: esse código não está sendo executado no mecanismo ECMAScript, esse código é o mecanismo ECMAScript. Ele não têm acesso a partes internas dos objetos que o código ECMAScript funcionamento no motor não tem. Em resumo: ele pode fazer o que quiser.

A propósito, se você realmente quiser, basta fazer isso uma vez: depois, você pode, por exemplo, descarregar sua memória interna e carregá-lo sempre que iniciar o mecanismo ECMAScript.

Observe que tudo isso ainda se aplica, mesmo que o próprio mecanismo ECMAScript tenha sido escrito em ECMAScript (como é realmente o caso do Mozilla Narcissus, por exemplo). Mesmo assim, o código ECMAScript que implementa o mecanismo ainda tem acesso total ao mecanismo que está implementando , embora, é claro, não tenha acesso ao mecanismo em que está sendo executado .

Jörg W Mittag
fonte
3

Da especificação 1 da ECMA

O ECMAScript não contém classes apropriadas, como as de C ++, Smalltalk ou Java, mas suporta construtores que criam objetos executando código que aloca armazenamento para os objetos e inicializa tudo ou parte deles atribuindo valores iniciais às suas propriedades. Todas as funções, incluindo construtores, são objetos, mas nem todos os objetos são construtores.

Não vejo como poderia ficar mais claro !!! </sarcasm>

Mais abaixo, vemos:

Protótipo Um protótipo é um objeto usado para implementar a herança de estrutura, estado e comportamento no ECMAScript. Quando um construtor cria um objeto, esse objeto faz referência implícita ao protótipo associado ao construtor com o objetivo de resolver as referências de propriedade. O protótipo associado ao construtor pode ser referenciado pela expressão do programa constructor.prototype, e as propriedades adicionadas ao protótipo de um objeto são compartilhadas, por herança, por todos os objetos que compartilham o protótipo.

Portanto, podemos ver que um protótipo é um objeto, mas não necessariamente um objeto de função.

Além disso, temos esse titbit interessante

http://www.ecma-international.org/ecma-262/8.0/index.html#sec-object-objects

O construtor Object é o objeto intrínseco% Object% e o valor inicial da propriedade Object do objeto global.

e

O construtor Function é o objeto intrínseco% Function% e o valor inicial da propriedade Function do objeto global.

Ewan
fonte
Agora sim. O ECMA6 permite criar classes e instanciar objetos a partir delas.
Ncmathsadist
2
As classes @ncmathsadist ES6 são apenas um açúcar sintático, a semântica é a mesma.
precisa saber é o seguinte
1
Seu sarcasmapelido, caso contrário, este texto é realmente bastante opaco para um iniciante.
19418 Robert Harvey
verdade, mal adicionar mais tarde, necessidade de fazer alguma escavação
Ewan
1
erm? de salientar que não está claro a partir do doc
Ewan
1

Os seguintes tipos abrangem todos os valores em JavaScript:

  • boolean
  • number
  • undefined(que inclui o valor único undefined)
  • string
  • symbol ("coisas" únicas abstratas que são comparadas por referência)
  • object

Todo objeto (ou seja, tudo) no JavaScript possui um protótipo, que é um tipo de objeto.

O protótipo contém funções, que também são um tipo de objeto 1 .

Os objetos também têm um construtor, que é uma função e, portanto, um tipo de objeto.

aninhado

É tudo recursivo, mas a implementação é capaz de fazer isso automaticamente, porque, diferentemente do código JavaScript, ele pode criar objetos sem precisar chamar funções JavaScript (já que os objetos são apenas memória que a implementação controla).

A maioria dos sistemas de objetos em muitas linguagens dinamicamente tipadas é circular 2 como esta. Por exemplo, no Python, classes são objetos e a classe de classes é type, portanto, typeé uma instância de si mesma.

A melhor idéia é usar as ferramentas fornecidas pela linguagem e não pensar muito em como elas chegaram lá.

1 As funções são bastante especiais porque são chamadas e são os únicos valores que podem conter dados opacos (seu corpo e possivelmente um fechamento).

2 Na verdade, é mais uma fita torturada e ramificada, dobrada para trás, mas "circular" está perto o suficiente.

Challenger5
fonte