Vue2源码学习
感觉工作了几个月了,也该看一看源码了,然后就捣鼓出了个盗版vue.js
实现的功能有模板渲染,生命周期,数据劫持,双向绑定,添加点击事件等很基础的功能,下面是源码
每天看掘金上的源码理论讲解,什么compile和watcher之类的看麻了,实现一下之后就有很大的感悟
首先在Vue的类中将$el和$data挂载在实例对象上,方便后续的操作
模板的编译思想就是,设置正则匹配,获取根节点后,再去获取根节点的子节点,循环去匹配文本节点,利用字符串的replace方法结合正则表达式,匹配到设置了模板字符串的文本节点和键,再利用键从data中获取相应值去替换文本节点
生命周期简单些,就是传递几个函数,判断类型,如果是函数就执行,顺序不能随便放,执行顺序是固定的,后期需要通过bind改变this指向
这是html内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| copyvye.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <h1>{{ str }}</h1> <h2>{{ b }}</h2> <input type="text" v-model="modelStr"> <button @click="changeStr">change str</button> <h1>{{ modelStr }}</h1> </div> <script src="./copyVue.js"></script> <script> new Vue({ el:"#app", data:{ str:'hao hello', b:'好嗨欧', modelStr:'v-model关联字符' }, methods: { changeStr(){ this.str = '我终于更新了' } }, beforeCreate() { console.log('beforeCreate') }, created() { console.log('created') }, beforeMount() { console.log('beforeMount') }, mounted() { console.log('mounted') } }) </script> </body> </html>
|
这是copyVue.js内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| copyVue.js class Vue { constructor(options){ if(typeof options.beforeCreate === 'function'){ options.beforeCreate.bind(this)() } this.$options = options this.$data = options.data this.$watchEvent = {} this.proxyData() this.observe() if(typeof options.created === 'function'){ options.created.bind(this)() } if(typeof options.beforeMount === 'function'){ options.beforeMount.bind(this)() } this.$el = document.querySelector(options.el) if(typeof options.mounted === 'function'){ options.mounted.bind(this)() } this.compile(this.$el) } // 劫持数据 此时并未更新视图 proxyData(){ for(let key in this.$data){ Object.defineProperty(this,key,{ get(){ return this.$data[key] }, set(val){ this.$data[key] = val } }) } } // 数据被修改但是视图并未更新 observe(){ for(let key in this.$data){ let value = this.$data[key] let that = this Object.defineProperty(this.$data,key,{ get(){ return value }, set(val){ value = val if(that.$watchEvent[key]){ that.$watchEvent[key].forEach(item => { item.update() }) } } }) } } compile(node){ let reg = /\{\{(.*?)\}\}/g; node.childNodes.forEach(item => { // 首先需要判断节点类型 if(item.nodeType === 1){ if(item.hasAttribute('@click')){ let vmKey = item.getAttribute('@click').trim() item.addEventListener('click',(event)=>{ this.eventFn = this.$options.methods[vmKey].bind(this) this.eventFn(event) }) } if(item.hasAttribute('v-model')){ let vmKey = item.getAttribute('v-model').trim() if(this.hasOwnProperty(vmKey)){ item.value = this[vmKey] } item.addEventListener('input',(event)=>{ this[vmKey] = item.value }) } if(item.childNodes.length > 0){ this.compile(item) } } if(item.nodeType === 3){ // 使用另外一个变量保存文本节点的内容 let nodeContent = item.textContent item.textContent = nodeContent.replace(reg,(match,vmKey)=>{ vmKey = vmKey.trim() if(this.hasOwnProperty(vmKey)){ let watcher = new Watch(this,vmKey,item,'textContent') if(this.$watchEvent[vmKey]){ this.$watchEvent[vmKey].push(watcher) }else{ this.$watchEvent[vmKey] = [] this.$watchEvent[vmKey].push(watcher) } } return this.$data[vmKey] }) } }) } } class Watch { constructor(vm,key,node,attr){ this.vm = vm this.key = key this.node = node this.attr = attr } update(){ this.node[this.attr] = this.vm[this.key] } }
|
loading……