Хорошо, после просмотра кода Knockout я выяснил, что происходит, и на момент написания статьи это не задокументировано.
Привязка value
, когда она читает значение элемента select
, не просто смотрит на значение DOM для элемента; звонит var elementValue = ko.selectExtensions.readValue(element);
Теперь, что неудивительно, selectExtensions
реализует специальное поведение для select
(и их дочерних object
) элементов. Вот где происходит волшебство, потому что, как говорится в комментарии в коде:
// Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
// are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
// that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
Итак, когда привязка значения пытается прочитать элемент select
через selectExtensions.readValue(...)
, он придет к следующему коду:
case 'select':
return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
Это в основном говорит: «Хорошо, найдите выбранный индекс и снова используйте эту функцию, чтобы прочитать элемент option
по этому индексу. Затем он читает элемент option
и приходит к следующему:
case 'option':
if (element[hasDomDataExpandoProperty] === true)
return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
return ko.utils.ieVersion <= 7
? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text)
: element.value;
Aha! Таким образом, он хранит свой собственный флаг «имеет свойство DOM data expando», и если он установлен, он НЕ получает простой element.value
, но идет в свою собственную память JavaScript и получает значение. Вот как он может вернуть сложный объект JS (например, объект еды в примере с моим вопросом) вместо только строки атрибута value
. Однако, если этот флаг не установлен, он действительно просто возвращает строку атрибута value
.
Расширение writeValue
, как и ожидалось, имеет другую сторону этого, где оно будет записывать сложные данные в память JS, если это не строка, но в противном случае оно просто сохранит их в атрибутной строке value
для option
switch (ko.utils.tagNameLower(element)) {
case 'option':
if (typeof value === "string") {
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
delete element[hasDomDataExpandoProperty];
}
element.value = value;
}
else {
// Store arbitrary object using DomData
ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
element[hasDomDataExpandoProperty] = true;
// Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
element.value = typeof value === "number" ? value : "";
}
break;
Так что, как я и подозревал, Knockout хранит сложные данные за кулисами, но только когда вы просите его сохранить сложный объект JS. Это объясняет, почему, когда вы не указываете optionsValue: [someStringValue]
, ваша вычисляемая функция получает сложный объект еды, тогда как, когда вы его указываете, вы просто передаете основную строку - Knockout просто дает вам строку из option
s value
атрибут.
Лично я думаю, что это должно быть ЯВНО задокументировано, потому что это немного неожиданное и особенное поведение, которое может сбить с толку, даже если это удобно. Я попрошу их добавить это в документацию.