在 Angular, Vue.js等MVVM框架中,都涉及到指令(directive)的概念,directive实际上是一种针对DOM操作的抽象封装,并通过框架处理,将DOM操作逻辑与DOM元素进行自动化绑定,用一个简单的声明式语法简化了DOM操作逻辑中的 给元素命名
, 查询目标元素
, 进行DOM操作
步骤。
举个小例子,比如我们有一个header组件,它的HTML片段内容是:
html
<div class="header">
<button>按钮</button>
</div>
如果我们希望给这里的按钮添加一个高亮的class,用zepto的代码是这样写的:
js
var $ = require('zepto');
var $btn = $('.header button')
$btn.addClass('highlight')
由以上小例子可以看出,我们在写JS交互逻辑的时候,几乎所有的JS交互逻辑都像以下流程一样:
初始化/数据变更/事件触发 --> 给目标元素命名(或者使用DOM树结构)--> 用选择器选中目标元素 --> 进行DOM操作
为此,封装DOM操作并自动化将DOM操作与元素绑定,可以减少1/2的交互逻辑代码(1个步骤替换3步骤),甚至做到根据DOM操作与元素与及数据进行绑定,那就可以用一个绑定声明减少了全部的手动步骤。
根据这一思路,我们先实现DOM操作封装与
html
<div class="header">
<button binding-class="highlight:isOn">按钮</button>
</div>
组件的HTML中声明了一个click事件,绑定JS模块中的onBtnClick函数,JS模块的代码为:
js
// 绑定时依赖的数据
var data = {
isOn: true
}
// 封装DOM操作
function addClassOrRemove (el, clazz, cnd) {
var $el = $(el)
cnd ? $el.addClass(clazz) : $el.remove(clazz)
}
// 将DOM操作与元素绑定,返回操作步骤
function classBinding () {
var tar = document.querySelector('[binding-class]')
var dec = $(tar).attr('binding-class')
var clazz = desc.split(':')[0]
var field = desc.split(':')[1].trim()
return function () {
addClassOrRemove(tar, clazz, data[field])
}
}
// 进行绑定操作,获取绑定后的操作方法
var updateClassAction = classBinding()
// 初始化时更新
updateClassAction()
显然,在完成DOM操作与DOM元素的绑定后,以后的每次更新触发场景中一个步骤就可以完成DOM元素的更新。
我们需要更懒惰一点,把手动触发更新的操作也省略了,如何?
那么我们需要现实监听数据的变更,在不考虑兼容IE9的情况下,我们可以使用ES5的 defineProperty
方法来实现:
js
var data = {}
var _isOn
Object.defineProperty(data, 'isOn', {
get: function () {
return _isOn
},
set: function (nextValue) {
_isOn = nextValue
// 在监听到数据变更后立即触发UI更新
updateClassAction()
}
})
这就是MVVM的数据绑定的实现,在框架帮助下完成以上一系列的绑定行为,要完成根据状态给按钮添加/移除高亮的class,我们只需要这样一个属性标志:
html
<div class="header">
<button binding-class="highlight: isOn">按钮</button>
</div>
个人认为,使用属性声明的方式自动化绑定优于具名选择器的方式操作DOM元素。从开发效率角度,我们省去了 给元素命名
/ 查询目标元素
/ 进行DOM操作
这3步操作,从维护性角度,我们也省却了 知道文档结构
/ 知道选择器标志
2个步骤。