Como e por que 'a' ['toUpperCase'] () no JavaScript funciona?

209

O JavaScript continua me surpreendendo e essa é outra instância. Acabei de encontrar um código que não entendi a princípio. Então, eu depurei e cheguei a esta descoberta:

alert('a'['toUpperCase']());  //alerts 'A'

Agora isso deve ser óbvio se toUpperCase()for definido como um membro do tipo string, mas inicialmente não fazia sentido para mim.

De qualquer forma,

  • isso funciona porque toUpperCaseé um membro de 'a'? Ou há algo mais acontecendo nos bastidores?
  • o código que eu estava lendo tem uma função da seguinte maneira:

    function callMethod(method) {
        return function (obj) {
            return obj[method](); //**how can I be sure method will always be a member of obj**
        }
    }
    
    var caps2 = map(['a', 'b', 'c'], callMethod('toUpperCase')); // ['A','B','C'] 
    // ignoring details of map() function which essentially calls methods on every 
    // element of the array and forms another array of result and returns it
    

    É meio que uma função genérica chamar QUALQUER método em QUALQUER objeto. Mas isso significa que o método especificado já será um membro implícito do objeto especificado?

Estou certo de que estou perdendo algum entendimento sério do conceito básico de funções JavaScript. Por favor, ajude-me a entender isso.

Mahesha999
fonte
24
Existem duas maneiras de acessar as propriedades de um objeto: notação de ponto e colchete. Ligeiramente relacionado: stackoverflow.com/a/11922384/218196 . Você já conhece sobre notação de colchetes, porque você sempre usá-lo ao acessar elementos do array: arr[5]. Se os números de onde os nomes de identificador válido que você poderia usar a notação de ponto: arr.5.
precisa
2
É o mesmo que 5['toString']().
0x499602D2
1
Também relacionado: stackoverflow.com/q/4968406/218196 .
precisa
Leitura relacionada: 1) Herança e a cadeia de protótipos: developer.mozilla.org/en-US/docs/JavaScript/Guide/… 2) A vida secreta das primitivas de JavaScript: javascriptweblog.wordpress.com/2010/09/27/…
Theraot
21
Na primeira leitura, pensei que o título era "como e por que o JavaScript funciona?" Ah bem.
Problemática

Respostas:

293

Para quebrar.

  • .toUpperCase() é um método de String.prototype
  • 'a'é um valor primitivo, mas é convertido em sua representação de objeto
  • Temos duas notações possíveis para acessar propriedades / métodos de objetos, notação de ponto e colchete

assim

'a'['toUpperCase'];

é o acesso via notação de colchete na propriedade toUpperCase, de String.prototype. Como essa propriedade faz referência a um método , podemos invocá-lo anexando()

'a'['toUpperCase']();
jAndy
fonte
Isso seria uma pergunta da entrevista hilariante
FluffyBeing
78

foo.bare foo['bar']são iguais, então o código que você postou é o mesmo que

alert('a'.toUpperCase())

Ao usar foo[bar](observe a falta de aspas), você não usa o nome literal, barmas qualquer valor que a variável barcontenha. Portanto, o uso da foo[]notação em vez de foo.permite que você use um nome de propriedade dinâmico.


Vamos dar uma olhada em callMethod:

Primeiro de tudo, ele retorna uma função que recebe objcomo seu argumento. Quando essa função é executada, ele chama methodesse objeto. Portanto, o método fornecido apenas precisa existir em objsi mesmo ou em algum lugar de sua cadeia de protótipos.

Caso toUpperCaseesse método venha String.prototype.toUpperCase- seria estúpido ter uma cópia separada do método para cada string existente.

ThiefMaster
fonte
25

Você pode acessar os membros de qualquer objeto com .propertyNamenotação ou ["propertyName"]notação. Essa é a característica da linguagem JavaScript. Para ter certeza de que o membro está no objeto, basta verificar se está definido:

function callMethod(method) {
    return function (obj) {
        if (typeof(obj[method]) == 'function') //in that case, check if it is a function
           return obj[method](); //and then invoke it
    }
}
Artyom Neustroev
fonte
17

Basicamente, o javascript trata tudo como um objeto, ou melhor, todo objeto pode ser visto como um dicionário / matriz associativa. E funções / métodos são definidos exatamente da mesma maneira para o objeto - como uma entrada nessa matriz associativa.

Então, essencialmente, você está referenciando / chamando (observe a propriedade ' () ') 'toUpperCase', do objeto 'a' (que é um tipo de string, neste caso).

Aqui está um código do topo da minha cabeça:

function myObject(){
    this.msg = "hey there! ;-)";
    this.woop = function(){
        alert(this.msg); //do whatever with member data
    }
}

var obj = new myObject();
alert( obj.msg );
alert( obj['msg'] );
obj['woop']();
Nisk
fonte
10

anyObject['anyPropertyName']é o mesmo de anyObject.anyPropertyNamequando anyPropertyNamenão há caracteres problemáticos.

Consulte Trabalhando com objetos , no MDN.

O toUpperCasemétodo está anexado ao tipo String. Quando você chama uma função em um valor primitivo, aqui 'a', ela é automaticamente promovida a um objeto, aqui uma String :

Nos contextos em que um método deve ser chamado em uma sequência primitiva ou ocorre uma pesquisa de propriedades, o JavaScript quebra automaticamente a primitiva da sequência e chama o método ou executa a pesquisa de propriedades.

Você pode ver a função existente registrando-se String.prototype.toUpperCase.

Denys Séguret
fonte
9

Então, em Javascript, objectssão objects. Isso é que eles são desta natureza {}. As propriedades do objeto podem ser definidas usando um destes: a.greeting = 'hello';ou a['greeting'] = 'hello';. Os dois lados funcionam.

A recuperação funciona da mesma maneira. a.greeting(sem aspas) é 'hello', a['greeting']é 'hello'. Exceção: se a propriedade for um número, apenas o método bracket funcionará. O método de ponto não.

Então, 'a'é um objeto com 'toUpperCase'propriedade que é realmente uma função. Você pode recuperar a função e chamá-la posteriormente de qualquer maneira: 'a'.toUpperCase()ou 'a'['toUpperCase']().

Mas, na melhor das hipóteses, a melhor maneira de escrever a função de mapa seria:

var caps = ['a','b','c'].map( function(char) { return char.toUpperCase(); } )

Quem precisa da callMethodfunção?

Yaw Boakye
fonte
Lindamente explicado :-)
Elisha Senoo
6

Todo objeto JavaScript é uma tabela de hash, portanto, você pode acessar seus membros especificando uma chave. por exemplo, se uma variável for uma sequência, ela deverá ter a função toUpperCase. Então, você poderia invocá-lo

var str = "a"
str['toUpperCase'](). // you get the method by its name as a key and invoke it.

então, por str embutido, você poderia ter abaixo

"a"["toUpperCase"]()
Shuping
fonte
6

toUpperCase é um método javascript padrão: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toUpperCase

O motivo pelo qual funciona 'a'['toUpperCase']()é que a função toUpperCase é uma propriedade do objeto string 'a'. Você pode fazer referência às propriedades em um objeto usando object[property]ou object.property. A sintaxe 'a''toUpperCase' indica que você está referenciando a propriedade 'toUppercase' do objeto de string 'a' e, em seguida, chamando-o ().

SteveP
fonte
4

Mas isso significa que o método especificado já será um membro implícito do objeto especificado?

Não. Alguém poderia passar um objeto que

  1. não possui uma propriedade chamada toUpperCase; ou
  2. tem uma propriedade nomeada toUpperCaseque não é uma função

No primeiro caso, um erro será gerado porque o acesso a uma propriedade que não existe retorna undefinede não podemos invocar undefinedcomo uma função.

No segundo caso, um erro será gerado porque, novamente, não podemos chamar uma não-função como uma função.

Lembre-se de que o JavaScript é uma linguagem muito pouco digitada. Pouca ou nenhuma verificação de tipo ocorre, a menos e até que seja necessário. O código que você mostrou funciona em certos casos, porque, nesses casos, o objeto passado tem uma propriedade chamada toUpperCase, que é uma função.

O fato de o objargumento não ter garantia dos tipos corretos de propriedades não incomoda o JavaScript, por assim dizer. Adota uma atitude de "esperar para ver" e não gera um erro até que um problema real ocorra no tempo de execução.

LarsH
fonte
4

Quase tudo em javascript pode ser tratado como um objeto. No seu caso, o próprio alfabeto atua como um objeto de string e toUpperCasepode ser chamado como seu método. Os colchetes são apenas uma maneira alternativa de acessar as propriedades do objeto e, como toUpperCaseé um método, o simplebracket ()é necessário ao lado da ['toUpperCase']formação ['toUpperCase']().

'a'['toUpperCase']() é equivalente a 'a'.toUpperCase()

'a'['toUpperCase']() // returns A
'a'.toUpperCase() // returns A
Sanjay
fonte
3

O importante a ser observado aqui é que, como o Javascript é uma linguagem dinâmica , todo objeto é, essencialmente, apenas um mapa de hash glorificado ( com algumas exceções ). E tudo em um objeto Javascript pode ser acessado de duas maneiras - notação entre colchetes e notação de ponto.

Analisarei rapidamente as duas anotações que respondem à primeira parte da sua pergunta e depois chegarei à segunda parte.

Notação de colchete

Este modo é mais semelhante ao acesso a hashmaps e matrizes em outras linguagens de programação. Você pode acessar qualquer componente (dados (incluindo outros objetos) ou função) usando esta sintaxe.

É exatamente isso que você está fazendo no seu exemplo. Você tem 'a', que é uma string (e não um caractere literal, como seria em uma linguagem como C ++).

Usando a notação de colchete, você acessa seu toUpperCasemétodo. Mas acessá-lo ainda não é suficiente; simplesmente digitar alert, por exemplo, em Javascript, não chama o método É apenas uma afirmação simples. Para chamar a função, é necessário adicionar o parêntese: alert()mostra uma caixa de diálogo simples contendo undefined, pois não recebeu parâmetros. Agora podemos usar esse conhecimento para decifrar seu código, que se torna:

alert('a'.toUpperCase());

O que é muito mais legível.

Na verdade, uma boa maneira de entender isso um pouco melhor é executar o seguinte Javascript:

alert(alert)

Isso chama alertpassando um objeto de função, também alert, sem também executar o segundo alerta. O que é mostrado (no Chrome 26, pelo menos) é o seguinte:

function alert() { [native code] } 

A ligar:

alert(alert())

mostra duas caixas de mensagens consecutivas contendo undefined. Isso é fácil de explicar: o interior alert()é executado primeiro, mostra undefined(porque não tinha nenhum parâmetro) e não retorna nada. O alerta externo recebe o valor de retorno do alerta interno - o que não é nada e também é exibido undefinedem uma caixa de mensagem.

Experimente todos os casos no jsFiddle!

Notação de ponto

Essa é a abordagem mais padrão, que permite que membros de um objeto sejam acessados ​​usando o .operador dot ( ). É assim que seu código ficaria na notação de ponto:

alert('a'.toUpperCase())

Muito mais legível. Então, quando devemos usar a notação de ponto e quando devemos usar a notação entre colchetes?

Comparação

A principal diferença entre os dois métodos é semântica. Há também outros detalhes, mas chegarei a esses em um segundo. O mais importante é o que você realmente deseja fazer - uma regra geral é que você use notação de ponto para campos e métodos bem estabelecidos que um objeto possui e a notação de colchete para quando você estiver realmente usando seu objeto como um mapa de hash .

Um ótimo exemplo de por que essa regra é tão importante pode ser mostrado em seu exemplo - como o código está usando a notação de colchete em um local onde a notação de pontos teria sido muito mais sensata, torna o código mais difícil de ler. E isso é uma coisa ruim, porque o código é lido muito mais vezes do que está escrito .

Em alguns casos, você deve usar a notação de colchete, mesmo que a notação de ponto seja mais sensata:

  • se um membro de um objeto tiver um nome contendo um ou mais espaços ou qualquer outro caractere especial, você não poderá usar a notação de ponto: foo.some method()não funciona, mas foo["some method"]()funciona;

  • se você precisar acessar dinamicamente os membros de um objeto, também ficará preso usando a notação de colchete;

Exemplo:

for(var i = 0; i < 10; ++i) {
   foo["method" + i](); 
 }

A linha inferior é que você deve usar a sintaxe de colchete ao usar o objeto como um mapa de hash ( foods["burger"].eat()) e a sintaxe de ponto ao trabalhar com campos e métodos "reais" ( enemy.kill()). Com o Javascript sendo uma linguagem dinâmica, a linha entre os campos e métodos "reais" de um objeto e "outros" dados armazenados pode ficar bastante embaçada. Mas contanto que você não os misture de maneiras confusas, você ficará bem.


Agora, vamos ao resto da sua pergunta (finalmente!: P).

como posso ter certeza de que o método sempre será um membro da obj

Você não pode. Tente. Tente chamar derpuma string. Você receberá um erro nas linhas de:

Uncaught TypeError: Object a has no method 'derp' 

É meio que uma função genérica chamar QUALQUER método em QUALQUER objeto. Mas isso significa que o método especificado já será um membro implícito do objeto especificado?

Sim, no seu caso, teria que ser. Caso contrário, você terminará com o erro mencionado acima. No entanto, você não precisa usar return obj[method]();a callMethod()função Você pode adicionar sua própria funcionalidade que depois será usada pela função de mapa. Aqui está um método codificado que transforma todas as letras em uma letra maiúscula:

function makeCap()
{
    return function(obj) {
        return obj.toUpperCase();
    }
}

var caps2 = map(['a', 'b', 'c'], makeCap()); // ['A','B','C'] 
console.log(caps2)

O código no tutorial ao qual você vinculou usa funções parciais . Eles são um conceito complicado por si mesmos. Ler mais sobre esse assunto deve ajudar a tornar as coisas mais claras do que eu jamais poderia torná-las.


Nota: este é o código da função de mapa usado pelo código na pergunta, fonte aqui .

function map(arr, iterator) {
  var narr = [];
  for (var i = 0; i < arr.length; i++) narr.push(iterator(arr[i], i));
  return narr;
}
Andrei Bârsan
fonte
2

Se você está perguntando como realmente funciona, é assim que eu o leio. Ok, esta é uma função matemática simples. Para entendê-lo, você precisa olhar para a tabela ascii. O que atribui um valor numérico a cada letra. Para encobri-lo, a competição simplesmente usa uma declaração lógica para encobri-lo, por exemplo, If (ChcrValue> 80 && charValue <106) // Este é o conjunto de letras minúsculas e charValue = charValue - 38; // a distância entre o conjunto inferior e o conjunto superior

é simples assim, eu realmente não me incomodei em procurar os valores corretos, no entanto, isso está basicamente mudando todas as letras minúsculas para o valor maiúsculo.

Daniel Haro
fonte
Como isso está relacionado à questão?
Quasdunk #
2
@glh, ele perguntou como e por que 'a'['toUpperCase']()trabalhar. Mas o mal-entendido é compreensível, se alguém não leu a pergunta.
LarsH
resposta gr8, como nenhum estilo text-transform: uppercasefoi adicionado, eu estava no fim de como JS conseguiu mudar o caso, tirou a dúvida e aprendeu uma coisa ou duas. Muito obrigado.
dkjain