Todo líder de opinião do JS diz que estender os objetos nativos é uma má prática. Mas por que? Temos um sucesso de desempenho? Eles temem que alguém faça "o caminho errado" e adiciona tipos inumeráveis Object
, praticamente destruindo todos os loops de qualquer objeto?
Tome TJ Holowaychuk 's should.js por exemplo. Ele adiciona um simples getter para Object
e tudo funciona bem ( fonte ).
Object.defineProperty(Object.prototype, 'should', {
set: function(){},
get: function(){
return new Assertion(Object(this).valueOf());
},
configurable: true
});
Isso realmente faz sentido. Por exemplo, pode-se estender Array
.
Array.defineProperty(Array.prototype, "remove", {
set: function(){},
get: function(){
return removeArrayElement.bind(this);
}
});
var arr = [0, 1, 2, 3, 4];
arr.remove(3);
Existem argumentos contra a extensão de tipos nativos?
javascript
prototype
prototypal-inheritance
buschtoens
fonte
fonte
Respostas:
Quando você estende um objeto, altera seu comportamento.
Alterar o comportamento de um objeto que será usado apenas pelo seu próprio código está correto. Mas quando você altera o comportamento de algo que também é usado por outro código, existe o risco de que você interrompa esse outro código.
Quando se trata de adicionar métodos às classes de objeto e matriz em javascript, o risco de quebrar algo é muito alto, devido à maneira como o javascript funciona. Longos anos de experiência me ensinaram que esse tipo de coisa causa todos os tipos de erros terríveis em javascript.
Se você precisar de um comportamento personalizado, é muito melhor definir sua própria classe (talvez uma subclasse) em vez de alterar uma nativa. Dessa forma, você não quebrará nada.
A capacidade de alterar como uma classe funciona sem subclassificar é uma característica importante de qualquer boa linguagem de programação, mas é uma que deve ser usada raramente e com cautela.
fonte
.stgringify()
seria considerado seguro?stringify()
método com comportamento diferente? Realmente não é algo que você deve fazer na programação diária ... não se tudo o que você quer fazer é salvar alguns caracteres de código aqui e ali. Melhor definir sua própria classe ou função que aceita qualquer entrada e a especifica. A maioria dos navegadores defineJSON.stringify()
, o melhor seria verificar se isso existe e se não definir você mesmo.someError.stringify()
mais do que issoerrors.stringify(someError)
. É simples e combina perfeitamente com o conceito de js. Estou fazendo algo especificamente vinculado a um determinado ErrorObject.Não há desvantagem mensurável, como um impacto no desempenho. Pelo menos ninguém mencionou nenhum. Portanto, esta é uma questão de preferência e experiências pessoais.
O principal argumento profissional: parece melhor e é mais intuitivo: açúcar de sintaxe. É uma função específica de tipo / instância, portanto, deve ser especificamente vinculada a esse tipo / instância.
O principal contra-argumento: o código pode interferir. Se a lib A adicionar uma função, ela poderá sobrescrever a função da lib B. Isso pode quebrar o código com muita facilidade.
Ambos têm razão. Quando você confia em duas bibliotecas que alteram diretamente seus tipos, provavelmente terminará com código quebrado, pois a funcionalidade esperada provavelmente não é a mesma. Eu concordo totalmente com isso. As bibliotecas de macros não devem manipular os tipos nativos. Caso contrário, você como desenvolvedor nunca saberá o que realmente está acontecendo nos bastidores.
E é por isso que não gosto de bibliotecas como jQuery, sublinhado, etc. Não me interpretem mal; eles são absolutamente bem programados e funcionam como um encanto, mas são grandes . Você usa apenas 10% deles e entende cerca de 1%.
É por isso que prefiro uma abordagem atomística , onde você só precisa do que realmente precisa. Dessa forma, você sempre sabe o que acontece. As micro-bibliotecas fazem apenas o que você quer que elas façam, para que não interfiram. No contexto em que o usuário final saiba quais recursos foram adicionados, a extensão de tipos nativos pode ser considerada segura.
TL; DR Em caso de dúvida, não estenda os tipos nativos. Somente estenda um tipo nativo se tiver 100% de certeza de que o usuário final conhecerá e desejará esse comportamento. Em nenhum caso, manipule as funções existentes de um tipo nativo, pois isso interromperia a interface existente.
Se você decidir estender o tipo, use
Object.defineProperty(obj, prop, desc)
; se não puder , use os tiposprototype
.Originalmente, eu fiz essa pergunta porque queria que
Error
s fossem enviados via JSON. Então, eu precisava de uma maneira de especificá-los.error.stringify()
me senti muito melhor do queerrorlib.stringify(error)
; como sugere o segundo construto, estou operandoerrorlib
e não sobreerror
si mesmo.fonte
MyError extends Error
ele puder ter umstringify
método sem colidir com outras subclasses. Você ainda precisa lidar com erros não gerados pelo seu próprio código, portanto, pode ser menos útil doError
que com outros.Na minha opinião, é uma má prática. A principal razão é a integração. Citando documentos should.js:
Bem, como o autor pode saber? E se minha estrutura de zombaria fizer o mesmo? E se minha lib de promessas fizer o mesmo?
Se você está fazendo isso em seu próprio projeto, tudo bem. Mas para uma biblioteca, é um design ruim. Underscore.js é um exemplo do que foi feito da maneira certa:
fonte
_(arr).flatten()
exemplo realmente me convenceu a não estender objetos nativos. Minha razão pessoal para fazer isso era puramente sintática. Mas isso satisfaz meu sentimento estético :) Mesmo usando um nome de função mais regular, comofoo(native).coolStuff()
para convertê-lo em algum objeto "estendido", parece ótimo sintaticamente. Então, obrigado por isso!A substituição de métodos já criados cria mais problemas do que resolve, e é por isso que geralmente é afirmado, em muitas linguagens de programação, para evitar essa prática. Como os Devs devem saber que a função foi alterada?
Nesse caso, não estamos substituindo nenhum método JS básico conhecido, mas estamos estendendo String. Um argumento neste post mencionou como o novo desenvolvedor deve saber se esse método faz parte do JS principal ou onde encontrar os documentos? O que aconteceria se o objeto JS String principal obtivesse um método chamado capitalize ?
E se, em vez de adicionar nomes que possam colidir com outras bibliotecas, você usasse um modificador específico de empresa / aplicativo que todos os desenvolvedores pudessem entender?
Se você continuasse estendendo outros objetos, todos os desenvolvedores os encontrariam na natureza com (nesse caso) nós , que os notificariam de que era uma extensão específica da empresa / aplicativo.
Isso não elimina colisões de nomes, mas reduz a possibilidade. Se você determinar que a extensão dos principais objetos JS é para você e / ou sua equipe, talvez seja para você.
fonte
Estender protótipos de built-ins é realmente uma má ideia. No entanto, o ES2015 introduziu uma nova técnica que pode ser utilizada para obter o comportamento desejado:
Utilizando
WeakMap
s para associar tipos a protótipos internosA implementação a seguir estende os protótipos
Number
eArray
sem tocá-los:Como você pode ver, até protótipos primitivos podem ser estendidos por essa técnica. Ele usa uma estrutura e
Object
identidade de mapa para associar tipos a protótipos internos.Meu exemplo habilita uma
reduce
função que apenas espera umArray
como argumento único, porque pode extrair as informações de como criar um acumulador vazio e como concatenar elementos com esse acumulador a partir dos elementos da própria matriz.Observe que eu poderia ter usado o
Map
tipo normal , pois as referências fracas não fazem sentido quando elas representam apenas protótipos internos, que nunca são coletados como lixo. No entanto, umWeakMap
não é iterável e não pode ser inspecionado, a menos que você tenha a chave certa. Esse é um recurso desejado, pois quero evitar qualquer forma de reflexão de tipo.fonte
xs[0]
em matrizes vazias tho.type(undefined).monoid ...
Object.prototype
dessa maneira não pode ser chamado em umArray
) e a sintaxe é significativamente mais longa / mais feia do que se você realmente estendesse um protótipo. Quase sempre seria mais simples criar apenas classes de utilitários com métodos estáticos; a única vantagem dessa abordagem é um suporte limitado ao polimorfismo.Mais uma razão pela qual você não deve estender objetos nativos:
Usamos o Magento, que usa prototype.js e estende muitas coisas sobre objetos nativos. Isso funciona bem até você decidir obter novos recursos e é aí que começam os grandes problemas.
Como apresentamos os componentes da Web em uma de nossas páginas, o webcomponents-lite.js decide substituir todo o objeto de evento (nativo) no IE (por quê?). É claro que isso quebra o prototype.js, que por sua vez quebra o Magento. (até encontrar o problema, você pode investir muitas horas rastreando-o de volta)
Se você gosta de problemas, continue fazendo isso!
fonte
Eu posso ver três razões para não fazer isso (de dentro de um aplicativo , pelo menos), apenas dois dos quais são abordados nas respostas existentes aqui:
Object.defineProperty
, o que cria propriedades não enumeráveis por padrão.O ponto 3 é sem dúvida o mais importante. Você pode garantir, por meio de testes, que suas extensões de protótipo não causem conflitos com as bibliotecas usadas, porque você decide quais bibliotecas você usa. O mesmo não ocorre com objetos nativos, supondo que seu código seja executado em um navegador. Se você definir
Array.prototype.swizzle(foo, bar)
hoje, e amanhã o Google adicionarArray.prototype.swizzle(bar, foo)
ao Chrome, é provável que você acabe com alguns colegas confusos que se perguntam por que.swizzle
o comportamento de um usuário parece não corresponder ao que está documentado no MDN.(Veja também a história de como os mootools brincando com protótipos que eles não possuíam forçaram um método ES6 a ser renomeado para evitar quebrar a web .)
Isso é evitável usando um prefixo específico do aplicativo para métodos adicionados a objetos nativos (por exemplo, defina em
Array.prototype.myappSwizzle
vez deArray.prototype.swizzle
), mas isso é meio feio; é tão fácil de resolver usando funções de utilitário independentes em vez de aumentar protótipos.fonte
Perf também é uma razão. Às vezes, você pode precisar fazer um loop sobre as teclas. Existem várias maneiras de fazer isso
Eu costumo usar
for of Object.keys()
como ele faz a coisa certa e é relativamente concisa, não há necessidade de adicionar a verificação.Mas, é muito mais lento .
Apenas supor que o motivo
Object.keys
é lento é óbvio,Object.keys()
tem que fazer uma alocação. De fato, o AFAIK precisa alocar uma cópia de todas as chaves desde então.É possível que o mecanismo JS possa usar algum tipo de estrutura de chave imutável, para que
Object.keys(object)
retorne uma referência que itere sobre chaves imutáveis e queobject.newProp
crie um objeto de chaves imutáveis totalmente novo, mas seja o que for, é claramente mais lento em até 15xAté a verificação
hasOwnProperty
é até 2x mais lenta.O ponto de tudo isso é que, se você tiver um código sensível ao desempenho e precisar fazer um loop sobre as chaves, poderá usar
for in
sem precisar chamarhasOwnProperty
. Você só pode fazer isso se não tiver modificadoObject.prototype
observe que se você
Object.defineProperty
modificar o protótipo se as coisas adicionadas não forem enumeráveis, elas não afetarão o comportamento do JavaScript nos casos acima. Infelizmente, pelo menos no Chrome 83, eles afetam o desempenho.Adicionei 3000 propriedades não enumeráveis apenas para tentar forçar a exibição de quaisquer problemas de perf. Com apenas 30 propriedades, os testes foram muito próximos para dizer se houve algum impacto no desempenho.
https://jsperf.com/does-adding-non-enumerable-properties-affect-perf
O Firefox 77 e o Safari 13.1 não mostraram diferença no perf entre as classes Augmented e Unaugmented. Talvez a v8 seja corrigida nessa área e você possa ignorar os problemas de perf.
Mas deixe-me acrescentar que há a história de
Array.prototype.smoosh
. A versão curta é a Mootools, uma biblioteca popular, criada por eles mesmosArray.prototype.flatten
. Quando o comitê de padrões tentou adicionar um nativo,Array.prototype.flatten
eles encontraram o impossível sem quebrar muitos sites. Os desenvolvedores que descobriram sobre o intervalo sugeriram nomear o método es5smoosh
como uma piada, mas as pessoas se assustaram ao não entender que era uma piada. Eles se estabeleceram emflat
vez deflatten
A moral da história é que você não deve estender objetos nativos. Caso contrário, você poderá encontrar o mesmo problema de quebra de suas coisas e, a menos que sua biblioteca em particular seja tão popular quanto o MooTools, é improvável que os fornecedores de navegadores contornem o problema que você causou. Se a sua biblioteca ficar tão popular, seria meio que forçar todo mundo a solucionar o problema que você causou. Portanto, não estenda objetos nativos
fonte
hasOwnProperty
informa se a propriedade está no objeto ou no protótipo. Mas ofor in
loop apenas fornece propriedades enumeráveis. Eu apenas tentei isso: adicione uma propriedade não enumerável ao protótipo Array e ele não aparecerá no loop. Não há necessidade não modificar o protótipo para o loop mais simples de trabalho.