compiler——编译模板:编译解析模板中的差值表达式和指令,从而实现数据变化更新视图,视图变化更新数据
vue.js文件的作用——把data中的成员挂载到vue实例上,并设置setter和getter
observer.js文件的作用——数据劫持——即:监听数据的变化
1.把$data中的属性设置为getter/setter
2.当数据发生改变的时候,要发送通知(发送消息)
数据劫持也是在vue.js中进行调用,只是具体实现定义到一个文件中,保证vue.js文件中的代码是少的;
各个文件各司其职:vue.js是初始化,是个入口
// 数据劫持,就是把data中的成员设置为setter/getter
new Observer(this.$data)
// 编译模板,就是编译模板中的差值表达式和指令
new Compiler(this)
emit 必须是同一个对象,才可以使用
compiler.js
中
// 编译模板
// 编译模板中的差值表达式和指令
function Compiler(vm) {
this.vm = vm
// 开始编译
this.compile(vm.$el)
}
// 编译模板
Compiler.prototype.compile = function(el) {
// Array.from传入一个伪数组,返回一个数组
// 遍历el中的所有子节点
Array.from(el.childNodes).forEach(node => {
// 判断节点的类型
if (this.isElementNode(node)) {
// 元素节点
this.compileElement(node)
// 如果是元素节点,继续遍历该元素节点的子节点
this.compile(node)
} else if (this.isTextNode(node)) {
// 文本节点
this.compileTextNode(node)
}
})
}
// 判断节点是否是元素节点
Compiler.prototype.isElementNode = function(node) {
return node.nodeType === 1
}
// 判断节点是否是文本节点
Compiler.prototype.isTextNode = function(node) {
return node.nodeType === 3
}
// 编译元素节点
Compiler.prototype.compileElement = function(node) {
// 遍历元素中的所有属性节点 node.attributes
Array.from(node.attributes).forEach(attr => {
// attr 属性节点,对象
const key = attr.name
// 判断属性的名字是否是指令
if (this.isDirective(key)) {
// 判断指令的类型
// v-model
if (key === 'v-model') {
// 如果当前的属性是v-model,获取属性的值 v-model="name"
// value -> name
const value = attr.value
node.value = this.vm[value]
// 订阅事件,当属性的值发生变化要更新视图,即给node.value赋值
em.$on(value, () => {
node.value = this.vm[value]
})
// 当视图变化,更新数据
node.oninput = () => {
// 把文本框中的数据,设置给data
this.vm[value] = node.value
// this.vm.name = node.value
}
} else if (key === 'v-text') {
// v-text
const value = attr.value
node.textContent = this.vm[value]
// 当数据变化的时候更新视图
em.$on(value, () => {
node.textContent = this.vm[value]
})
}
}
})
}
// 编译文本节点
Compiler.prototype.compileTextNode = function(node) {
// 获取文本节点中的内容
const txt = node.textContent
// 判断文本节点中是否有差值表达式
const reg = /\{\{(.+)\}\}/
if (reg.test(txt)) {
// 获取到差值表达式中的key
// {{ name }} ====> name
const key = RegExp.$1.trim()
// data[key] 值
const value = this.vm[key]
node.textContent = txt.replace(reg, value)
// 当值发生变化,重新更新视图
em.$on(key, () => {
node.textContent = this.vm[key]
})
}
}
// 判断是否是指令 v-开头
Compiler.prototype.isDirective = function(attrName) {
return attrName.startsWith('v-')
}
eventEmitter.js
中
function EventEmitter () {
this.subs = {}
}
// 注册事件
// 1. 事件类型 2. 事件处理函数
EventEmitter.prototype.$on = function (eventType, handler) {
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
// 触发事件
// 在function (eventType, ...args) 此时的args是剩余参数
// 剩余参数在参数列表的最后,args是数组
EventEmitter.prototype.$emit = function (eventType, ...args) {
if (this.subs[eventType]) {
// [fn, fn1]
this.subs[eventType].forEach(handler => {
// 此时的...args 是展开运算符
// 此时的this就是em对象
handler.call(this, ...args)
})
}
}
// 创建一个em的对象,让observer和compiler去使用
// observer 发布通知
// compiler 订阅事件
const em = new EventEmitter()
编译
获取节点
function Compiler (vm) {
this.vm = vm
this.compile(this.vm.$el)
}
Compiler.prototype.compileText = function (node) {
console.log('text', node)
}
Compiler.prototype.compileElement = function (node) {
console.log('element', node)
}
Compiler.prototype.compile = function (el) {
// 获取所有子节点
el.childNodes.forEach((node) => {
switch (node.nodeType) {
case 1:
// nodeType === 1 元素节点
this.compileElement(node)
// 如果是元素节点,递归调用
this.compile(node)
break
case 3:
// nodeType === 3 文本节点
this.compileText(node)
break
}
})
}
编译文本节点
Compiler.prototype.compileText = function (node) {
const reg = /\{\{(.+)\}\}/
// 判断是否匹配正则
if (reg.test(node.nodeValue)) {
// 获取正则匹配第一个分组的内容
const key = RegExp.$1.trim()
// 把正则匹配的结果替换成,指定的数据
node.nodeValue = this.vm[key]
}
}
编译元素节点
Compiler.prototype.compileElement = function (node) {
// 所有属性节点
// console.dir(node)
Array.from(node.attributes).forEach((attr) => {
let { name, value } = attr
name = name.toLocaleLowerCase()
// 判断是否以 v- 开头
if (!name.startsWith('v-')) {
return
}
switch (name) {
case 'v-model':
node.value = this.vm[value]
break
}
})
}
监听数据变化
const em = new EventEmitter()
// 在处理文本节点的时候,如果 data 中的值变化了,给本文重新赋值
em.$on(key, () => {
node.nodeValue = this.vm[key]
})
// 在处理 v-model 的时候,如果 data 中的值变化了,重新给元素赋值
em.$on(value, () => {
node.value = this.vm[value]
})
// 当文本框的值改变把文本框的值赋给 data
node.oninput = () => {
this.vm[value] = node.value
}
// 在属性的 set 方法中,触发事件
em.$emit(key)
推荐阅读
-
剖析Vue实现原理 - 如何实现双向绑定mvvm:
-
深入响应式原理