(三)Knockout 控制流程

foreach

示例1:迭代数组

foreach binding主要做用于lists或是tables内数据单元的动态绑定。下面是一个简单的例子:javascript

<table>
          <thead>
            <tr>
              <th>First Name</th>
              <th>Last Name</th>
            </tr>
          </thead>
          <tbody data-bind="foreach: people">
            <tr>
              <td data-bind="text: firstName"></td>
              <td data-bind="text: lastName"></td>
            </tr>
          </tbody>
        </table>
    <script type="text/javascript">
      var myViewModel = {
        people: [
          { firstName: "Chiaki", lastName: "Izumi" },
          { firstName: "Kazusa", lastName: "Touma" },
          { firstName: "Haruki", lastName: "Murakami" }
        ]
      };
      ko.applyBindings(myViewModel);
    </script>

示例2:添加 / 删除的实例

在上述示例中,咱们简单的在ko.applybindings中添加了一个数组并将其绑定在一个tbody元素中,咱们也能够自定义一个view model来实现这一效果,下面是一个更为复杂一些的例子:html

<h4>People</h4>
    <ul data-bind="foreach: people">
      <li>
        Person at position <span data-bind="text: $index"></span>:
        <span data-bind="text: name"></span>
        <a href="#" data-bind="click: $parent.removePerson">Remove</a>
      </li>
    </ul>
    <button data-bind="click: addPerson">Add</button>
    <script type="text/javascript">
      function MyViewModel() {
        var self = this;

        self.people = ko.observableArray([
          { name: "Chiaki" },
          { name: "Yuki" },
          { name: "Kazusa" }
        ]);

        self.addPerson = function() {
          self.people.push({ name: "New name at " + new Date() });
        };

        self.removePerson = function() {
          self.people.remove(this);
        };
      }

      ko.applyBindings(new MyViewModel());
    </script>


参数java

  • 主要参数
    传递但愿迭代的数组。绑定将为每一个条目输出一段标记。
    或者,传递一个JavaScript对象文本和一个名为data的属性,该属性是您但愿迭代的数组。对象文字还可能具备其余属性,如afterAddincludeDestroyed,有关这些额外选项的详细信息及其使用示例,请参见下面。
    若是您提供的数组是可见的,那么foreach绑定将经过在DOM中添加或删除标记的相应部分来响应数组内容的任何将来更改。

注释1:使用 $data 引用每一个数组条目

如上面的示例所示,foreach块中的绑定能够引用数组条目上的属性。 例如,示例1引用了每一个数组条目的firstName和lastName属性。
可是,若是您想引用数组条目自己(而不只仅是它的一个属性),该怎么办?在这种状况下,可使用特殊的上下文属性$data。在foreach块中,它表示当前项。例如:node

<ul data-bind="foreach: months">
  <li>
      The current item is: <b data-bind="text: $data"></b>
  </li>
</ul>
  
<script type="text/javascript">
  ko.applyBindings({
      months: [ 'Jan', 'Feb', 'Mar', 'etc' ]
  });
</script>

若是须要,能够在引用每一个条目上的属性时使用$data做为前缀。例如,您能够将示例1的部分重写以下jquery

<!-- $data 引用对象每一个值-->
<td data-bind="text: $data.firstName"></td>

但您没必要这样作,由于firstName在默认状况下将在$data上下文中进行计算。若是数组中的项是被监控的,$data将引用每一个监控的值。要引用可观察对象自己,推荐使用$rawDatagit

<!--$rawDat 引用对象自己-->
<td data-bind="text: $rawData.firstName"></td>

注释2:使用$index、$parent和其余上下文属性

从上面的示例2能够看出,可使用$index引用当前数组项的从零开始的索引。$index是一个可观察的对象,当项目的索引起生变化时(例如,若是项目被添加到数组中或从数组中删除),$index就会被更新。
相似地,您可使用$parent 引用来自foreach外部的数据,例如。github

<h1 data-bind="text: blogPostTitle"></h1>
    <ul data-bind="foreach: likes">
        <li>
            <b data-bind="text: name"></b> likes the blog post <b data-bind="text: $parent.blogPostTitle"></b>
        </li>
    </ul>
   
  <script type="text/javascript">
     function AppViewModel() {
    var self = this;

    self.blogPostTitle =ko.observable('你好');

    self.likes = ko.observableArray([
        { name: 'Bert' },
        { name: 'Charles' },
        { name: 'Denise' }
    ]);
}
 
ko.applyBindings(new AppViewModel());
  </script>

注释3:使用 “as” 的别名为 “foreach” 项目

在注释1中提到,咱们可以经过$data来调用foreach绑定的数组自己,可是当咱们使用嵌套的foreach,内层foreach如何可以调用外层foreach绑定的数组呢?这时咱们能够借由as给外层foreach所绑定的数组定义一个另外的名称,进而在内层foreach中利用此名称来调用外层foreach所绑定的数组。接下来是一个简单的例子:ajax

<ul data-bind="foreach: { data: person, as: 'person' }">
      <li>
        <ul data-bind="foreach: { data: friends, as: 'friends' }">
          <li>
            <span data-bind="text: person.name"></span>:
            <span data-bind="text: friends"></span>
          </li>
        </ul>
      </li>
    </ul>

    <script>
      var viewModel = {
        person: ko.observableArray([
          { name: "Chiaki", friends: ["Charlie", "Kazusa"] },
          { name: "Kazusa", friends: ["Chiaki", "Charlie"] },
          { name: "Charlie", friends: ["Chiaki", "Kazusa"] }
        ])
      };

      ko.applyBindings(viewModel);
    </script>


这个例子中的外层foreach绑定的是person数组,person数组中有一个属性name和另一个数组firends,在内层foreach中绑定的是数组firends。当咱们在内层foreach要调用外层的person数组内的属性时,借由as,使用了person.name来调用。而这里也有一个有趣的状况,就是当一个数组里面只有单纯的元素,好比说friends数组,它的元素并非object,也就不存在这identifier的问题,这时咱们要调用它的元素时,只须要调用数组自己便可,这也就是为何在以前的示例中若是咱们调用绑定的数组自己会返回[object, object]。api

这代表,通常状况下,遍历数组中的元素只须要调用数组名(as指定)或是调用$data便可,而碰到那些内部元素是object的时候,咱们要调用object内的属性则须要调用相应属性的名称。数组

另外须要注意的一点就是,as后所跟着的必须是一个字符串(as: "person"而不是as: person)。

注释4:不使用foreach当容器

有些状况下,咱们使用foreach的场合会比较复杂,好比说以下的例子:

<ul>
      <li>Header item</li>
      <!-- The following are generated dynamically from an array -->
      <li>Item A</li>
      <li>Item B</li>
      <li>Item C</li>
    </ul>

这种状况下,咱们并不知道改在哪一个元素内添加foreach。若是是在ul添加的话,咱们事先写好的header item便会被覆盖掉,而ul元素内又只能嵌套li元素,添加另外一个容器来实现foreach也是不可行的。为了解决这一问题,咱们须要使用无容器的控制流语法(containerless control flow syntax),与先前所提到的containerless text syntax相似。一个简单的例子以下:

<!-- 不使用foreach,使用容器 -->
    <ul>
      <li>Header item</li>
      <!-- ko foreach: people -->
      <li>name: <span data-bind="text: $data"></span></li>
      <!-- /ko -->
    </ul>
    
    <!-- 使用foreach -->
    <ul data-bind="foreach: people">
      <li>Header item</li>
      <li>name: <span data-bind="text: $data"></span></li>
    </ul>

    <script>
      var viewModel = {
        people: ko.observableArray(["Kazusa", "Chiaki", "Yuki"])
      };

      ko.applyBindings(viewModel);
    </script>

注释7:处理后所生成的 DOM 元素

当咱们须要在生成的DOM元素上运行一些自定义的逻辑时,咱们能够用到

  • afterRender
  • afterAdd
  • beforeRemove
  • beforeMove
  • afterMove
    等回调函数。

须要注意的是,这些回调函数仅仅适用于触发与列表的改变有关的动画,若是咱们须要对新添加的DOM元素附加一些其余的行为,好比说事件的处理或是第三方的UI控制,将这些行为做为自定义的绑定(custom binding)会更为合适,由于这样设定的行为是与foreach互相独立的。

接下来是一个调用afterAdd的简单的例子,其中用到了jQuery Color plugin

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.js"></script>
<script src="http://code.jquery.com/color/jquery.color-2.1.2.min.js"></script>
<script src="../js/knockout-3.5.0rc2.debug.js"></script>
<ul data-bind="foreach: { data: people, afterAdd: yellowFadeIn }">
      <li data-bind="text: $data"></li>
    </ul>

    <button data-bind="click: addItem">Add</button>

    <script>
      var viewModel = {
        people: ko.observableArray(["Kazusa", "Chiaki", "Yuki"]),

        yellowFadeIn: function(element, index, data) {
          $(element)
            .filter("li")
            .animate({ backgroundColor: "yellow" }, 200)
            .animate({ backgroundColor: "white" }, 800);
        },

        addItem: function() {
          this.people.push("New person");
        }
      };

      ko.applyBindings(viewModel);
    </script>

如下是对一些回调函数详尽的介绍:

afterRender是在foreach模块初始化或是添加新的元素时触发的,其接受的参数为(为了可以保持愿意,这里用英文显示,下同):

An array of the inserted DOM elements
The data item against which they are being bound
afterAddafterRender相似,不过它只会在新元素添加时触发(foreach初始化的时候并不会触发),它所接受的参数为:

A DOM node being added to the document
The index of the added array element
The added array element
beforeRemove函数会在数组中的某一项被删除时触发。须要注意的是,beforeRemove实际上替代了UI界面对remove所作出的回应,即在beforeRemove函数中若是不对DOM相应的元素进行remove操做,则在页面上的元素是不会被删除的,可是viewModel中的数组相应的元素却已经被删除了。beforeRemove函数接受如下参数:

A DOM node that you should remove
The index of the removed array element
The removed array element
beforeMove函数会在数组中的元素索引起生变化时触发,beforeMove会应用于全部索引产生变化的元素,即倘若咱们在数组开头插入元素,则其后全部元素都将受到beforeMove回调函数的影响。一个比较经常使用的作法是经过beforeMove来保存原有元素的坐标以便咱们可以在afterMove中控制元素移动的动画。beforeMove函数接受如下参数:

A DOM node that may be about to move
The index of the moved array element
The moved array element
afterMove函数也会在数组中的元素索引起生变化时触发,afterMove会应用于全部索引产生变化的元素,即倘若咱们在数组开头插入元素,则其后全部元素都将受到afterMove回调函数的影响。afterMove函数接收如下参数:

A DOM node that may have moved
The index of the moved array element
The moved array element
对于回调函数中的beforeafter,咱们应该有一个比较清醒的认识。before和after针对的都是UI界面中的元素变化,也就是页面产生变化以前和页面产生变化以后,与此同时,viewModel部分的数组已经发生了变化,正是viewModel部分的数组的变化才触发了before和after所对应的回调函数。

if和ifnot绑定

if binding与visible binding相似。不一样之处在于,包含visible binding的元素会在DOM中一直保存,而且该元素相应的data-bind属性会一直保持,visible binding只是利用CSS来触发元素的可见性。另外一方面,if binding是物理地增长或删除包含它的元素,而且元素内的data-bind只有在判断语句为真时才生效。

例子1

下面是一个简单的if binding的例子:

<label>
      <input type="checkbox" data-bind="checked: displayMessage" /> 
      Display message
    </label>

    <div data-bind="if: displayMessage">Here is a message. Astonishing.</div>
    <script type="text/javascript">
      ko.applyBindings({
        displayMessage: ko.observable(false)
      });
    </script>

例子2

在下面的例子中,<div>元素对于“Mercury”是空的,可是对于“Earth”是填充的。这是由于 Earth 有一个非空 capital 属性,而 Mercury 的 capital 属性为 null

<ul data-bind="foreach: planets">
          <li>
              Planet: <b data-bind="text: name"> </b>
              <div data-bind="if: capital">
                  Capital: <b data-bind="text: capital.cityName"> </b>
              </div>
          </li>
      </ul>
      
      <script>
          ko.applyBindings({
              planets: [
                  { name: 'Mercury', capital: null }, 
                  { name: 'Earth', capital: { cityName: 'Barnsley' } }        
              ]
          });
      </script>

重要的是要理解 if 绑定对于使代码正常工做很是重要。没有它,在评估时就会出现错误。“Mercury”上下文中的 capital.cityName ,其中 capitalnull。在JavaScript中,不容许计算空值或未定义值的子属性。

无容器

if binding也支持无容器的控制流语法,一个简单的示例以下:

<label>
      <input type="checkbox" data-bind="checked: displayMessage" /> 
      Display message
    </label>

        <ul>
            <li>Item 1</li>
            <!-- ko if: displayMessage -->
            <li>Message</li>
            <!-- /ko -->
        </ul>

    <div data-bind="if: displayMessage">Here is a message. Astonishing.</div>
    <script type="text/javascript">
      ko.applyBindings({
        displayMessage: ko.observable(false)
      });
    </script>

ifnot

<div data-bind="ifnot: someProperty">...</div>

等价于

<div data-bind="if: !someProperty()">...</div>

with和using绑定

withusing 绑定建立一个新的绑定上下文,以便将后代元素绑定到指定对象的上下文中。(这些绑定之间的区别将在下面的参数中描述。)
固然,您能够任意嵌套使用with绑定以using及其余控制流绑定(如ifforeach )。

例子1:

下面是一个将绑定上下文切换到子对象的很是基本的示例。注意,在 data-bind属性中,没有必要在经纬度前面加上coords。,由于绑定上下文已切换到coords
这里也可使用 with

<h1 data-bind="text: city"> </h1>
      <p data-bind="using: coords">
          Latitude: <span data-bind="text: latitude"> </span>,
          Longitude: <span data-bind="text: longitude"> </span>
      </p>
       
      <script type="text/javascript">
          ko.applyBindings({
              city: "London",
              coords: {
                  latitude:  51.5001524,
                  longitude: -0.1262362
              }
          });
      </script>

例子2

<form data-bind="submit: getTweets">
      Twitter account:
      <input data-bind="value: twitterName" />
      <button type="submit">Get tweets</button>
    </form>

    <div data-bind="with: resultData">
      <h3>
        Recent tweets fetched at <span data-bind="text: retrievalDate"> </span>
      </h3>
      <ol data-bind="foreach: topTweets">
        <li data-bind="text: text"></li>
      </ol>

      <button data-bind="click: $parent.clearResults">Clear tweets</button>
    </div>

    <script type="text/javascript">
      function AppViewModel() {
        var self = this;
        self.twitterName = ko.observable("@example");
        self.resultData = ko.observable(); // No initial value

        self.getTweets = function() {
          var name = self.twitterName(),
            simulatedResults = [
              { text: name + " What a nice day." },
              { text: name + " Building some cool apps." },
              { text: name + " Just saw a famous celebrity eating lard. Yum." }
            ];

          self.resultData({
            retrievalDate: new Date(),
            topTweets: simulatedResults
          });
        };

        self.clearResults = function() {
          self.resultData(undefined);
        };
      }

      ko.applyBindings(new AppViewModel());
    </script>

从这里例子中,咱们能够看出with binding在使用时的几个特色。

  • 当with binding所绑定的binding context为null或是undefined时,包含with binding的元素的全部子元素都将从UI页面中移除。
  • 若是咱们须要从parent binding context中获取data或是function,咱们可使用特殊的context properties好比说$parentroot,这部分能够参考The binding context

假若绑定的binding context是一个observable,包含with binding的元素会随着observable的变化而移除现有的子孙元素并添加一系列隶属于新的binding context的子孙元素。

相似的,with binding也提供无容器的控制流语法,这里省略例子,能够参考if binding等。

with和using区别

若是您提供的表达式包含任何监控的值,则每当这些值发生更改时,将从新计算该表达式。这些绑定在绑定值发生变化时的反应不一样:
- 对于with绑定,将清除后代元素,并将标记的新副本添加到文档中,并在新值的上下文中绑定。
- 对于using绑定,后代元素将保留在文档中,并使用新的上下文值从新评估它们的绑定。

附加参数

  • as
    as选项容许为新上下文对象设置别名。虽然您可使用$data上下文变量引用对象,可是使用as选项为它提供一个更具描述性的名称可能会颇有用
<div data-bind="with: currentPerson, as: 'person'"></div>
  • noChildContext
      as选项的默认行为是为提供的对象设置一个名称,同时仍然将内容绑定到对象。可是您可能更愿意保持上下文不变,只设置对象的名称。后一种行为多是未来版本的击倒的默认行为。若要打开特定绑定,请将noChildContext选项设置为true。当这个选项与as一块儿使用时,对对象的全部访问都必须经过给定的名称,而且$data将保持设置为外部viewmodel。
      对于using绑定,虽然您可使用此选项,但使用let绑定一般会更有效和描述性。 而不是 using: currentPerson, as: 'person', noChildContext: true,你可使用 let: { person: currentPerson }

注释1:无容器

与其余控制流绑定(如if和foreach)同样,可使用with和using而不使用任何容器元素来承载它。若是您须要在不合法的地方使用这些绑定,仅仅为了保存绑定而引入新的容器元素,那么这是很是有用的。

<ul>
    <li>Header element</li>
    <!-- ko with: outboundFlight -->
        ...
    <!-- /ko -->
    <!-- ko with: inboundFlight -->
        ...
    <!-- /ko -->
</ul>

注释2:为何使用两个相似的绑定?

当不须要从新呈现后代元素时,他在knockoutjs 3.5中using了绑定来替代with。(参考with和using区别) 由于using从新评估后代绑定而不是从新呈现,每一个后代绑定将包含对使用上下文的附加依赖。

component 绑定

component 组件绑定将指定的组件注入元素,并可选地向其传递参数。

示例1:计算字数

<h4>First instance, without parameters</h4>
      <div data-bind='component: "message-editor"'></div>
       
      <h4>Second instance, passing parameters</h4>
      <div data-bind='component: {
          name: "message-editor",
          params: { initialText: "Hello, world!" }
      }'></div>

    <script type="text/javascript">
      ko.components.register('message-editor', {
        viewModel: function (params) {
          this.text = ko.observable(params && params.initialText || '');
        },
        template: 'Message: <input data-bind="value: text" /> '
          + '(length: <span data-bind="text: text().length"></span>)'
      });

      ko.applyBindings();
    </script>


注意:在更现实的状况下,您一般会从外部文件加载组件视图模型和模板,而不是将它们硬编码到注册中。请参见示例和注册文档。

API

有两种方法可使用组件绑定

  • 速记语法
    若是只传递一个字符串,它将被解释为组件名。而后注入命名组件,而不向其提供任何参数。例子
<div data-bind='component: "my-component"'></div>

简写值也能够被监控到。在本例中,若是组件绑定发生更改,则组件绑定将处理(dispose)旧组件实例,并注入新引用的组件。例子

<div data-bind='component: observableWhoseValueIsAComponentName'></div>
  • 完整语法
    要向组件提供参数,请传递具备如下属性的对象
    • name 要注入的组件的名称。这也是能够观察到的。
    • params 将传递给组件的对象。一般,这是一个包含多个参数的键值对象,一般由组件的viewmodel构造函数接收。
    <div data-bind='component: {
      name: "shopping-cart",
      params: { mode: "detailed-list", items: productsList }
    }'></div>
    请注意,不管什么时候删除组件(要么是由于名称更改了observable,要么是由于封闭的控制流绑定删除了整个元素),删除的组件被释放( disposed)。

组件生命周期

1.组件加载器被要求提供 viewmodel 工厂和模板
  一般,这是一个异步过程。 它可能涉及对服务器的请求。 对于API一致性,默认状况下Knockout确保加载过程做为异步回调完成,即便组件已经加载并缓存在内存中也是如此。 有关此内容以及如何容许同步加载的更多信息,请参阅控制同步/异步加载
2.组件模板被克隆并注入容器元素
3.若是组件有一个视图模型 viewmodel ,则实例化它
4.视图模型 viewmodel 绑定到视图
5.组件被激活
6.组件被拆除,视图模型被放置

注意1:仅有模板组件

组件一般有视图模型,但不必定非得有。组件能够只指定模板。
在本例中,绑定组件视图的对象是传递给组件绑定的params对象。例子

ko.components.register('special-offer', {
    template: '<div class="offer-box" data-bind="text: productName"></div>'
});

能够注入:

<div data-bind='component: {
     name: "special-offer-callout",
     params: { productName: someProduct.name }
}'></div>

或者,更方便地,做为自定义元素

<special-offer params='productName: someProduct.name'></special-offer>

使用无容器

<!-- ko component: {
    name: "message-editor",
    params: { initialText: "Hello, world!", otherParam: 123 }
} -->
<!-- /ko -->

标记传递给组件

附加组件绑定的元素可能包含进一步的标记。例如

<div data-bind="component: { name: 'my-special-list', params: { items: someArrayOfPeople } }">
    <!-- Look, here's some arbitrary markup. By default it gets stripped out
         and is replaced by the component output. -->
    The person <em data-bind="text: name"></em>
    is <em data-bind="text: age"></em> years old.
</div>

尽管该元素中的DOM节点将被删除,而且默认状况下不进行绑定,但它们不会丢失。相反,它们被提供给组件(在本例中是my-special-list),组件能够按照本身的意愿将它们包含在输出中。
若是您想要构建表示容器UI元素的组件,例如网格、列表、对话框或选项卡集,这是很是有用的,由于这些元素须要将任意标记注入并绑定到公共结构中。有关自定义元素的完整示例,它也能够在没有使用上面所示语法的自定义元素的状况下工做。

<!-- This could be in a separate file -->
    <template id="my-special-list-template">
      <h3>Here is a special list</h3>

      <ul data-bind="foreach: { data: myItems, as: 'myItem' }">
        <li>
          <h4>Here is another one of my special items</h4>
          <!-- ko template: { nodes: $componentTemplateNodes, data: myItem } --><!-- /ko -->
        </li>
      </ul>
    </template>

    <my-special-list params="items: someArrayOfPeople">
      <!-- Look, I'm putting markup inside a custom element -->
      The person <em data-bind="text: name"></em> is
      <em data-bind="text: age"></em> years old.
    </my-special-list>

    <script type="text/javascript">
      ko.components.register("my-special-list", {
        template: { element: "my-special-list-template" },
        viewModel: function(params) {
          this.myItems = params.items;
        }
      });

      ko.applyBindings({
        someArrayOfPeople: ko.observableArray([
          { name: "Lewis", age: 56 },
          { name: "Hathaway", age: 34 }
        ])
      });
    </script>

相关文章
相关标签/搜索