本文深入探讨了JavaScript中视图与数据双向绑定的实现机制。通过对比Vue的Object.defineProperty监听与AngularJS的脏检查,分析了在低版本浏览器或特定环境(如微信小程序)下的替代方案。文章提供了基于原型链get/set方法、观察者模式以及jQuery自定义属性选择器的具体代码实现,旨在帮助开发者理解MVVM核心原理并优化旧项目架构。
本文详细解析了JavaScript实现视图与数据双向绑定的具体方法,旨在为开发者提供实用的参考与借鉴。
前言
视图与数据的绑定机制实现了视图层与逻辑层的解耦,使视图层转变为数据驱动模式,这是前端开发领域的一项重要进步。基于此理念诞生的MVVM类前端框架,显著提升了开发效率。
在现有旧项目中,探讨如何引入更先进的设计模式来替换大量面向过程编程,是一个值得关注的议题。
各大主流框架对数据绑定的实现各有千秋,本文不作深入对比,仅做简要介绍。
Vue框架利用ES5标准中的特性,实现对数据读取和设置的监听,这属于元编程的一种应用。相较于AngularJS采用的脏检查(Dirty Checking)机制,Vue的方式在数据更新策略上显得更为科学高效。
实现了对数据get和set方法的重新定义,使得在执行最简单的赋值操作时能够触发特定逻辑。具体的底层实现细节并非本文讨论重点,此处略过。
在低版本浏览器中,的支持并不完善。因此,我们可以探索一种替代方案,以达到与类似的效果。例如,在微信小程序中,由于视图层与JS引擎本质分离,设置数据必须使用特定的set方法,这相当于手动通知视图层数据已更新。借鉴这一思路,我们也可以利用类似机制来监听数据更新。
实现
首先,我们需要一个对象来存储数据,并在数据发生变更时执行相应操作。可以通过定义一个构造函数来创建此类对象。
由于get和set方法属于公共接口,将其定义在原型对象上更为合适。
var $vm = function(obj) {
this.data = obj.data
}
$vm.prototype.get = function(prop) {
//返回当前值
return this.data[prop]
}
$vm.prototype.set = function (prop, val) {
//赋值操作
this.data[prop] = val
}
当实例化该构造函数的对象时,该对象将拥有get和set方法。从代码可以看出,这些方法作用于对象上的data属性。
至此,一个简单的get/set方法体系已构建完成:get用于获取当前对象属性的值,set用于设置新的值。
若在set方法中触发相应的视图层操作,即可实现一个简单的数据绑定效果。
var vm = new $vm({
// 绑定的变量值
data: {
info: true
}
})
取值
vm.get('info')
存值
vm.set('info', false)
如果在set方法中加入console.log(),每次数据变动时都会被打印输出。
使用set方法替代直接的=号赋值操作,在一定程度上可以替代Object.defineProperty的效果,且具备更好的兼容性。
视图数据的绑定是一个复杂的问题,关键在于如何让数据的变动实时反映在视图上。
这里提供一种最简单的替代实现思路:手动绑定数据与视图,采用jQuery的方式进行处理。
例如,在set方法中执行对应属性变更的回调函数。
具体示例如下:
$vm.prototype.set = function (prop, val) {
this.data[prop] = val
if (this.$$fn[prop]) {
this.$$fn[prop](val, oldVal)
}
}
可以看到,如果当前对象上$$fn属性中存在同名函数,将会被自动执行。
这样,我们可以将DOM绑定的操作显式地放置在这些回调函数中。
这种写法显然不利于代码维护,因此我考虑参照Vue框架的watch观察者模式来实现优化。
在Vue中,可以观察对象上某个值的改变并执行相应的操作。
因此,在此场景下可以借鉴这一思路。
// 存值
$vm.prototype.set = function (prop, val) {
var oldVal = this.data[prop]
this.data[prop] = val
//如果发现被列入观察者 执行函数并注入修改后的值
if (this.watch[prop]) {
this.watch[prop](val, oldVal)
}
}
鸽了,这里省略3000字。哈哈哈哈,因为写了好多代码但是没写博客,懒得写了直接跳过吧,有兴趣的童鞋直接看源码。虚拟dom和{{}}表达式,观察者模式,计算属性等等。
突发奇想,针对数据层绑定还有另一种简单方案:利用HTML5的data自定义属性来绑定相应属性,并通过jQuery选择器找到对应节点进行更新。这也是一种可行的替代方案。

利用jQuery选择器,可以选择属性等于特定值的DOM节点。通过该选择器,可以获取所有绑定了某属性的节点。
<div data-vm="info">
</div>
$('[data-vm="info"]').text(300)
此时,该节点的值已变为300。
因此,利用自定义属性和jQuery选择器,只要在DOM节点上添加data-vm(自定义名称)并赋予要绑定的值,即可实现对视图的绑定。
// 存值
$vm.prototype.set = function (prop, val) {
try {
var oldVal = this.data[prop]
this.data[prop] = val
//如果发现被列入观察者 执行函数并注入修改后的值
if (this.watch[prop]) {
this.watch[prop](val, oldVal)
}
//查询是否有订阅值
if (this.$$fn[prop]) {
this.$$fn[prop](val, oldVal)
}
//查询是否有依赖于此项的计算属性
if (this.$$count[prop]) {
// 获得所有依赖此值的计算属性
var arr = this.$$count[prop]
//循环遍历每个计算属性并重新计算它的值
for (var i = 0; i < arr.length; i++) {
var item = arr[i]
// 获得返回的值
this.data[item] = this.computed[item]()
}
}
// 如果节点绑定了此属性 更新节点
var dom = $('[data-vm="' + prop + '"]')
if (dom) {
dom.text(val)
}
// this.updateView()
} catch(e) {
console.log('error setData' + prop)
}
}
实现成功!
目前关于绑定的各种方式我进行了多次修改,具体代码已上传至GitHub,有兴趣的读者可以查看。
项目GitHub地址:https://github.com/unjust-life/mvvm
更多关于JavaScript相关内容可查看本站专题:《JavaScript操作DOM技巧总结》、《JavaScript页面元素操作技巧总结》、《JavaScript事件相关操作与技巧大全》、《JavaScript查找算法技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript错误与调试技巧总结》
希望本文所述内容对大家学习JavaScript程序设计有所帮助。