Если Knockout оборачивает выражение ограниченной функции в вычисляемое, почему оно не всегда создает зависимость?

Это дополнительный вопрос к этому один:

Как объяснено в приведенном выше ответе:

When you provide an expression for a binding value rather than just a reference to an observable, KO effectively wraps that expression in a computed when applying the bindings.

Таким образом, я ожидал, что при предоставлении changeCity в качестве выражения привязки (это функция, а не наблюдаемая), изменение значения в поле input вызовет функцию changeCity.

Однако, как вы можете видеть в первом фрагменте, это не так (и при привязке его как changeCity()), но если changeCity объявлен как ko.computed, он срабатывает - см. Второй фрагмент.

Означает ли это, что ограниченная функция и ограниченная вычисляемая не полностью идентичны в отношении отслеживания зависимостей?

Первый фрагмент - ограниченная функция:

var handlerVM = function () {
   var self = this;
   self.city = ko.observable("London");
   self.country = ko.observable("England");
   self.changeCity = function () {
     if (self.country() == "England") {
       self.city("London");
     } else {
       self.city("NYC");
     }
   } 
}
ko.applyBindings(new handlerVM());
<script src = "https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h3 data-bind = "text: city"> </h1>
 <span data-bind = "text: 'change the country, get out of focus, and nothing will happen...'"></span>
<br/>
<input data-bind = "value: country" />

Второй фрагмент - вычислено ограниченное:

var handlerVM = function () {
   var self = this;
   self.city = ko.observable("London");
   self.country = ko.observable("England");
   self.changeCity = ko.computed(function () {
     if (self.country() == "England") {
       self.city("London");
     } else {
       self.city("NYC")
     }
   }); 
}
ko.applyBindings(new handlerVM());
<script src = "https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h3 data-bind = "text: city"> </h1>
<span data-bind = "text: 'change the country, get out of focus, and behold:'"> </span>
<br/>
<input data-bind = "value: country" />
ko.applyBindings(handlerVM); не правильный. Вы не можете просто передать функцию конструктора, вам нужно фактически Создайте модели просмотра. ko.applyBindings(new handlerVM());
Tomalak 19.03.2018 15:24

@Tomalak, спасибо, исправлено

OfirD 19.03.2018 15:28
«Означает ли это, что ограниченная функция и вычисляемая ограниченная функция - не одно и то же в отношении отслеживания зависимостей?» - Да, именно это и означает. Обычная функция не отслеживает зависимости и не может иметь подписчиков. Если вам нужен побочный эффект только для записи (например, установка наблюдаемого значения при нажатии кнопки), можно использовать простую функцию. Если вы хотите вычислить возвращаемое значение из других наблюдаемых (например, фильтрация наблюдаемого массива с полем ввода), использование ko.computed будет правильным решением.
Tomalak 19.03.2018 15:35

@Tomalak, я смущен, так как ответ, который я связал, говорит с точностью до наоборот, т.е. поскольку ограниченная функция обернута в вычисляемую, она делает имеет отслеживание зависимостей.

OfirD 19.03.2018 16:13

Но «вычисляемый» - это именно то, что: функция, заключенная в ko.computed(). В отличие от self.changeCity = function ..., который выполняет простую функцию. В последнем нет отслеживания зависимостей, в первом есть.

Tomalak 19.03.2018 16:16

Разница, по-видимому, заключается в том, как knockout обрабатывает встроенные выражения, определенные непосредственно в разметке. Я считаю, что ответ, который вы указали, конкретно относился к этому делу. Однако это всего лишь обоснованное предположение; У меня нет источника об этом

Jason Spake 19.03.2018 17:14

В любом случае вы не можете привязать вычисленное к обработчику событий. Это бессмысленно. Что вы ожидаете увидеть во втором примере кода? Вычисленное вычисляет значение, и это его цель. Посылать на него события кликов не имеет смысла. (Кроме того, я не очень понимаю, почему у вас кнопки disabled: true, это тоже не имеет никакого смысла)

Tomalak 19.03.2018 17:30

@Tomalak, ожидаемое поведение в обоих фрагментах заключается в том, что после изменения текста в поле ввода (и смещения фокуса) changeCity запускается (происходит 2-го, а не 1-го). Кнопки отключены, чтобы указать, что нажатие кнопки не требуется для запуска changeCity, но я согласен, что это сбивает с толку, я отредактирую его.

OfirD 19.03.2018 17:35
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
8
127
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

the expected behavior in both snippets is that once the text in the input box is changed (and the focus is out), changeCity is fired (Happens on the 2nd, not on the 1st).

Ааа, теперь я понимаю. Вы описываете, что делает подписка.

Во-первых, избавьтесь от событий DOM. Поле <input> не существует. Все, что есть, - это ваша модель просмотра. (*)

С таким мышлением ясно, что делать: реагировать на изменения в вашем свойстве country через .subscribe(). Следующее делает то, что вы имеете в виду.

var handlerVM = function () {
  var self = this;

  self.city = ko.observable("London");
  self.country = ko.observable("England");

  self.country.subscribe(function (newValue) {
    switch (newValue.toLowerCase()) {
      case "england":
        self.city("London");
        break;
      case "usa":
        self.city("NYC");
        break;
      default:
        self.city("(unknown)");
    }
  });
}

ko.applyBindings(new handlerVM());
<script src = "https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<h3 data-bind = "text: city"></h3>
<input data-bind = "value: country" />

(*) Конечно, поле <input> все еще существует. Но это помогает представить представление (ваш HTML) на 100% зависимым от вашей модели представления. Knockout делает за вас все взаимодействие "модель-представление". Он заботится об отображении изменений в данных модели просмотра и заботится об обратном взаимодействии пользователя с вашей моделью просмотра. Все, на что вам следует обратить внимание, это изменения в вашей модели просмотра.

Всякий раз, когда вы чувствуете, что вам нужно прослушать базовое событие DOM, такое как «щелчок», есть вероятность, что вы делаете что-то не так, то есть, скорее всего, вам не хватает наблюдаемого или пользовательская привязка.

Спасибо, Томалак, но, кажется, меня все еще неправильно поняли. Я пытаюсь уладить то, что вы сказали (обычная функция не отслеживает зависимости), завершив связанный выше ответ, цитируя: «поскольку func использует значение наблюдаемого [...], отслеживание изменений KO видит и запоминает этот факт при вычислении вычисленного значения».

OfirD 20.03.2018 00:21

Прости, ты не имеешь смысла. Согласно вашему комментарию, вы хотите, чтобы что-то произошло, и я снова цитирую "после того, как текст в поле ввода будет изменен (и фокус исчезнет)". Это именно то, что делает мой код, а ваш - нет. Для этого вам не нужно какое-либо событие DOM «изменить» или «щелкнуть», и я до сих пор не понимаю, почему вы хотите привязать вычисляемый как обработчик событий.

Tomalak 20.03.2018 09:41

Tomalak, я пытался спросить о Зачем, некоторые Икс происходят в случае а, а не в случае б в отношении предыдущего ответа SO, а не о как, чтобы это Икс произошло. Ожидаемое поведение, о котором я упоминал, - это просто добавление других деталей к вопросу Зачем. Ваш ответ не относится к предыдущему вопросу, поэтому я не понимаю, как был дан ответ на мой вопрос Зачем. Конечно, я считаю себя виноватым в том, что меня неправильно поняли.

OfirD 20.03.2018 09:51

Хорошо, в качестве общего подхода к заданию, пожалуйста, задавайте вопросы как самостоятельные единицы. Нет смысла требовать от читателя, чтобы он сначала прочитал и понял предыдущий вопрос. Никто не станет этим заниматься.

Tomalak 20.03.2018 10:08

При этом в вашем первом примере кода ничего не происходит, потому что ничто никогда не вызывает функцию changeCity. Думаю, это ясно. Во втором примере кода ваша функция changeCity является наблюдаемой (наблюдаемые - это функции!). Knockout вызывает эту функцию один раз, когда вы создаете модель просмотра, чтобы определить ее значение (значение - undefined, потому что вы никогда ничего не возвращаете). Тем не менее, во время этого вызова функции нокаут замечает, что вы читаете значение country, поэтому он создает зависимость от country. И - о чудо - когда страна меняется, ваша наблюдаемая снова работает.

Tomalak 20.03.2018 10:11

Измените второй codeSample на pureComputed, и он больше не будет работать, потому что нокаут не запускает чистые вычисления, у которых нет подписчика, поэтому запуск исходный, который происходит на обычном computed (для определения его значения), не происходит в чистом вычисляется, поэтому нет отслеживания зависимостей, следовательно, нет волшебного поведения при изменении country.

Tomalak 20.03.2018 10:15
Ответ принят как подходящий

Я так понимаю, что вы не просто пытаетесь решить практическую проблему, но вас больше всего интересует «теоретическая разница» между передачей computed или простого function в привязку. Попробую объяснить различия / сходства.

Начнем с примера

const someObs =   ko.observable(10);
const someFn =    () => someObs() + 1;
const someComp =  ko.computed(someFn);


const dec = () => someObs(someObs() - 1);

ko.applyBindings({ someObs, someFn, someComp, dec });
<script src = "https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<div>Obs. value: <span data-bind = "text: someObs"></span></div>
<div>Computed created in binding: <span data-bind = "text: someFn()"></span></div>
<div>Computed created in vm: <span data-bind = "text: someComp"></span></div>

<button data-bind = "click: dec">-1</button>

Пример выше показывает, что и someFn, и someComp делают одно и то же. Ссылаясь на someFn() в значении обработчика привязки, вы, по сути, создали вычисляемый с зависимостью от someObs.

Почему это не работает в вашем первом примере

Вы никогда не ссылались на свой метод changeCity ни в каком коде, связанном с нокаутом, а это значит, что у вас никогда не будет возможности создать зависимость. Конечно, можно форсировать, но это как-то странно:

var handlerVM = function () {
   var self = this;
   self.city = ko.observable("London");
   self.country = ko.observable("England");
   self.changeCity = function () {
     if (self.country() == "England") {
       self.city("London");
     } else {
       self.city("NYC");
     }
   } 
}
ko.applyBindings(new handlerVM());
<script src = "https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h3 data-bind = "text: city"> </h1>
 <span data-bind = "html: 'change the country, get out of focus, and <strike>nothing</strike> <strong>something</strong> will happen...'"></span>
<br/>
<input data-bind = "value: (changeCity(), country)" />

Почему штатный computedделает работает

Во втором примере вы используете ko.computed. После создания экземпляра ko.computed переданная функция оценивается один раз (немедленно) и создаются зависимости для всех используемых наблюдаемых объектов.

Если вы замените ko.computed на ko.pureComputed, вы увидите, что ваш второй пример также перестанет работать. pureComputed выполняет оценку только после того, как его возвращаемое значение действительно используется, и до тех пор не будет создавать зависимости.

Внутренности

Нокаут оборачивает значение вашей привязки в функциюкак строка. Подробнее об этом можно прочитать в ответ, который я написал ранее.

Мы также знаем, что любой observable, который вызывается внутри метода init обработчика привязки, создает зависимость, которая вызывает метод привязки update, когда происходит изменение.

Итак, в приведенном мною примере происходит следующее:

  1. Привязка text анализируется
  2. Функция function() { return someFn(); } передается как аксессуар значения методу text связывания init.
  3. Метод доступа к значению вызывается для инициализации текстового поля.
  4. У someObs запрашивается его значение, и создается зависимость
  5. Правильное значение отображается в DOM

Затем, нажав на кнопку и сменив someObs:

  1. someObs изменяется, запускает метод text связывания update
  2. Метод update вызывает valueAccessor, повторно оценивает someObs и правильно обновляет его текст.

Практический совет

В заключение несколько практических советов:

  • Используйте ko.pureComputed, когда вы создаете новое значение из одного или нескольких значений observable. (ваш пример)

    self.city = ko.pureComputed(
      () => self.country() === "england" 
        ? "london"
        : "nyc"
    );
    
  • Используйте subscribe, если вы хотите создать побочные эффекты на основе наблюдаемого изменения значения. Например: console.info нового значения или сброс таймера.
  • Используйте ko.computed, если вы хотите создать побочные эффекты на основе изменения любого из нескольких наблюдаемых.

Фантастическое, это то объяснение, которое я искал.

OfirD 20.03.2018 17:22

Другие вопросы по теме