最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - ko observable object or function - Stack Overflow

programmeradmin0浏览0评论

I am trying to understand what is the difference between keeping the observable a function or setting it as an object

My observable item:

self.SelectedItem = ko.observable();

My view:

  <div class="collapse" data-bind="with: SelectedItem">
    @Html.Action("Profile", "Team")
  </div>
  <div class="collapse" data-bind="with: ItemForEditing">
    @Html.Action("EditProfile", "Team")
  </div>

selecting the item from grid :

ko.utils.extend(TeamManager.prototype, {
  selectItem: function (item) {   
    if (typeof item == "undefined") return;  
    this.SelectedItem(item);  
    //this.SelectedItem = item;  
    ko.mapping.fromJS(ko.mapping.toJS(item), this.ItemForEditing);   
  },

  acceptItem: function (itemData) {
    ko.mapping.fromJS(itemData, {}, this.SelectedItem);
  },

Why is my binding on the view only working if observable SelectedItem is called as a function, eg this.SelectedItem(item);, and not as an assignment, eg this.SelectedItem = item;. The latter causes the partial view to not render.

If I use it like this this.SelectedItem(item); it's all ok - why do I need to use it this way?

I am trying to understand what is the difference between keeping the observable a function or setting it as an object

My observable item:

self.SelectedItem = ko.observable();

My view:

  <div class="collapse" data-bind="with: SelectedItem">
    @Html.Action("Profile", "Team")
  </div>
  <div class="collapse" data-bind="with: ItemForEditing">
    @Html.Action("EditProfile", "Team")
  </div>

selecting the item from grid :

ko.utils.extend(TeamManager.prototype, {
  selectItem: function (item) {   
    if (typeof item == "undefined") return;  
    this.SelectedItem(item);  
    //this.SelectedItem = item;  
    ko.mapping.fromJS(ko.mapping.toJS(item), this.ItemForEditing);   
  },

  acceptItem: function (itemData) {
    ko.mapping.fromJS(itemData, {}, this.SelectedItem);
  },

Why is my binding on the view only working if observable SelectedItem is called as a function, eg this.SelectedItem(item);, and not as an assignment, eg this.SelectedItem = item;. The latter causes the partial view to not render.

If I use it like this this.SelectedItem(item); it's all ok - why do I need to use it this way?

Share Improve this question edited Jan 29, 2016 at 7:51 Mursa Catalin asked Jan 28, 2016 at 15:40 Mursa CatalinMursa Catalin 1,4492 gold badges23 silver badges39 bronze badges 3
  • And could you tell us what is stored in item? – Loïc Faure-Lacroix Commented Jan 29, 2016 at 10:12
  • item it is been taken from grid data-bind="click: $selectItem" it's an object with all properties observable self.TeamsModel($.map(data, function (item) { return ko.mapping.fromJS(item); })); – Mursa Catalin Commented Jan 29, 2016 at 10:17
  • All I can say is that it's hard to know why it's not rendering. Usually when something is wrong with knockout, you should see an error in the console. Anything? if you do console.log(item) what does it show?. May be the format of the object isn't correct. Most likely that somewhere you're using this.SelectedItem as an observable. If you set an object, somewhere, it will try to call SelectedItem and crash because objects aren't callable. – Loïc Faure-Lacroix Commented Jan 29, 2016 at 10:32
Add a ment  | 

2 Answers 2

Reset to default 4

In knockout.js, all observables are formed as a function containing a data. As there was no really easy wait to track changes on object with =, the dev of knockoutjs decided that instead of using getter/setter methods on objects, the use of function would be the easiest way to achieve tracking changes.

Getter/Setter on objects are new features that aren't available on older browsers, but calling a JavaScript function is available and you have access to the method that called this method aka arguments.callee.

https://developer.mozilla/fr/docs/Web/JavaScript/Reference/Fonctions/arguments/callee

In order to be able to achieve tracking, whenever you call the observable method, it will build a dependency tree using the callee and this way, KnockoutJS is able to know what to update whenever en observable is being called.

Let say you have this object:

t.fun = ko.Observable(10)

Whenever, you use the method. t.fun() you're actually calling the method but knockout would register this place as a place that requires to be called letter when the value of fun will change.

When you call t.fun(value) it will trigger a change and will call again those places that were already called.

This lead to why on knockout, every observable have to be called at least once, to catch any change. If you don't call in any way, t.fun() anywhere, knockoutjs doesn't have any idea that this part of your code depends on t.fun.

Also, whenever you affect t.fun to anything else..

t.foo = t.fun.

t.foo should work exactly like t.fun.

For that reason, if you set a value in your object. It's always a value an unlike some other languages, t.fun = 1 isn't updating t.fun but replacing the value of t.fun by 1. It's possible that JavaScript could do update on basic types, but in the case of Objects, you're affecting a plain new value and there is nothing JavaScript can do to know if the value changed.

AngularJS on the other hand, is checking for a value change by paring an old value to a new value. KnockoutJS instead is just tracking value changes so no parison is required in any way.

here's an example fiddle https://jsfiddle/gatnfbao/1/

var ViewModel = function(first, last) {
    this.firstName = first;
    this.lastName = ko.observable(last);

    this.fullName = ko.puted(function() {
        // Knockout tracks dependencies automatically. 
        // It knows that fullName depends on firstName and lastName,
        // because these get called when evaluating fullName.
        return this.firstName + " " + this.lastName();
    }, this);
};

ko.applyBindings(new ViewModel("Planet", "Earth")); // This makes

You can see that the puted field is using this.firstName and this.lastName(), it will tell knockout that the puted field is basing it self on lastName. What happens is that when knockoutjs boots, it will try to call every ko.puted variables to add them to the dependency tree. You can see that in our viewModel, the field fullname is correctly puted: Planet Earth!. Yet if you modify the field in the input text, it will not change anything in the ko.puted. But if you change the "Last Name" field, it will update the fullName taking into account of both fields because they are both present. And if you modify firstName you'll see the result only and only when lastName is changed.

But now, we'll change a bit the code to make you understand that when you modify lastName you see that the puted value use the correct value of firstName as a side effect of lastName being an observable but firstName wasn't... Lets remove all observables! https://jsfiddle/gatnfbao/2/

var ViewModel = function(first, last) {
    this.firstName = first;
    this.lastName = last;

    this.fullName = ko.puted(function() {
        return this.firstName + " " + this.lastName;
    }, this);
};

ko.applyBindings(new ViewModel("Planet", "Earth")); // This makes

Notice that now the puted field is using this.lastName not this.lastName(). Now knockout doesn't have any idea to what this puted value is depending on.

The ko.puted will be called once with the values Planet and Earth, but as it isn't using an observable, ko.puted has no idea what's going on in there. For that reason, if you modify the field firstName or lastName, then fullName is never updated. It was puted with the initial value but it wasn't containing any observable.

That's just how observables work. A knockout observable turns your property/variable into a function. To get the value from it, you have to call the function with no parameters. To set the value, you call it with the new value. The act of calling the function with the new value allows knockout to update any subscriptions, including your bindings.

When you do:

this.SelectedItem = item; 

SelectedItem is no longer an observable, it's been updated to point directly at the item, so no bindings will update.


Having said that, if you're working in a modern environment, one of the main authors of knockout has made a plugin that allows more usual property access, where an assignment would work.

发布评论

评论列表(0)

  1. 暂无评论