At the bottom of the knockoutjs docs for extending observables it states...
More than one extender can be applied in a single call to the .extend method of an observable.
this.firstName = ko.observable(first).extend({ required: "Please enter a first name", logChange: "first name" });
In this case, both the required and logChange extenders would be executed against our observable.
... I want to know what order the extensions will be executed in. Will it always execute in the same order? What definesd the order?
If the order is important, you can call extend
multiple times:
this.firstName = ko.observable(first)
.extend({ required: "Please enter a first name" })
.extend({ logChange: "first name" });
That being said, browsers generally process an object's properties in the same order they were defined. Thus your original method will have the same effect as this one.
The order is undefined in the current Knockout (3.4.0 as of this writing), because JavaScript object properties had no defined order prior to ES2015, and even in ES2015, for-in
and Object.keys
have no order.
The current Knockout loops through those extenders like this:
function applyExtenders(requestedExtenders) {
var target = this;
if (requestedExtenders) {
ko.utils.objectForEach(requestedExtenders, function(key, value) {
var extenderHandler = ko.extenders[key];
if (typeof extenderHandler == 'function') {
target = extenderHandler(target, value) || target;
}
});
}
return target;
}
Its objectForEach
function uses for-in
:
function objectForEach(obj, action) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
action(prop, obj[prop]);
}
}
}
So, there is no defined order in which the extenders will run (even on ES2015) in the current Knockout.
In theory, a future version of Knockout could use the new Object.getOwnPropertyNames
instead:
function applyExtenders(requestedExtenders) {
var target = this;
if (requestedExtenders) {
Object.getOwnPropertyNames(requestedExtenders).forEach(function(key) {
var extenderHandler = ko.extenders[key];
if (typeof extenderHandler == 'function') {
target = extenderHandler(target, requestedExtenders[key]) || target;
}
});
}
return target;
}
getOwnPropertyNames
respects the newly-defined order of JavaScript properties, listed in §9.1.12 of the spec. In your example, that would be required
, then logChange
, because object initializers create properties in lexical order, and the order of "own" properties with names that are not all-numeric and not Symbol
s is the order of creation (but see the spec for details).