- 创建index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue基本结构</title></head><body> <div id="app"> <h1 v-on:click="myClick">点击事件</h1> <h1>插值表达式</h1> <h3>{{msg}}</h3> <h3>{{count}}</h3> <h1 v-html="html">v-html</h1> <h1>v-text</h1> <div v-text="msg"></div> <h1>v-model</h1> <input type="text" v-model="msg"> <input type="text" v-model="count"> </div> <script src="./js/Watcher.js"></script> <script src="./js/Dep.js"></script> <script src="./js/compile.js"></script> <script src="./js/observer.js"></script> <script src="./js/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { msg: 'hello vue', count: 200, content: { foo: 12 }, html: '<button style="color: red; font-size: 26px">v-html</button>' }, methods: { myClick() { alert('点击事件') }, } }) </script></body></html>
- 创建js/vue.js
class Vue { constructor (options){ //1.保存选项的数据 this.$options = options || {} this.$el =typeof options.el ==='string' ? document.querySelector(options.el):options.el this.$data = options.data || {} this.$methods = options.methods || {} //2.负责把data中的成员转换成getter和setter,注入到Vue实例 this._proxyData(this.$data) //把methods中的成员注入到vue实例中 this._proxyMethods(this.$methods) //3.负责调用observer实现数据劫持 new Observer(this.$data) //4.负责调用Compiler解析指令/插值表达式等 new Compiler(this) } _proxyData (data) { //遍历data的所有属性 Object.keys(data).forEach(key => { //把data的属性注入到vue实例中 Object.defineProperty(this, key, { enumerable:true, configurable:true, get () { return data[key] }, set (newValue) { if(newValue === data[key]){ return } data[key] = newValue } }) }) } _proxyMethods(methods) { Object.keys(methods).forEach(key => { //把methods中的成员注入到vue实例中 this[key] = methods[key] }) }}
-
创建js/observer.js
//负责数据劫持//把$data中的成员转换成getter/setterclass Observer { constructor (data) { this.walk(data) } //1.判断data是否是对象,如果不是对象返回 //2.如果是对象,遍历对象的所有属性,设置为getter/setter walk (data) { if(!data || typeof data !== 'object'){ return } //遍历data的所有成员 Object.keys(data).forEach(key => { this.defineReactive(data,key,data[key]) }) } //定义响应式成员 defineReactive(data,key,val) { let that = this //创建dep对象收集依赖 const dep = new Dep() //如果val是对象,继续设置它下面的成员成为响应式数据 this.walk(val) Object.defineProperty(data,key, { enumerable:true, configurable:true, get () { //get的过程中收集依赖 Dep.target && dep.addSub(Dep.target) return val }, set(newValue) { if(newValue === val){ return } val = newValue //如果newValue是对象,设置newValue的成员成为响应式 that.walk(newValue) //当数据变化时,发送通知 dep.notify() } }) }}
-
创建js/compile.js
//负责解析指令/插值表达式class Compiler { constructor(vm) { this.vm = vm this.el = vm.el //编译模板 this.compile(this.el) } //编译模板 //处理文本节点和元素节点 compile (node) { //childNodes属性可返回指定节点的子节点的节点列表 let nodes = node.childNodes Array.from(nodes).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) } }) } //编译元素节点,处理指令 compileElement (node) { Array.from(node.attributes).forEach(attr => { //获取元素属性的名称 let attrName = attr.name //判断当前属性名称是否是指令 if(this.isDirective(attrName)){ //截取属性的名称 attrName = attrName.substr(2) let key = attr.value if(attrName.startsWith('on')){ const event = attrName.replace('on:','') //事件更新 this.eventUpdate(node, key, event) }else{ this.update(node,key,attrName) } } }) } eventUpdate(node,key,event) { this.onUpdater(node,key,event) } update (node, key, attrName) { let updateFn = this[attrName + 'Updater'] updateFn && updateFn.call(this, node,this.vm[key],key) } // 处理 v-on 指令 onUpdater(node, key, event) { node.addEventListener(event, (e) =>this.vm[key](e)) } //处理html指令 htmlUpdater (node ,value, key) { node.innerHTML = value new Watcher(this.vm, key, newValue => { node.innerHTML = newValue }) } //处理v-text指令 textUpdater (node, value, key) { node.textContent = value //每一个指令中创建一个watcher,观察数据的变化 new Watcher(this.vm, key, newValue => { node.textContent = newValue }) } //处理v-model指令 modelUpdater (node, value, key) { node.value = value //每一个指令中创建一个watcher,观察数据的变化 new Watcher(this.vm, key, newValue => { node.value = newValue }) //监听视图的变化,双向绑定 node.addEventListener('input', () => { this.vm[key] = node.value }) } //编译文本节点,处理插值表达式 compileText (node) { // console.dir(node); //js正则表达式的非贪婪模式 ? const reg = /\{\{(.+?)\}\}/ const value = node.textContent if(reg.test(value)){ //插值表达式中的值就是我们要的属性名称 //RegExp是js中的一个内置对象,为正则表达式 //RegExp.1是RegExp的一个属性,指的是与正则表达式匹配的第一个子匹配(以括号为标志)字符串 let key = RegExp.$1.trim() node.textContent = value.replace(reg, this.vm[key]) new Watcher(this.vm,key,newValue => { node.textContent = newValue }) } } //判断元素属性是否是指令 isDirective (attrName) { return attrName.startsWith('v-') } //判断节点是否是文本节点 isTextNode (node) { return node.nodeType === 3 } //判断节点是否是元素节点 isElementNode (node) { return node.nodeType === 1 }}
创建js/Dep.js
class Dep { constructor () { //存储所有的观察者 this.subs = [] } //添加观察者 addSub (sub) { if(sub && sub.update){ this.subs.push(sub) } } //通知所有观察者 notify () { this.subs.forEach(sub => { sub.update() }) }}
-
创建js/Watcher.js
class Watcher { constructor(vm, key, cb) { this.vm = vm //data中的属性名称 this.key = key //当数据变化的时候,调用cb更新视图 this.cb = cb //在Dep的静态属性上记录当前watcher对象,当访问数据的时候把watcher添加到dep的subs中 Dep.target = this //触发一次getter,让dep为当前key记录watcher this.oldValue = vm[key] //清空target Dep.target = null } update(){ const newValue = this.vm[this.key] if(this.oldValue === newValue) { return } this.cb(newValue) }}
-
v-html实现:
-
vue实例初始化时,调用new Compiler(this)解析指令和插值表达式
-
在Compiler类中编译模板,处理文本节点和元素节点:this.compile(this.el)
-
处理元素节点:this.compileElement(node)
-
compileElement(node)方法会遍历这个节点写下的属性,如果是指令则调用相对应的指令处理函数:
-
htmlUpdater:处理v-html指令
-
textUpdater:处理v-text指令
-
modelUpdater:处理v-model指令
-
onUpdater:处理v-on指令
-
v-on实现:
-
vue实例初始化时,调用new Compiler(this)解析指令和插值表达式
-
把methods中的成员注入到Vue实例中,this._proxyMethods(this.$methods)
-
在Compiler类中编译模板,处理文本节点和元素节点:this.compile(this.el)
-
处理元素节点:this.compileElement(node)
-
如果找到属性名是以on开头的,则调用this.eventUpdate(node,key,event)
-
eventUpdate调用onUpdater来处理v-on指令