Przejdź do treści

Backbone.js: problem z unikalnymi właściwościami obiektowymi

W dniu dzisiejszym Backbone.js zaskoczył mnie kolejny raz. Niestety tym razem negatywnie…

Baner promujący artykuł

Właściwości

Tworzenie “klas”, które dziedziczą po sobie właściwości jest czym naturalnym w Backbone. Nadpisywanie tychże właściwości jest równie proste. Przykład:

var AbstractWidget = Backbone.View.extend({
    template: 'foo.html',
});

var MenuWidget = AbstractWidget.extend({
    template: 'bar.html',
});

var v = new MenuWidget();
v.template; // bar.html

Niestety, to działa tylko wtedy, gdy wartość właściwości przypisujemy na nowo. Jeśli ją tylko modyfikujemy, zaczynają się problemy.

Problem

Inaczej sytuacja wygląda gdy mamy do czynienia z obiektami:

var AbstractWidget = Backbone.View.extend({});

var MenuWidget = AbstractWidget.extend({
    templateParams: {},

    initialize: function () {
        this.templateParams.id = _.uniqueId();
    },
});

var x = new MenuWidget();
var v = new MenuWidget();
x.templateParams.id; // "4"
v.templateParams.id; // "4"

Co ciekawe… gdy zamienimy kolejność wywołania ostatnich linijek:

var x = new MenuWidget();
x.templateParams.id; // "2"

var v = new MenuWidget();
v.templateParams.id; // "4"

// Ale gdy uruchamiamy następującą linię:
x.templateParams.id; // "4"

Wartości są stringami i co drugą liczbą, bo konstruktor Backbone.View przed initialize sam woła _.uniqueId('view'), żeby nadać widokowi cid.

Rozwiązanie

Problemy leży o podnóża działań JavaScript.

var AbstractWidget = Backbone.View.extend({});

var MenuWidget = AbstractWidget.extend({
    templateParams: {},

    initialize: function () {
        // czyścimy obiektowe właściwości
        this.templateParams = {};

        this.templateParams.id = _.uniqueId();
    },
});

var x = new MenuWidget();
var v = new MenuWidget();
x.templateParams.id; // "2"
v.templateParams.id; // "4"

Skąd ten problem? Właściwości przekazane do extend() trafiają do prototypu, więc wszystkie instancje MenuWidget współdzielą jeden i ten sam obiekt templateParams. Zapis this.templateParams.id = ... nie tworzy nowego obiektu w instancji, tylko modyfikuje ten wspólny. Przy template problem nie występował, bo przypisanie this.template = ... tworzy własną właściwość instancji i przesłania wartość z prototypu. Dlatego w initialize tworzymy każdej instancji jej własny obiekt templateParams.

Cya! 👋