MVVM中的bug

本文专门记录我再做MVVM时遇到的难点和bug

无法触发刷新

场景

<h1>{{tasks.length - activeCount}} remain</h1>

为什么会无法触发呢?触发的条件是属性修改(add, update, delete). 而需要注意的是model仅仅observe自己的属性, 并不会知道嵌套属性的修改. 因此, activeCount修改, h1的内容会轻松的变化, 然而tasks length属性的变化(删除task)却无法触发刷新, 因为length已经属于tasks这个scope的东西了

解决方法:

  1. 不再以model作为刷新节点的单位, 只要是属性修改, 全部节点刷新, 这势必带来不必要的性能损失, 我个人非常喜欢model控制一片的概念, 这个方法必然否决
  2. 是否可以手动触发父model的刷新?
  3. 是否可以改一种写法, 放弃子model刷新父model, 增加手动触发刷新的接口, 如model.refresh

子scope无法获得父scope的值

场景

<li data-repeat='tasks' class="" data-bind="editing: editing">
  <div class="view">
    <input class="toggle" type="checkbox" data-bind="completed: toggle" data-on="click: toggleTask">
    <label data-on="dblclick: editTask">{{content}}</label>
    <button class="destroy" data-on="click: destroyTask"></button>
  </div>
  <input class="edit" value="" data-bind="completed: edit"> <!-- onblur change class -->
</li>

在data-repeat中, model就已经属于tasks[i]了, 也就是说那样才可以直接用completed, content这些值, 问题就是子model获取不到父model的key(editing), 这并不合理, 参照js本身, 子函数的变量都是自己的scope加上全部父scope的. 但在我mvvm设计之初根本没有scope的概念, render时with的对象直接就是model

加上scope的概念是不可少的, 而且这个scope将完全是一个新的对象(不会污染model)

可喜的是我在model中已经加入了$parent属性, 不用专门传scope对象就可以达到类似的效果

function getScope(model) {
  if (!model.$parent) return model
  var scope = {}, $parent = model
  while ($parent) {
    if (!Array.isArray($parent)) extend(scope, $parent) // skip array
    $parent = data[$parent.$parent]
  }
  return scope
}

父model的修改无法触发子model

场景同上, 就算子model通过scope认识了editing这个父元素的属性, 但仍然没法获取它修改的动态

这个错误的产生是因为我将data-bind{{ key }}一样处理了, 但他们是不同的, 后者属于单纯的渲染, 而data-bind更像是订阅, 属于全局

修改方式如下

<div data-bind="editing: editingFunc"></div>
keyBind: {
  'editing': [editingFunc]
}

observe(changes) {
  if (keyBind[changes.name]) {
    var handlers = keyBind[changes.name]
    // exec handlers...
  }
}

做完发现有问题, 子model绑定一个key, 导致全部函数都在keyBind中了, 改一个值全部都变了. 像completed这种确实应该属于自己, 那如何区分自己scope的bind和全局scope的bind呢?

继续解决, 对全局bind和子bind进行区分, 如果bind的name在model中, 则为该model的bind, 否则就是全局的bind

最后解决竟然是通过事件触发的model和model对应的dom实现的

mvvm如果对data-bind加上model和node的传值, 很多操作会简单很多, 但问题是获取这个显然会造成性能的降低

不过至此todomvc已经没有明显bug, 可以在这里看到demo

接下来要做的事情主要有三个

  1. 加入defineProperty, 这样我们获取model和node的时候可以get的时候再操作

  2. 两种模式切换, 一个是支持defineProperty模式, 一种是不支持, 保持写法一样

  3. 彻底使用data, model和element以及event一进来就转换成data类型