上一篇数据绑定:模型到视图简单的介绍了从模型到视图的绑定,而我们想要的不止如此,我们希望视图改变的时候也能影响到数据模型。应用最多的场景应该就是表单了,这样的话,改变了视图也就改变了要提交的数据,这样我们的工作量就可以大大减少。
事件,还是事件
在用户在表单内操作的时候,自然会通过键盘或者鼠标触发相应的事件,我们捕获这些事件进行处理,在事件处理函数里对模型进行操作。
一般的,对于DOM事件,框架都会用自定义的指令来做,比如angular里用ng-开头,regular里用on-开头。
不过有时候视图的改变并不是事件触发的,或者是用户在JS代码里更新了视图的某部分,比如: input.setAttribute(‘value‘, ‘1000‘) ,那么我们也要监听到然后改变数据模型。
一般来说,会通过订阅/发布(PubSub)这种模式来处理,也就是自定义事件,原理大概就是:
提前把某个自定义事件名称一系列的监听函数push进数组里,当收到fire/trigger/emit(名字真多)通知的时候,监听函数全部执行。
var events = (function(){ var topics = {}; var hOP = topics.hasOwnProperty; return { subscribe: function(topic, listener) { // Create the topic‘s object if not yet created if(!hOP.call(topics, topic)) topics[topic] = []; // Add the listener to queue var index = topics[topic].push(listener) -1; // Provide handle back for removal of topic return { remove: function() { delete topics[topic][index]; } }; }, publish: function(topic, info) { // If the topic doesn‘t exist, or there‘s no listeners in queue, just leave if(!hOP.call(topics, topic)) return; // Cycle through topics queue, fire! topics[topic].forEach(function(item) { item(info != undefined ? info : {}); }); } }; })();
简单的实现来自Pub/Sub JavaScript Object。还有更加精细的实现:PubsubJS。
脏检查
在数据绑定:模型到视图的最后,给力脏检查双向绑定的例子。一般的情况下框架会自动进入$degist阶段,但在一些特殊情况下,用户也可以通过框架暴露的方法手动进行,像Regular就提供了$update方法。
MutationObserver
振奋人心的时候到了:
MutationObserver
给开发者们提供了一种能在某个范围内的DOM树发生变化时作出适当反应的能力。
而且支持情况也还是凑合的:
通过MutationObserver,我们可以获取到DOM树的变化,不管你是事件的还是莫名其妙的变化。。。。
用法也是极其的简单:
//设置要监察的行为var observeConfig = { attributes: true, childList: true, characterData: true, attributeOldValue :true, subtree: true, attributeFilter:[‘value‘] } var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var observer = new MutationObserver(function(mutations) { //每当有变化就会触发次方法,循环打印变化的记录 mutations.forEach(function(record, i) { console.log(record); }); }); observer.observe(element, observeConfig);
这样的话,我们在监听函数里就可以为所欲为了。
不过这里其实有个问题,对于表单元素,比如输入框,输入或删除一些字符,并不会触发监听方法,像这样:input.value = xxx这样操作也不会触发。
这是因为MutationObserver只是监听DOM书的变化,也就是说Nodes和Attributes,而输入或删除以及直接value并不会引起DOM树的变化,打印一些input.getAttribute(‘value‘)你发现值根本没变!!!
所有还是要求助事件了。
input.onkeyup = function(e) { var target = e.target; target.setAttribute(‘value‘, target.value); }
这样就OK了。
其实IE中早就实现了onpropertychange事件,不过标准里只有一个oninput只能监听value值,不过这也给兼容提供了一个思路。
DOM Attributes on the prototype chain
刚写完这篇博文,不小心看到了一篇文章:DOM Attributes now on the prototype chain,中文翻译在这里。
Chrome在IE、Firefox也支持了原型链DOM,我觉得这也应该是视图到模型绑定的方式,我们可以给DOM节点上自定义属性,然后像这样:
Object.defineProperty(HTMLDivElement.prototype, "isSuperContentEditable", { get: function() { return true; }, set: function() { /* some logic to set it up */ }, });
在setter里可以做我们想做的事情。可以轻易的实现像**-model之类的指令。
<input type="text" z-model="">
var data = {}; var input = document.querySelector(‘input‘); Object.defineProperty(input, ‘z-model‘, { get: function() { return data.name; }, set: function(n) { data.name = n; input.value = n; } });
然后再简单一改,双向绑定也有了。。。
var data = {}; var _name = ‘init‘; var input = document.querySelector(‘input‘); Object.defineProperty(data, ‘name‘, { get: function() { return _name; }, set: function(n) { _name = n; input.value = n; } }); Object.defineProperty(input, ‘z-model‘, { get: function() { return data.name; }, set: function(n) { data.name = n; input.value = n; } });
参考资料: