introduzir
Este capítulo é o segundo capítulo sobre a implementação orientada a objetos do ECMAScript. No primeiro capítulo, estamos discutindo a comparação entre Introdução e Cemascript. Se você não leu o primeiro capítulo, antes de prosseguir com este capítulo, recomendo fortemente que você leia o primeiro capítulo primeiro, porque este capítulo é muito longo (página 35).
Texto em inglês original: http://dmitrysohnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
Nota: Como o artigo é muito longo, os erros são inevitáveis e estão sendo constantemente corrigidos.
Na introdução, nos estendemos ao ECMAScript. Agora, quando sabemos que é a implementação da OOP, vamos defini -lo com precisão:
A cópia do código é a seguinte:
O ECMAScript é uma linguagem de programação orientada a objetos que suporta a herança delegadora com base em protótipos.
O ECMAScript é uma linguagem orientada a objetos que suporta herança delegada baseada em protótipo.
Analisaremos os tipos de dados mais básicos. A primeira coisa que precisamos entender é que o ECMAScript usa valores e objetos primitivos para distinguir entidades. Portanto, o "In JavaScript, tudo é um objeto" mencionado em alguns artigos está errado (não completamente correto), e o valor primitivo são alguns tipos de dados que discutiremos aqui.
Tipo de dados
Embora o ECMAScript seja um idioma dinâmico do tipo fraco que possa converter dinamicamente tipos, ele ainda possui tipos de dados. Em outras palavras, um objeto deve pertencer a um tipo real.
Existem 9 tipos de dados definidos na especificação padrão, mas apenas 6 são acessíveis diretamente nos programas ECMAScript. São eles: indefinido, nulo, booleano, string, número e objeto.
Os outros três tipos só podem ser acessados no nível de implementação (esses tipos não podem ser usados por objetos ECMAScript) e são usados para especificações para explicar algum comportamento operacional e salvar valores intermediários. Esses três tipos são: referência, lista e conclusão.
Portanto, a referência é usada para explicar os operadores como excluir, tipos e isso e contém um objeto base e um nome de atributo; A lista descreve o comportamento da lista de parâmetros (quando novas expressões e chamadas de função); A conclusão é usada para explicar a quebra do comportamento, continuar, retornar e arremessar declarações.
Tipo de valor primitivo
Olhando para os tipos de dados usados nos programas ECMAScript em 6, os 5 primeiros são tipos de valor primitivo, incluindo indefinidos, nulos, booleanos, string, número e objeto.
Exemplo do tipo de valor original:
A cópia do código é a seguinte:
var a = indefinido;
var b = nulo;
var c = true;
var d = 'teste';
var e = 10;
Esses valores são implementados diretamente na camada inferior, eles não são objetos; portanto, não há protótipo, nenhum construtor.
Tio Nota: Embora esses valores nativos sejam semelhantes aos que geralmente usamos (boolean, string, número, objeto), mas não são a mesma coisa. Portanto, os resultados do tipo de (verdadeiro) e tipo de (booleano) são diferentes, porque o resultado do tipo de (booleano) é uma função; portanto, também serão mencionadas as funções booleanas, string e número (o capítulo de atributos de leitura e gravação a seguir).
Se você quiser saber qual tipo de dados é o melhor, é melhor usar o TypeOf. Há um exemplo que precisa ser observado. Se você usar o tipoof para julgar o tipo de nulo, o resultado será objeto. Por que? Porque o tipo de nulo é definido como nulo.
A cópia do código é a seguinte:
alerta (tipo de nulo); // "objeto"
O motivo da exibição de "objeto" é porque a especificação estipula o seguinte: retornar "objeto" para o valor de string typeof do valor nulo.
A especificação não imagina explicar isso, mas Brendan Eich (inventor de JavaScript) notou que o NULL é usado principalmente para que os objetos apareçam em relação a indefinidos, como definir um objeto para uma referência nula. No entanto, alguns documentos têm alguns irritantes ao atribuí -lo a um bug e colocar o bug na lista de bugs que Brendan Eich também participou da discussão. O resultado é que, se você o deixar ir, definirá o resultado do tipo de nulo para objeto (embora o padrão 262-3 defina o tipo de nulo como nulo e o 262-5 mudou o padrão para nulo como objeto).
Tipo de objeto
Em seguida, o tipo de objeto (que não deve ser confundido com o construtor de objeto, agora apenas o tipo de abstração é discutido) é o único tipo de dados que descreve o objeto ECMAScript.
O objeto é uma coleção não ordenada de pares de valor-chave.
Objeto é uma coleção não ordenada contendo pares de valor-chave
O valor -chave de um objeto é chamado de propriedade e a propriedade é um contêiner do valor original e de outros objetos. Se o valor de uma propriedade for uma função, chamamos de método.
Por exemplo:
A cópia do código é a seguinte:
var x = {// o objeto "x" tem 3 propriedades: a, b, c
A: 10, // Valor original
B: {z: 100}, // O objeto "B" tem um atributo z
C: function () {// function (método)
alerta ('método x.c');
}
};
alerta (Xa); // 10
alerta (xb); // [objeto objeto]
alerta (xbz); // 100
xc (); // 'método x.c'
Dinâmico
Como apontamos no capítulo 17, os objetos em ES são completamente dinâmicos. Isso significa que, quando o programa é executado, podemos adicionar, modificar ou excluir as propriedades do objeto à vontade.
Por exemplo:
A cópia do código é a seguinte:
var foo = {x: 10};
// Adicione novos atributos
foo.y = 20;
console.log (foo); // {x: 10, y: 20}
// modifica o valor da propriedade para uma função
foo.x = function () {
console.log ('foo.x');
};
foo.x (); // 'foo.x'
// Excluir atributos
excluir foo.x;
console.log (foo); // {y: 20}
Algumas propriedades não podem ser modificadas - (propriedades somente de leitura, propriedades excluídas ou propriedades não confundíveis). Vamos explicar isso nas características do atributo posteriormente.
Além disso, a especificação ES5 estipula que objetos estáticos não podem estender novos atributos e suas páginas de propriedade não podem ser excluídas ou modificadas. Eles são os chamados objetos congelados, que podem ser obtidos aplicando o método do objeto.Freeze (O).
A cópia do código é a seguinte:
var foo = {x: 10};
// congelar o objeto
Object.freeze (foo);
console.log (object.isfrozen (foo)); // verdadeiro
// não pode ser modificado
foo.x = 100;
// não pode expandir
foo.y = 200;
// não pode ser excluído
excluir foo.x;
console.log (foo); // {x: 10}
Na especificação ES5, o método Object.PreventExtensions (O) também é usado para evitar extensões ou o método do objeto.DefineProperty (O) é usado para definir propriedades:
A cópia do código é a seguinte:
var foo = {x: 10};
Object.DefineProperty (Foo, "y", {
Valor: 20,
gravável: falso, // apenas leitura
Configurável: falso // não configurável
});
// não pode ser modificado
foo.y = 200;
// não pode ser excluído
excluir foo.y; // false
// Expansão de prevenção e controle
Object.PreventExtensions (Foo);
console.log (object.isextensible (foo)); // false
// não pode adicionar novos atributos
foo.z = 30;
console.log (foo); {x: 10, y: 20}
Objetos internos, objetos nativos e objetos host
É necessário observar que a especificação também distingue esses objetos internos, objetos de elemento e objetos de host.
Objetos e objetos de elemento internos são definidos e implementados pela especificação do ECMAScript, e a diferença entre os dois é trivial. Todos os objetos de implementação do ECMAScript são objetos nativos (alguns dos quais são objetos internos, alguns são criados quando o programa é executado, como objetos definidos pelo usuário). Um objeto embutido é um subconjunto do objeto nativo e é incorporado ao ECMAScript antes do início do programa (por exemplo, parseint, correspondência etc.). Todos os objetos host são fornecidos pelo ambiente do host, geralmente um navegador, e podem incluir, por exemplo, janela, alerta, etc.
Observe que o objeto host pode ser implementado pelo próprio ES e está totalmente em conformidade com a semântica normativa. Nesse sentido, eles podem ser chamados de objetos de "host nativo" (o mais rápido possível), mas a norma não define o conceito de objetos de "host nativo".
Objetos booleanos, string e numéricos
Além disso, a especificação também define algumas classes nativas de wrapper, esses objetos são:
1. Objeto booleano
2. Objeto de string
3. Objetos digitais
Esses objetos são criados pelo construtor interno correspondente e contêm valores nativos como suas propriedades internas. Esses objetos podem ser convertidos para salvar o valor original e vice -versa.
A cópia do código é a seguinte:
var c = novo booleano (verdadeiro);
var d = new string ('teste');
var e = novo número (10);
// converter para o valor original
// Use funções sem nova palavra -chave
с = booleano (c);
d = string (d);
e = número (e);
// Reconvert para objeto
с = objeto (c);
d = objeto (d);
e = objeto (e);
Além disso, existem objetos criados por construtores internos especiais: função (função construtor de objetos), matriz (construtor de matriz), regexp (construtor de expressão regular), matemática (módulo de matemática), data (construtor de data) etc. Esses objetos também são valores dos tipos de objetos. Suas diferenças são gerenciadas por propriedades internas. Discutiremos essas coisas abaixo.
Literal
Para os valores de três objetos: objeto, matriz e expressão regular, eles têm identificadores abreviados chamados de inicializador de objetos, inicializador da matriz e inicializador de expressão regular:
A cópia do código é a seguinte:
// equivalente a uma nova matriz (1, 2, 3);
// ou array = new Array ();
// matriz [0] = 1;
// matriz [1] = 2;
// matriz [2] = 3;
var array = [1, 2, 3];
// equivalente a
// var object = new Object ();
// object.a = 1;
// object.b = 2;
// object.c = 3;
var object = {a: 1, b: 2, c: 3};
// equivalente a novo regexp ("^// d+$", "g")
var re =/^/d+$/g;
Observe que, se os três objetos acima forem reatribuídos a um novo tipo, a semântica de implementação subsequente será usada de acordo com o novo tipo. Por exemplo, no rinoceronte atual e na versão antiga do Spidermonkey 1.7, os objetos serão criados com sucesso com o construtor da nova palavra -chave, mas a semântica de algumas implementações (spider/tracemonkey atual) pode não mudar após a alteração do tipo.
A cópia do código é a seguinte:
var getClass = object.prototype.toString;
Objeto = número;
var foo = novo objeto;
alerta ([foo, getclass.call (foo)]); // 0, "[Número do objeto]"
var bar = {};
// Rhino, Spidermonkey 1.7- 0, "[Número do objeto]"
// outros: ainda "[objeto objeto]", "[objeto objeto]"
alerta ([bar, getclass.call (bar)]);
// a matriz tem o mesmo efeito
Array = número;
foo = nova matriz;
alerta ([foo, getclass.call (foo)]); // 0, "[Número do objeto]"
bar = [];
// Rhino, Spidermonkey 1.7- 0, "[Número do objeto]"
// outros: ainda "", "[objeto]"
alerta ([bar, getclass.call (bar)]);
// Mas para regexp, a semântica dos literais não é alterada. semântica do literal
// não está sendo alterado em todas as implementações testadas
Regexp = número;
foo = novo regexp;
alerta ([foo, getclass.call (foo)]); // 0, "[Número do objeto]"
bar = /(?!) /g;
alerta ([bar, getclass.call (bar)]); ///(?!)/g, "[objeto regexp]"
Regex literais e objetos regexp
Observe que nos dois exemplos a seguir, na especificação da terceira edição, a semântica das expressões regulares é equivalente. Os literais regexp só existem em uma frase e são criados no estágio de análise. No entanto, o construtor regexp cria um novo objeto; portanto, isso pode causar alguns problemas, como o valor do LastIndex está errado durante o teste:
A cópia do código é a seguinte:
for (var k = 0; k <4; k ++) {
var re = /ecma /g;
alerta (re.LastIndex); // 0, 4, 0, 4
alerta (re.test ("ecmascript")); // verdadeiro, falso, verdadeiro, falso
}
// Compare
for (var k = 0; k <4; k ++) {
var re = novo regexp ("ecma", "g");
alerta (re.LastIndex); // 0, 0, 0, 0
alerta (re.test ("ecmascript")); // verdadeiro, verdadeiro, verdadeiro, verdadeiro
}
Nota: No entanto, esses problemas foram corrigidos na especificação ES na 5ª edição. Independentemente de serem baseados em literais ou construtores, eles criam novos objetos.
Matriz associativa
Várias discussões estáticas de texto, objetos JavaScript (geralmente criados com o Inicializador de Objetos {}) são chamados tabelas de hash e tabelas de hash ou outros títulos simples: Hash (conceitos em rubi ou perl), matrizes de gerenciamento (conceitos no PHP), dicionários (conceitos em python), etc.
Existem apenas esses termos, principalmente porque suas estruturas são semelhantes, ou seja, usando pares de "valor-chave" para armazenar objetos, que estão totalmente em conformidade com a estrutura de dados definida pela teoria da "matriz de associação" ou "tabela de hash". Além disso, os tipos de dados abstratos da tabela de hash são geralmente usados no nível de implementação.
No entanto, embora o conceito seja descrito em termos de termos, isso é realmente um erro. Da perspectiva do ECMAScript: o ECMAScript possui apenas um objeto, tipo e seu subtipo, que não é diferente de "valor-chave" para armazenamento, portanto, não há um conceito especial sobre isso. Porque as propriedades internas de qualquer objeto podem ser armazenadas como pares de valor-chave:
A cópia do código é a seguinte:
var a = {x: 10};
a ['y'] = 20;
AZ = 30;
var b = novo número (1);
bx = 10;
por = 20;
b ['z'] = 30;
var c = nova função ('');
cx = 10;
cy = 20;
c ['z'] = 30;
// etc., subtipo de qualquer objeto "subtipo"
Além disso, como os objetos podem estar vazios no ecmascript, o conceito de "hash" também está incorreto aqui:
A cópia do código é a seguinte:
Object.prototype.x = 10;
var a = {}; // Crie "hash" vazio
alerta (a ["x"]); // 10, mas não vazio
alerta (A.ToString); // função
a ["y"] = 20; // Adicione um novo par de key-value ao "hash"
alerta (a ["y"]); // 20
Object.prototype.y = 20; // Adicionar atributos de protótipo
excluir um ["y"]; // excluir
alerta (a ["y"]); // mas aqui a chave e o valor ainda valem 20
Observe que o padrão ES5 nos permite criar objetos desprotetiados (implementados usando o método Object.Create (NULL)). Nesta perspectiva, esses objetos podem ser chamados de tabelas de hash:
A cópia do código é a seguinte:
var ahashtable = object.create (null);
console.log (ahashtable.toString); // Indefinido
Além disso, algumas propriedades têm métodos específicos de getter/setter, para que também possa levar a confusão sobre esse conceito:
A cópia do código é a seguinte:
var a = new string ("foo");
a ['comprimento'] = 10;
alerta (a ['comprimento']); // 3
No entanto, mesmo que o "hash" possa ter um "protótipo" (por exemplo, uma classe que delega objetos de hash em rubi ou python), esse termo não está correto no ecmascript porque não há diferença semântica entre as duas anotações (ou seja, a notação do ponto ab e a ["b"].
O conceito de "atributo de propriedade" no ECMAScript não é semanticamente separado de "chave", índice de matriz e métodos. Aqui, toda a leitura e a escrita dos objetos de atributos devem seguir uma regra unificada: verifique a cadeia do protótipo.
No exemplo do rubi a seguir, podemos ver a diferença semântica:
A cópia do código é a seguinte:
a = {}
A.Class # Hash
A. Length # 0
# novo par de "valor-chave"
a ['comprimento'] = 10;
# Semanticamente, o atributo ou método que é acessado com um ponto, não uma chave
A. Length # 1
# O indexador acessa a chave no hash
a ['comprimento'] # 10
# É semelhante a declarar dinamicamente uma classe de hash em um objeto existente
# Então declare o novo atributo ou método
Hash de classe
Def Z.
100
fim
fim
# Novas propriedades são acessíveis
AZ # 100
# Mas não "chave"
a ['z'] # nil
O padrão ECMA-262-3 não define o conceito de "hash" (e similar). No entanto, se houver uma teoria tão estrutural, o objeto nomeado após isso pode ser.
Conversão de objetos
Para converter um objeto em um valor primitivo, você pode usar o método ValueOf. Como dissemos, quando o construtor da função é chamado de função (para alguns tipos), mas se você não usar a nova palavra -chave, converte o objeto no valor original, é equivalente a um valor implícito do método Chamada:
A cópia do código é a seguinte:
var a = novo número (1);
var primitivea = número (a); // chamada "Valorof" implícita
var alsoprimitivea = a.valueof (); // chamada explícita
alerta([
Tipo de A, // "Objeto"
tipo de primitivea, // "número"
Tipo de alsoprimitivea // "número"
]);
Este método permite que os objetos participem de várias operações, como:
A cópia do código é a seguinte:
var a = novo número (1);
var b = novo número (2);
alerta (a + b); // 3
// até
var c = {
x: 10,
y: 20,
valueof: function () {
retornar this.x + this.y;
}
};
var d = {
x: 30,
y: 40,
// a mesma função que o valor de C
Valorof: C.ValueOf
};
alerta (c + d); // 100
O valor padrão do valueof mudará de acordo com o tipo de objeto (se não for substituído). Para alguns objetos, ele retorna isso - por exemplo: object.prototype.valueof () e também o valor calculado: date.prototype.valueof () retorna data e hora:
A cópia do código é a seguinte:
var a = {};
alerta (a.valueOf () === A); // verdadeiro, "valueof" retorna isso
var d = new Date ();
alerta (d.valueOf ()); // tempo
alerta (d.valueOf () === D.GetTime ()); // verdadeiro
Além disso, o objeto possui uma representação mais primitiva - exibição de string. Este método de tostragem é confiável e é usado automaticamente em algumas operações:
A cópia do código é a seguinte:
var a = {
valueof: function () {
retornar 100;
},
ToString: function () {
retornar '__test';
}
};
// Nesta operação, o método da tostragem é chamado automaticamente
alerta (a); // "__teste"
// mas aqui, o método valueof () é chamado
alerta (a + 10); // 110
// no entanto, uma vez que o valorof é excluído
// ToString pode ser chamado automaticamente novamente
excluir a.valueof;
alerta (a + 10); // "_test10"
O método de tostragem definido no objeto. O prototipo tem um significado especial e retorna o valor interno do atributo [[class]] que discutiremos abaixo.
Comparado com a conversão em valores originais (toprimitivos), também há uma especificação de conversão (ToObject) para converter valores em tipos de objetos.
Um método explícito é usar o construtor de objeto interno como uma função para chamar ToObject (algo como usar a nova palavra-chave está OK):
A cópia do código é a seguinte:
var n = objeto (1); // [número do objeto]
var s = objeto ('teste'); // [string de objeto]
// Algumas semelhanças também são possíveis com o novo operador
var b = novo objeto (true); // [objeto booleano]
// Se você aplicar o novo objeto do parâmetro, você cria um objeto simples
var o = new Object (); // [objeto objeto]
// Se o parâmetro for um objeto existente
// O resultado da criação é simplesmente retornar o objeto
var a = [];
alerta (a === novo objeto (a)); // verdadeiro
alerta (a === objeto (a)); // verdadeiro
Em relação aos construtores de chamadas, não há regras comuns para usar ou não aplicar o novo operador, dependendo do construtor. Por exemplo, a matriz ou função usa um construtor do novo operador ou uma função simples que não usa o novo operador para produzir o mesmo resultado:
A cópia do código é a seguinte:
var a = matriz (1, 2, 3); // [matriz de objeto]
var b = nova matriz (1, 2, 3); // [matriz de objeto]
var c = [1, 2, 3]; // [matriz de objeto]
var d = function (''); // [função do objeto]
var e = nova função (''); // [função do objeto]
Quando alguns operadores são usados, também existem algumas conversões de exibição e implícitas:
A cópia do código é a seguinte:
var a = 1;
var b = 2;
// implícito
var c = a + b; // 3, número
var d = a + b + '5' // "35", string
// explícito
var e = '10'; // "10", string
var f = +e; // 10, número
var g = parseint (e, 10); // 10, número
// etc.
Propriedades dos atributos
Todas as propriedades podem ter muitos atributos.
1. {readonly} - ignore a operação de gravação de atribuir valores aos atributos, mas os atributos somente leitura podem ser alterados pelo comportamento do ambiente do host - ou seja, eles não são "valores constantes";
2. {Donntenum}-O atributo não pode ser enumerado por para ... em loop
3. {DONTDELETE}-O comportamento do operador de exclusão é ignorado (ou seja, não pode ser excluído);
4. {Internal} - Atributo interno, sem nome (usado apenas no nível de implementação), esses atributos não podem ser acessados no ECMAScript.
Observe que em es5 {readonly}, {dontenum} e {dontDelete} são renomeados para [[WRITED]], [[enumerável]] e [[configurável]], e essas propriedades podem ser gerenciadas manualmente através do objeto.DefineProperty ou métodos semelhantes.
A cópia do código é a seguinte:
var foo = {};
Object.DefineProperty (Foo, "X", {
Valor: 10,
gravável: verdadeiro, // ou seja, {readonly} = false
enumerável: false, // ou seja, {Donntenum} = true
Configurável: verdadeiro // ou seja, {dontDelete} = false
});
console.log (foo.x); // 10
// Obtenha os atributos através da descrição
var desc = object.getownPropertyDescriptor (Foo, "x");
console.log (descendente); // false
console.log (descendente); // verdadeiro
// etc.
Propriedades e métodos internos
Os objetos também podem ter propriedades internas (parte do nível de implementação) e os programas ECMAScript não podem acessar diretamente (mas veremos abaixo que algumas implementações permitem acesso a algumas dessas propriedades). Essas propriedades são acessadas através de colchetes aninhados [[]]. Vejamos algumas dessas propriedades. A descrição dessas propriedades pode ser encontrada na especificação.
Cada objeto deve implementar as seguintes propriedades e métodos internos:
1. [[Prototype]] - O protótipo do objeto (que será introduzido em detalhes abaixo)
2. [[Class]] - Uma representação de um objeto String (por exemplo, matriz de objeto, objeto de função, função, etc.); usado para distinguir objetos
3. [[Get]]-O método para obter valores de atributo
4. [[PUT]]-O método de definir valores de atributo
5. [[Canput]] - Verifique se o atributo é gravável
6. [[Hasproperty]]-Verifique se o objeto já possui essa propriedade
7. [[delete]] - Exclua esta propriedade do objeto
8. [[DefaultValue]] Retorna o valor original do objeto para (ligue para o método ValueOf, e alguns objetos podem lançar uma exceção do TypeError).
O valor da propriedade interna [[classe]] pode ser obtido indiretamente através do método object.prototype.toString (), que deve retornar a seguinte sequência: "[objeto" + [[class]] + "]". Por exemplo:
A cópia do código é a seguinte:
var getClass = object.prototype.toString;
getClass.call ({}); // [objeto objeto]
getClass.call ([]); // [matriz de objeto]
getClass.Call (novo número (1)); // [número do objeto]
// etc.
Essa função é geralmente usada para verificar os objetos, mas na especificação, a [[classe]] do objeto host pode ser qualquer valor, incluindo o valor do atributo [[class]] do objeto interno, portanto, teoricamente, não pode ser 100% preciso. Por exemplo, a propriedade [[Classe]] do Document.Childnodes.item (...) Método retorna "String" no IE, mas a "função" retornada em outras implementações é de fato.
A cópia do código é a seguinte:
// no ie - "string", em outros - "função"
alert (getclass.call (document.childnodes.item));
Construtor
Assim, como mencionamos acima, os objetos no ECMAScript são criados através dos chamados construtores.
O construtor é uma função que cria e inicializa o objeto recém -criado.
Um construtor é uma função que cria e inicializa objetos recém -criados.
A criação de objetos (alocação de memória) é de responsabilidade do método interno do construtor [[construto]]. O comportamento desse método interno é definido e todos os construtores usam esse método para alocar memória para novos objetos.
A inicialização é gerenciada chamando a função para cima e para baixo do novo objeto, responsável pelo método interno do construtor [[chamado]].
Observe que o código do usuário só pode ser acessado na fase de inicialização, embora possamos retornar objetos diferentes durante a fase de inicialização (ignorando os objetos TIHS criados na primeira fase):
A cópia do código é a seguinte:
função a () {
// Atualize objetos recém -criados
this.x = 10;
// mas o retorno é um objeto diferente
retornar [1, 2, 3];
}
var a = novo a ();
console.log (ax, a); indefinido, [1, 2, 3]
Referindo -se às funções do capítulo 15 - seção de algoritmo para criar funções, podemos ver que a função é um objeto nativo, incluindo as propriedades [[construct]]]] e [[chamadas]]], bem como o protótipo do protótipo exibido - o protótipo do pseud (note: o nativo é uma convenção para objetos nativos.
A cópia do código é a seguinte:
F = new NativeObject ();
F. [[Classe]] = "função"
.... // outros atributos
F. [[Call]] = <Referência à função> // Funciona em si
F. [[Construct]] = internalconstructor // Construtor interno comum
.... // outros atributos
// protótipo de objeto criado pelo construtor F
__ObjectPrototype = {};
__ObjectPrototype.Constructor = f // {donenmum}
F.Prototype = __ObjectPrototype
[[Call]]] é a principal maneira de distinguir objetos, exceto o atributo [[class]] (que é equivalente a "função" aqui), portanto o atributo interno [[chamado]] do objeto é chamado de função. Se esse objeto usar o operador TIPOOF, ele retornará "função". No entanto, está principalmente relacionado a objetos nativos. Em alguns casos, a implementação usa o TypeOf para obter o valor de maneira diferente, como o efeito da janela.Alet (...) no IE:
A cópia do código é a seguinte:
// IE navegador - "objeto", "objeto", outros navegadores - "função", "função"
alert (object.prototype.toString.call (window.alert));
alerta (tipoof window.alert); // "objeto"
O método interno [[Construct]] é ativado usando um construtor com um novo operador, como dissemos, é responsável pela alocação de memória e criação de objetos. Se não houver parâmetros, os colchetes para ligar para o construtor também podem ser omitidos:
A cópia do código é a seguinte:
função a (x) {// construtor а
this.x = x || 10;
}
// Se você não passa parâmetros, os suportes também podem ser omitidos
var a = novo a; // ou novo a ();
alerta (machado); // 10
// Passe explicitamente o parâmetro x
var b = novo a (20);
alerta (bx); // 20
Também sabemos que a xis no construtor (fase de inicialização) é definida como o objeto recém -criado.
Vamos estudar o algoritmo para a criação de objetos.
Algoritmo de criação de objetos
O comportamento do método interno [[Construct]] pode ser descrito da seguinte forma:
A cópia do código é a seguinte:
F. [[Construct]] (InitialParameters):
O = new nativeObject ();
// A propriedade [[classe]] está definida como "objeto"
O. [[Classe]] = "Objeto"
// Obtenha o objeto G ao se referir ao F.Prototype
var __ObjectProType = f.prototype;
// Se __ObjectPrototype for um objeto, então:
O. [[Prototype]] = __ObjectPrototype
// De outra forma:
O. [[Prototype]] = object.prototype;
// aqui o. [[Prototype]] é o protótipo do objeto objeto
// f. [[Call]] é aplicado quando o objeto recém -criado é inicializado
// defina isso como objeto recém -criado o
// Os parâmetros são os mesmos que os parâmetros iniciais em f
R = f. [[Call]] (InitialParameters); this === O;
// aqui r é o valor de retorno de [[chamado]]
// em js, assim:
// r = f.Apply (O, InitialParameters);
// se r é um objeto
retornar r
// De outra forma
retornar o
Observe dois recursos principais:
1. Primeiro, o protótipo do objeto recém -criado é obtido a partir da propriedade do protótipo da função no momento (isso significa que as propriedades do protótipo dos dois objetos criados criados pelo mesmo construtor podem ser diferentes porque as propriedades do protótipo da função também podem ser diferentes).
2. Em segundo lugar, como mencionamos acima, se [[Call]] retornar o objeto quando o objeto for inicializado, este é exatamente o resultado usado para todo o novo operador:
A cópia do código é a seguinte:
função a () {}
A.prototype.x = 10;
var a = novo a ();
alerta (machado); // 10 Obtenha -o do protótipo
// Defina a propriedade .prototype como um novo objeto
// Por que o atributo .Constructor declarado explicitamente será explicado abaixo
A.Prototype = {
Construtor: A,
y: 100
};
var b = novo a ();
// O objeto "B" tem novas propriedades
alerta (bx); // indefinido
alerta (por); // 100 Get do protótipo
// mas o protótipo do objeto a ainda pode obter o resultado original
alerta (machado); // 10 - Obtenha do protótipo
função b () {
this.x = 10;
retornar nova matriz ();
}
// se o construtor "B" não retornar (ou retornar isso)
// então este objeto pode ser usado, mas a seguinte situação retorna uma matriz
var b = novo b ();
alerta (bx); // indefinido
alert (object.prototype.toString.call (b)); // [matriz de objeto]
Vamos aprender mais sobre o protótipo
protótipo
Cada objeto possui um protótipo (exceto alguns objetos do sistema). A comunicação do protótipo é realizada por meio de acesso interno, implícito e indireto às propriedades [[protótipo]] do protótipo. O protótipo pode ser um objeto ou um valor nulo.
Construtor de propriedades
O exemplo acima tem dois pontos de conhecimento importantes. O primeiro é sobre a propriedade do protótipo da propriedade do construtor da função. No algoritmo da criação da função, sabemos que a propriedade do construtor é definida como a propriedade do protótipo da função durante o estágio de criação da função. O valor da propriedade do construtor é uma referência importante à própria função:
A cópia do código é a seguinte:
função a () {}
var a = novo a ();
alerta (a.constructor); // função a () {}, por delegação
alerta (a.constructor === a); // verdadeiro
Geralmente, neste caso, há um mal -entendido: é errado construir uma propriedade como o próprio objeto recém -criado, mas, como podemos ver, essa propriedade pertence ao protótipo e acessa o objeto por meio da herança.
Ao herdar uma instância do atributo construtor, você pode obter indiretamente uma referência ao objeto de protótipo:
A cópia do código é a seguinte:
função a () {}
A.prototype.x = novo número (10);
var a = novo a ();
alerta (a.constructor.prototype); // [objeto objeto]
alerta (machado); // 10, por protótipo
// o mesmo efeito que a. [[Prototype]]. X
alerta (a.constructor.prototype.x); // 10
alerta (a.constructor.prototype.x === AX); // verdadeiro
Mas observe que as propriedades do construtor e protótipo da função podem ser redefinidas após a criação do objeto. Nesse caso, o objeto perde o mecanismo mencionado acima. Se você editar o protótipo protótipo de um elemento através do atributo protótipo da função (adicione novos objetos ou modifique os objetos existentes), você verá o atributo recém -adicionado na instância.
No entanto, se alterarmos completamente a propriedade do protótipo da função (atribuindo um novo objeto), a referência ao construtor original é perdida, porque o objeto que criamos não inclui a propriedade do construtor:
A cópia do código é a seguinte:
função a () {}
A.Prototype = {
X: 10
};
var a = novo a ();
alerta (machado); // 10
alerta (a.constructor === a); // false!
Portanto, as referências de protótipo às funções precisam ser restauradas manualmente:
A cópia do código é a seguinte:
função a () {}
A.Prototype = {
Construtor: A,
X: 10
};
var a = novo a ();
alerta (machado); // 10
alerta (a.constructor === a); // verdadeiro
Observe que, embora o atributo do construtor seja restaurado manualmente, em comparação com o protótipo ausente original, o recurso {donnEnmum} se foi, ou seja, a declaração de loop for..in loop no a.prototipo não é mais suportada, mas no recurso de 5ª edição, o recurso [enumerável] fornece a capacidade de controlar o recurso enumerável.
A cópia do código é a seguinte:
var foo = {x: 10};
Object.DefineProperty (Foo, "y", {
Valor: 20,
enumerável: false // aka {Donntenum} = true
});
console.log (foo.x, foo.y); // 10, 20
para (var k em foo) {
console.log (k); // apenas "x"
}
var xdesc = object.getownPropertyDescriptor (Foo, "X");
var ydesc = object.getownPropertyDescriptor (Foo, "y");
console.log (
xdesc.enumerable, // true
ydesc.enumerable // false
);
Protótipo explícito e propriedades implícitas [[protótipo]]
Geralmente, é incorreto se referir explicitamente ao protótipo de um objeto através da propriedade Prototype da função. Refere -se ao mesmo objeto, a propriedade [[protótipo]] do objeto:
a. [[prototype]] ---> protótipo <---- a.prototipo
Além disso, o valor [[protótipo]] da instância é realmente obtido na propriedade do protótipo do construtor.
No entanto, o envio do atributo do protótipo não afetará o protótipo que foi criado (ele só afetará quando o atributo do protótipo das alterações do construtor), ou seja, o objeto recém -criado tiver um novo protótipo e o objeto criado ainda se referirá ao protótipo antigo original (esse protótipo não pode mais ser modificado).
A cópia do código é a seguinte:
// A situação antes de modificar o protótipo A.Prototype
a. [[prototype]] ---> protótipo <---- a.prototipo
// após modificação
A.prototype ---> novo protótipo // novo objeto terá este protótipo
a. [[prototipo]] ---> prototipo // no protótipo original da bota
Por exemplo:
A cópia do código é a seguinte:
função a () {}
A.prototype.x = 10;
var a = novo a ();
alerta (machado); // 10
A.Prototype = {
Construtor: A,
X: 20
y: 30
};
// Objeto A é um valor obtido do protótipo do petróleo bruto por meio de uma referência implícita [[protótipo]]
alerta (machado); // 10
alerta (ay) // indefinido
var b = novo a ();
// mas o novo objeto é o valor obtido do novo protótipo
alerta (bx); // 20
Alerta (por) // 30
Portanto, está errado para alguns artigos dizer que "a modificação dinâmica de protótipos afetará todos os objetos que possuem novos protótipos", e o novo protótipo só entra em vigor nos objetos criados recém -criados após o modificado do protótipo.
A regra principal aqui é: o protótipo do objeto é criado quando o objeto é criado e não pode ser modificado em um novo objeto depois disso. Se o mesmo objeto ainda for referenciado, ele poderá ser referenciado através do protótipo explícito do construtor. Depois que o objeto é criado, as propriedades do protótipo só podem ser adicionadas ou modificadas.
Atributos __proto__ não padrão
No entanto, algumas implementações (como Spidermonkey) fornecem propriedades explícitas __proto__ para referência ao protótipo do objeto:
A cópia do código é a seguinte:
função a () {}
A.prototype.x = 10;
var a = novo a ();
alerta (machado); // 10
var __NewPrototype = {
Construtor: A,
x: 20,
y: 30
};
// referência a um novo objeto
A.Prototype = __NewPrototype;
var b = novo a ();
alerta (bx); // 20
alerta (por); // 30
// O objeto "A" ainda está usando o protótipo antigo
alerta (machado); // 10
alerta (ay); // indefinido
// modifica explicitamente o protótipo
a .__ Proto__ = __NewPrototype;
// agora o objeto "A" refere -se a um novo objeto
alerta (machado); // 20
alerta (ay); // 30
注意,ES5提供了Object.getPrototypeOf(O)方法,该方法直接返回对象的[[Prototype]]属性――实例的初始原型。 然而,和__proto__相比,它只是getter,它不允许set值。
A cópia do código é a seguinte:
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // verdadeiro
对象独立于构造函数
因为实例的原型独立于构造函数和构造函数的prototype属性,构造函数完成了自己的主要工作(创建对象)以后可以删除。原型对象通过引用[[Prototype]]属性继续存在:
A cópia do código é a seguinte:
function A() {}
A.prototype.x = 10;
var a = novo a ();
alert(ax); // 10
// 设置A为null - 显示引用构造函数
A = null;
// 但如果.constructor属性没有改变的话,
// 依然可以通过它创建对象
var b = new a.constructor();
alert(bx); // 10
// 隐式的引用也删除掉
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
// 通过A的构造函数再也不能创建对象了
// 但这2个对象依然有自己的原型
alert(ax); // 10
alert(bx); // 10
instanceof操作符的特性
我们是通过构造函数的prototype属性来显示引用原型的,这和instanceof操作符有关。该操作符是和原型链一起工作的,而不是构造函数,考虑到这一点,当检测对象的时候往往会有误解:
A cópia do código é a seguinte:
if (foo instanceof Foo) {
...
}
这不是用来检测对象foo是否是用Foo构造函数创建的,所有instanceof运算符只需要一个对象属性――foo.[[Prototype]],在原型链中从Foo.prototype开始检查其是否存在。instanceof运算符是通过构造函数里的内部方法[[HasInstance]]来激活的。
让我们来看看这个例子:
A cópia do código é a seguinte:
function A() {}
A.prototype.x = 10;
var a = novo a ();
alert(ax); // 10
alert(a instanceof A); // verdadeiro
// 如果设置原型为null
A.prototype = null;
// ..."a"依然可以通过a.[[Prototype]]访问原型
alert(ax); // 10
// 不过,instanceof操作符不能再正常使用了
// 因为它是从构造函数的prototype属性来实现的
alert(a instanceof A); // 错误,A.prototype不是对象
另一方面,可以由构造函数来创建对象,但如果对象的[[Prototype]]属性和构造函数的prototype属性的值设置的是一样的话,instanceof检查的时候会返回true:
A cópia do código é a seguinte:
function B() {}
var b = new B();
alert(b instanceof B); // verdadeiro
function C() {}
var __proto = {
constructor: C
};
C.prototype = __proto;
b.__proto__ = __proto;
alert(b instanceof C); // verdadeiro
alert(b instanceof B); // false
原型可以存放方法并共享属性
大部分程序里使用原型是用来存储对象的方法、默认状态和共享对象的属性。
事实上,对象可以拥有自己的状态,但方法通常是一样的。 因此,为了内存优化,方法通常是在原型里定义的。 这意味着,这个构造函数创建的所有实例都可以共享找个方法。
A cópia do código é a seguinte:
function A(x) {
this.x = x || 100;
}
A.prototype = (function () {
// inicialize o contexto
// 使用额外的对象
var _someSharedVar = 500;
function _someHelper() {
alert('internal helper: ' + _someSharedVar);
}
function method1() {
alert('method1: ' + this.x);
}
function method2() {
alert('method2: ' + this.x);
_someHelper();
}
// 原型自身
retornar {
constructor: A,
method1: method1,
method2: method2
};
}) ();
var a = new A(10);
var b = new A(20);
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
// 2个对象使用的是原型里相同的方法
alert(a.method1 === b.method1); // verdadeiro
alert(a.method2 === b.method2); // verdadeiro
读写属性
正如我们提到,读取和写入属性值是通过内部的[[Get]]和[[Put]]方法。这些内部方法是通过属性访问器激活的:点标记法或者索引标记法:
A cópia do código é a seguinte:
// Escrever
foo.bar = 10; // 调用了[[Put]]
console.log(foo.bar); // 10, 调用了[[Get]]
console.log(foo['bar']); // 效果一样
让我们用伪代码来看一下这些方法是如何工作的:
[[Get]]方法
[[Get]]也会从原型链中查询属性,所以通过对象也可以访问原型中的属性。
O.[[Get]](P):
A cópia do código é a seguinte:
// 如果是自己的属性,就返回
if (O.hasOwnProperty(P)) {
return OP;
}
// 否则,继续分析原型
var __proto = O.[[Prototype]];
// 如果原型是null,返回undefined
// 这是可能的:最顶层Object.prototype.[[Prototype]]是null
if (__proto === null) {
return undefined;
}
// 否则,对原型链递归调用[[Get]],在各层的原型中查找属性
// 直到原型为null
return __proto.[[Get]](P)
请注意,因为[[Get]]在如下情况也会返回undefined:
A cópia do código é a seguinte:
if (window.someObject) {
...
}
这里,在window里没有找到someObject属性,然后会在原型里找,原型的原型里找,以此类推,如果都找不到,按照定义就返回undefined。
注意:in操作符也可以负责查找属性(也会查找原型链):
A cópia do código é a seguinte:
if ('someObject' in window) {
...
}
这有助于避免一些特殊问题:比如即便someObject存在,在someObject等于false的时候,第一轮检测就通不过。
[[Put]]方法
[[Put]]方法可以创建、更新对象自身的属性,并且掩盖原型里的同名属性。
O.[[Put]](P, V):
A cópia do código é a seguinte:
// 如果不能给属性写值,就退出
if (!O.[[CanPut]](P)) {
retornar;
}
// 如果对象没有自身的属性,就创建它
// 所有的attributes特性都是false
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributes: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Internal: false
});
}
// 如果属性存在就设置值,但不改变attributes特性
OP = V
retornar;
Por exemplo:
A cópia do código é a seguinte:
Object.prototype.x = 100;
var foo = {};
console.log(foo.x); // 100, 继承属性
foo.x = 10; // [[Colocar]]
console.log(foo.x); // 10, 自身属性
delete foo.x;
console.log(foo.x); // 重新是100,继承属性
请注意,不能掩盖原型里的只读属性,赋值结果将忽略,这是由内部方法[[CanPut]]控制的。
// 例如,属性length是只读的,我们来掩盖一下length试试
function SuperString() {
/* nada */
}
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, "abc"的长度
// 尝试掩盖
foo.length = 5;
console.log(foo.length); // 依然是3
但在ES5的严格模式下,如果掩盖只读属性的话,会保存TypeError错误。
属性访问器
内部方法[[Get]]和[[Put]]在ECMAScript里是通过点符号或者索引法来激活的,如果属性标示符是合法的名字的话,可以通过“.”来访问,而索引方运行动态定义名称。
A cópia do código é a seguinte:
var a = {testProperty: 10};
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10, 动态属性通过索引的方式
这里有一个非常重要的特性――属性访问器总是使用ToObject规范来对待“.”左边的值。这种隐式转化和这句“在JavaScript中一切都是对象”有关系,(然而,当我们已经知道了,JavaScript里不是所有的值都是对象)。
如果对原始值进行属性访问器取值,访问之前会先对原始值进行对象包装(包括原始值),然后通过包装的对象进行访问属性,属性访问以后,包装对象就会被删除。
Por exemplo:
A cópia do código é a seguinte:
var a = 10; // 原始值
// 但是可以访问方法(就像对象一样)
alert(a.toString()); // "10"
// 此外,我们可以在a上创建一个心属性
a.test = 100; // 好像是没问题的
// 但,[[Get]]方法没有返回该属性的值,返回的却是undefined
alert(a.test); // indefinido
那么,为什么整个例子里的原始值可以访问toString方法,而不能访问新创建的test属性呢?
答案很简单:
首先,正如我们所说,使用属性访问器以后,它已经不是原始值了,而是一个包装过的中间对象(整个例子是使用new Number(a)),而toString方法这时候是通过原型链查找到的:
A cópia do código é a seguinte:
// 执行a.toString()的原理:
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;
接下来,[[Put]]方法创建新属性时候,也是通过包装装的对象进行的:
A cópia do código é a seguinte:
// 执行a.test = 100的原理:
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;
我们看到,在第3步的时候,包装的对象以及删除了,随着新创建的属性页被删除了――删除包装对象本身。
然后使用[[Get]]获取test值的时候,再一次创建了包装对象,但这时候包装的对象已经没有test属性了,所以返回的是undefined:
A cópia do código é a seguinte:
// 执行a.test的原理:
1. wrapper = new Number(a);
2. wrapper.test; // indefinido
这种方式解释了原始值的读取方式,另外,任何原始值如果经常用在访问属性的话,时间效率考虑,都是直接用一个对象替代它;与此相反,如果不经常访问,或者只是用于计算的话,到可以保留这种形式。
herdar
我们知道,ECMAScript是使用基于原型的委托式继承。链和原型在原型链里已经提到过了。其实,所有委托的实现和原型链的查找分析都浓缩到[[Get]]方法了。
如果你完全理解[[Get]]方法,那JavaScript中的继承这个问题将不解自答了。
经常在论坛上谈论JavaScript中的继承时,我都是用一行代码来展示,事实上,我们不需要创建任何对象或函数,因为该语言已经是基于继承的了,代码如下:
A cópia do código é a seguinte:
alert(1..toString()); // "1"
我们已经知道了[[Get]]方法和属性访问器的原理了,我们来看看都发生了什么:
1.首先,从原始值1,通过new Number(1)创建包装对象
2.然后toString方法是从这个包装对象上继承得到的
为什么是继承的? 因为在ECMAScript中的对象可以有自己的属性,包装对象在这种情况下没有toString方法。 因此它是从原理里继承的,即Number.prototype。
注意有个微妙的地方,在上面的例子中的两个点不是一个错误。第一点是代表小数部分,第二个才是一个属性访问器:
A cópia do código é a seguinte:
1.toString(); // 语法错误!
(1).toString(); // OK
1..toString(); // OK
1['toString'](); // OK
原型链
让我们展示如何为用户定义对象创建原型链,非常简单:
A cópia do código é a seguinte:
função a () {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = novo a ();
alert([ax, ay]); // 10 (自身), 20 (继承)
function B() {}
// 最近的原型链方式就是设置对象的原型为另外一个新对象
B.prototype = new A();
// 修复原型的constructor属性,否则的话是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10, 20, 2个都是继承的
// [[Get]] bx:
// bx (no) -->
// b.[[Prototype]].x (yes) - 10
// [[Get]] by
// by (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype
这种方法有两个特性:
首先,B.prototype将包含x属性。乍一看这可能不对,你可能会想x属性是在A里定义的并且B构造函数也是这样期望的。尽管原型继承正常情况是没问题的,但B构造函数有时候可能不需要x属性,与基于class的继承相比,所有的属性都复制到后代子类里了。
尽管如此,如果有需要(模拟基于类的继承)将x属性赋给B构造函数创建的对象上,有一些方法,我们后来来展示其中一种方式。
其次,这不是一个特征而是缺点――子类原型创建的时候,构造函数的代码也执行了,我们可以看到消息"A.[[Call]] activated"显示了两次――当用A构造函数创建对象赋给B.prototype属性的时候,另外一场是a对象创建自身的时候!
下面的例子比较关键,在父类的构造函数抛出的异常:可能实际对象创建的时候需要检查吧,但很明显,同样的case,也就是就是使用这些父对象作为原型的时候就会出错。
A cópia do código é a seguinte:
function A(param) {
if (!param) {
throw 'Param required';
}
this.param = param;
}
A.prototype.x = 10;
var a = new A(20);
alert([ax, a.param]); // 10, 20
function B() {}
B.prototype = new A(); // Error
此外,在父类的构造函数有太多代码的话也是一种缺点。
解决这些“功能”和问题,程序员使用原型链的标准模式(下面展示),主要目的就是在中间包装构造函数的创建,这些包装构造函数的链里包含需要的原型。
A cópia do código é a seguinte:
função a () {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = novo a ();
alert([ax, ay]); // 10 (自身), 20 (集成)
function B() {
// 或者使用A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}
// 继承:通过空的中间构造函数将原型连在一起
var F = function () {};
F.prototype = A.prototype; // Citar
B.prototype = new F();
B.superproto = A.prototype; // 显示引用到另外一个原型上, "sugar"
// 修复原型的constructor属性,否则的就是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10 (自身), 20 (集成)
注意,我们在b实例上创建了自己的x属性,通过B.superproto.constructor调用父构造函数来引用新创建对象的上下文。
我们也修复了父构造函数在创建子原型的时候不需要的调用,此时,消息"A.[[Call]] activated"在需要的时候才会显示。
为了在原型链里重复相同的行为(中间构造函数创建,设置superproto,恢复原始构造函数),下面的模板可以封装成一个非常方面的工具函数,其目的是连接原型的时候不是根据构造函数的实际名称。
A cópia do código é a seguinte:
function inherit(child, parent) {
var F = function () {};
F.prototype = parent.prototype
child.prototype = new F();
child.prototype.constructor = child;
child.superproto = parent.prototype;
return child;
}
因此,继承:
A cópia do código é a seguinte:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A); // 连接原型
var b = new B();
alert(bx); // 10, 在A.prototype查找到
也有很多语法形式(包装而成),但所有的语法行都是为了减少上述代码里的行为。
例如,如果我们把中间的构造函数放到外面,就可以优化前面的代码(因此,只有一个函数被创建),然后重用它:
A cópia do código é a seguinte:
var inherit = (function(){
function F() {}
return function (child, parent) {
F.prototype = parent.prototype;
child.prototype = new F;
child.prototype.constructor = child;
child.superproto = parent.prototype;
return child;
};
}) ();
由于对象的真实原型是[[Prototype]]属性,这意味着F.prototype可以很容易修改和重用,因为通过new F创建的child.prototype可以从child.prototype的当前值里获取[[Prototype]]:
A cópia do código é a seguinte:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A);
B.prototype.y = 20;
B.prototype.foo = function () {
alert("B#foo");
};
var b = new B();
alert(bx); // 10, 在A.prototype里查到
function C() {}
inherit(C, B);
// 使用"superproto"语法糖
// 调用父原型的同名方法
C.ptototype.foo = function () {
C.superproto.foo.call(this);
alert("C#foo");
};
var c = new C();
alert([cx, cy]); // 10, 20
c.foo(); // B#foo, C#foo
注意,ES5为原型链标准化了这个工具函数,那就是Object.create方法。ES3可以使用以下方式实现:
A cópia do código é a seguinte:
Object.create ||
Object.create = function (parent, properties) {
function F() {}
F.prototype = parent;
var child = new F;
for (var k in properties) {
child[k] = properties[k].value;
}
return child;
}
// 用法
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20
此外,所有模仿现在基于类的经典继承方式都是根据这个原则实现的,现在可以看到,它实际上不是基于类的继承,而是连接原型的一个很方便的代码重用。
para concluir
本章内容已经很充分和详细了,希望这些资料对你有用,并且消除你对ECMAScript的疑问,如果你有任何问题,请留言,我们一起讨论。