// vue 类
class Vue {
/*
1. 通过属性保存数据
2. 将data中的数据转换为getter, setter,注入到vue实例中
3. 调用Observer, 监听data中的属性(响应式)
4. 调用compiler 解析指令和差值表达式
*/
constructor(options) {
// $options 保存 传入的options
this.$options = options || {}
// 保存 data
this.$data = this.$options.data || {}
// $el 绑定根元素
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
// 将data中的数据转换为getter, setter,注入到vue实例中
this._proxyData(this.$data)
// 调用Observer, 监听data中的属性(响应式)
new Observer(this.$data)
new Compiler(this)
}
_proxyData(data) {
Object.keys(data).forEach(key => {
// 这里的this是vue实例
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get() {
return data[key]
},
set(newValue) {
if(newValue === data[key]) {
return
}
data[key] = newValue
}
})
})
}
}
observer.js 监听数据,将数据变成响应式
/*
1. 负责将传入的data中的属性 转化为响应式数据
2. 如果 data里面的某个属性也是对象,也需要 处理
3. 数据变化,发送通知
*/
class Observer {
constructor(data) {
this.walk(data)
}
walk(data) {
if(!data || typeof data !== 'object') {
return
}
// 遍历data, 每一个属性都需要处理
Object.keys(data).forEach(key => {
this.defineReactvie(data, key, data[key])
})
}
defineReactvie(obj, key, val) {
const that = this
// 如果val也是对象,其下属的属性也需要处理
this.walk(val)
Object.defineProperty(obj, key,{
configurable: true,
enumerable: true,
get() {
// 如果写 obj[key], 就会进入死循环
return val
},
set(newValue) {
if(newValue === obj[key]) {
return
}
// 如果写 obj[key], 就会进入死循环
val = newValue
// 当赋值为对象时也需要 响应式
that.walk(newValue)
}
})
}
}
compiler.js 编译模板,解析差值表达式 和 指令
// 编译
/*
1. 负责编译模板,解析指令/差值表达式
2. 负责页面首次渲染
3. 当数据变化时重新渲染视图
结构:
// 变量
el
vm
// 方法
compile(el)
compileText(node)
compileElement(node)
isDirective
isTextNode
*/
class Compiler {
constructor(vm) {
this.el = vm.$el
this.vm = vm
this.compile(this.el)
}
// 入口,负责编译
compile(el) {
// 获取所有的节点,伪数组
const childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
if(this.isTextNode(node)) {
// 文本节点
this.compileText(node)
} else if(this.isElementNode(node)) {
this.compileElement(node)
}
// 如果子节点下还有节点,也需要 递归处理
if(node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
// 负责编译 文本节点, 解析差值表达式
compileText(node) {
// {{ msg }} , 形式
/*
1. 取出 msg
2. 获取msg对应的值,并返回
*/
// . 匹配所有字符, + 多个, ? 非贪婪匹配,尽可能结束 () 将匹配到的 分组
const reg = /\{\{(.+?)\}\}/ // 匹配{{}}
const value = node.textContent
if(reg.test(value)) { // 如果获取到 表达式,那么就处理
const key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
}
}
// 负责编译 元素节点,获取其属性,解析 指令
compileElement(node) {
Array.from(node.attributes).forEach(attr => {
let attrName = attr.name
if(this.isDirective(attrName)) {
// 解析指令
// v-text v-model v-html, v-on等
attrName = attrName.slice(2,)
console.log(node, attrName, 'asdf')
const key = attr.value
this.update(node, attrName, key )
}
})
}
update(node, attrName, key) {
// 根据 指令 名不同 调用不同的 函数
const updateFn = this[attrName+'Updater']
updateFn && updateFn(node, this.vm[key])
// v-on 还未处理
}
textUpdater(node, value) {// node,当前node, 指令对应的值
node.textContent = value
}
modelUpdater(node, value) {
node.value = value
}
htmlUpdater(node, value) {
node.innerHTML = value
}
// 是否为指令
isDirective(attrName) {
return attrName.startsWith('v-')
}
// 是否为 文本节点
isTextNode(node) {
return node.nodeType === 3
}
isElementNode(node) {
return node.nodeType === 1
}
}