Diferença entre os modelos de exibição knockout declarados como literais de objeto versus funções

195

Em knockout js, vejo os modelos de declaração declarados como:

var viewModel = {
    firstname: ko.observable("Bob")
};

ko.applyBindings(viewModel );

ou:

var viewModel = function() {
    this.firstname= ko.observable("Bob");
};

ko.applyBindings(new viewModel ());

Qual é a diferença entre os dois, se houver?

Eu encontrei essa discussão no grupo do Google do knockoutjs, mas isso realmente não me deu uma resposta satisfatória.

Eu posso ver um motivo para querer inicializar o modelo com alguns dados, por exemplo:

var viewModel = function(person) {
    this.firstname= ko.observable(person.firstname);
};

var person = ... ;
ko.applyBindings(new viewModel(person));

Mas se eu não estou fazendo isso, importa qual estilo eu escolho?

Kev
fonte
Não acredito que haja diferença. Normalmente, uso o padrão construtor, pois geralmente tenho métodos que prefiro declarar prototype(métodos que, por exemplo, costumam buscar dados do servidor e atualizar o modelo de exibição de acordo). No entanto, você ainda pode obviamente declará-los como propriedade de um objeto literal, portanto não vejo realmente diferença.
James Allardice
4
Isto não tem nada a ver com o nocaute, e tudo a ver com a facilidade de instanciação de objetos personalizados em JavaScript
zzzzBov
1
@ Kev, se o viewModel for uma função construtora, você a escreverá no UpperCase como var PersonViewModel = function () {...};
Elisabeth

Respostas:

252

Existem algumas vantagens em usar uma função para definir seu modelo de exibição.

A principal vantagem é que você tem acesso imediato a um valor thisigual à instância que está sendo criada. Isso significa que você pode fazer:

var ViewModel = function(first, last) {
  this.first = ko.observable(first);
  this.last = ko.observable(last);
  this.full = ko.computed(function() {
     return this.first() + " " + this.last();
  }, this);
};

Portanto, seu observável calculado pode ser vinculado ao valor apropriado de this, mesmo se chamado de um escopo diferente.

Com um objeto literal, você teria que fazer:

var viewModel = {
   first: ko.observable("Bob"),
   last: ko.observable("Smith"),
};

viewModel.full = ko.computed(function() {
   return this.first() + " " + this.last();
}, viewModel);

Nesse caso, você pode usar viewModeldiretamente no observável calculado, mas ele é avaliado imediatamente (por padrão) para que você não possa defini-lo no literal do objeto, como viewModelnão é definido até que o literal do objeto seja fechado. Muitas pessoas não gostam que a criação do seu modelo de visualização não seja encapsulada em uma chamada.

Outro padrão que você pode usar para garantir que thisseja sempre apropriado é definir uma variável na função igual ao valor apropriado de thise usá-la. Seria assim:

var ViewModel = function() {
    var self = this;
    this.items = ko.observableArray();
    this.removeItem = function(item) {
         self.items.remove(item);
    }
};

Agora, se você estiver no escopo de um item e chamada individual $root.removeItem, o valor de thisserá realmente os dados vinculados nesse nível (que seria o item). Usando self neste caso, você pode garantir que ele esteja sendo removido do modelo de vista geral.

Outra opção está usando bind, que é suportada por navegadores modernos e adicionada pelo KO, se não for suportada. Nesse caso, seria semelhante a:

var ViewModel = function() {
    this.items = ko.observableArray();
    this.removeItem = function(item) {
         this.items.remove(item);
    }.bind(this);
};

Há muito mais a ser dito sobre esse tópico e muitos padrões que você pode explorar (como padrão de módulo e padrão de módulo revelador), mas basicamente o uso de uma função oferece mais flexibilidade e controle sobre como o objeto é criado e a capacidade de referenciar variáveis ​​que são privadas para a instância.

RP Niemeyer
fonte
1
Ótima resposta. Costumo usar uma função (usando padrão de módulo revelador) para objetos complexos, como modelos de exibição. Mas para modelos simples, eu uso uma função para poder lidar com tudo em um só lugar.
John Papa
1
@JohnPapa - acabou de assistir seu vídeo do PluralSight por nocaute (pouco mais da metade) e, coincidentemente, apenas assistiu a seção sobre o objeto literal versus função). Muito bem feito e ajudou o centavo a cair. Vale a pena a assinatura de um mês apenas para isso.
Kev
@ Kev - Obrigado. Que bom que você está obtendo valor com isso. Alguns não se importam com esse módulo, pois não é realmente um conceito de Knockout, mas sim de padrões de JavaScript. Mas descobri que, ao me aprofundar no Knockout, esses conceitos realmente me ajudaram a criar um código mais limpo e estável. De qualquer forma, feliz que você se divertir :)
John Papa
não deve ser self.items = ko.observableArray (); no seu segundo exemplo? Você usou isso, está correto?
JackNova
1
@JackNova na função construtora selfe thissão os mesmos, portanto, ambos serão equivalentes. Na função removeItem, selftorna-se mais útil, pois thisnão seria mais a instância atual quando executada no contexto de um item filho.
RP Niemeyer
12

Eu uso um método diferente, embora semelhante:

var viewModel = (function () {
  var obj = {};
  obj.myVariable = ko.observable();
  obj.myComputed = ko.computed(function () { return "hello" + obj.myVariable() });

  ko.applyBindings(obj);
  return obj;
})();

Duas razões:

  1. Não usar this, o que pode confundir quando usado em ko.computeds etc
  2. Meu viewModel é um singleton, não preciso criar várias instâncias (por exemplo new viewModel())
paulslater19
fonte
Este é o padrão do módulo revelador, se não estiver errado. Boa resposta, mas a pergunta não era sobre esse padrão.
Phil
@ Paul: Desculpe pedir discussão antiga. Você disse, My viewModel is a singleton, I don't need to create multiple instances (i.e. new viewModel()) mas não está claro o que você está tentando dizer: I don't need to create multiple instances pode ser que você venha com mais uso para que se possa entender a vantagem de sua abordagem. obrigado
Mou 29/05
Na IMO, um dos motivos pelos quais você declararia o ViewModel como a functioné porque você o executaria mais de uma vez. No entanto, no meu exemplo sobre, é uma função anônima chamada imediatamente, portanto, não será criada mais de uma vez. É muito semelhante ao Literal de Objetos no exemplo acima, mas oferece mais isolamento
paulslater19 29/05/15