
看过上一节的小伙伴肯定已经知道了这次的主要内容——实现数据更新驱动页面视图
数据驱动视图的基本构想
我们已经实现了数据的getter
和setter
,也实现了页面上数据的替换,那么怎样将两者结合呢?最简单的方法当然就是在数据的setter
方法中实现视图的更新了
那么怎样结合呢?我们先来回想一下上一节的代码:在 实例l
初始化时,就已经将数据替换到页面上了——但这是一次性的
通过 “指令” 将数据的变更与视图的更新关联
那如何使其摆脱一次性呢?我们可以在遍历到每一个需要填入数据的文本节点时,将这个节点的相关信息(填入的数据名、节点信息等)保存起来,是这些信息成为一个数组,当数据改变时,获取到数组中对应的信息,即可更新视图
如果给上面的这些信息起一个名字,我们可以叫它为“指令”,因为每一个信息都会对应页面上一个节点的更新,它负责给页面发送一条“指令”从而执行更新的任务
当然,“指令”中不仅仅可以存在文本数据的更新,我们知道 Vue 中的指令还有v-if
v-bind
等等,这些都可以通过扩展“指令”的属性和方法来实现,但我们这一节仅讨论文本数据的更新,其他的如果有机会以后在讨论
设计一个“指令”类
在这里,我们将“指令”单独拎出来,作为一个独立的类存在,当然你也可以将它放在Lue
中
/** 声明一个 Directive 类
* @param {String} name 指令的种类 当存在多种指令时用来区分是哪种指令 本文中的 name=text
* @param {Object} el 与这条指令相关联的节点 node类型
* @param {Object} vm 指令存在的 Lue 实例 可以方便地获取到该实例上的属性
* @param {String} expression 表达式 本文中值属性名
*/
function Directive(name, el, vm, expression) {
this.name = name
this.el = el
this.vm = vm
this.expression = expression
// 节点的值的属性名 此处写死 当需要扩展指令时可以通过传参确定值
this.attr = 'nodeValue'
this.update()
}
// 为 Directive 添加一个更新视图的方法 当指令类型增多时可以添加更多的方法
Directive.prototype.update = function() {
this.el[this.attr] = this.vm.$data[this.expression]
}
为 Lue 添加指令的存储数组
首先为 Lue
添加属性this.$directives=[]
重写_compileText
方法
Lue.prototype._compileText: function (node) {
var patt = /{{\w+}}/g
var nodeValue = node.nodeValue
var expressions = nodeValue.match(patt)
if (!expressions) return
var self = this
expressions.forEach(function (expression) {
// 这里删除了上一节中的创建节点后替换的代码 可以直接通过 nodeValue 来更新节点的值
var property = expression.replace(/[{}]/g, '')
// 向 $directives 添加一条指令
self.$directives.push(new Directive('text', node, self, property))
})
}
重写 setter 将数据的更新与指令关联
Lue.prototype._bindData = function (obj, key, val) {
var directive, self = this
// 遍历 $directives 中的所有指令,找出与当前数据的 key 相同的指令
self.$directives.forEach(function (dir) {
if (dir.expression === key) {
directive = dir
}
})
Object.defineProperty(obj, key, {
enumerable: true, // 是否可枚举
configurable: true, // 是否可删除
get: function () {
return val
},
set: function (newVal) {
if (val !== newVal) {
val = newVal
// 当新旧值不同且不为对象时 执行指令的页面更新
if (typeof newVal !== 'object') directive.update()
}
if (typeof newVal === 'object') self._each(newVal, newVal)
}
})
}
由于先执行节点指令的解析 所以需要在初始化完成后调用一下指令的 update() 方法,否则初始化时页面将不会显示数据
Lue
框架还有很多不够完善的地方,但我们的目标不是重现实现一个 Vue,而是去学习它的长处,去了解怎样实现一个相似的功能,从而提升自己的能力。感谢所有支持我的小伙伴,感谢你们的赞、评论和关注!让我们一起进步
最后无耻的做个广告:
查看完整代码,可以关注公众号,回复 Lue 即可!

网友评论