// 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 }}