摘要:
你知道怎样撸一个Vue吗?
Fundebug经授权转载,版权归原作者所有。
vue的使用相信大家都很熟练了,使用起来简单。但是大部分人不知道其内部的原理是怎么样的,今天我们就来一起实现一个简单的vue
Object.defineProperty()
实现之前我们得先看一下Object.defineProperty的实现,因为vue主要是通过数据劫持来实现的,通过
get
、
set
来完成数据的读取和更新。
var obj = {name :'wclimb' }var age = 24 Object .defineProperty(obj,'age' ,{ enumerable: true , configurable: false , get () { return age }, set (newVal) { console .log('我改变了' ,age +' -> ' +newVal); age = newVal } }) > obj.age > 24 > obj.age = 25 ; > 我改变了 24 -> 25 > 25
从上面可以看到通过
get
获取数据,通过
set
监听到数据变化执行相应操作,还是不明白的话可以去看看Object.defineProperty文档。
流程图
html代码结构
<div id ="wrap" > <p
v-html ="test" > p > <input type ="text" v-model ="form" > <input type ="text" v-model ="form" > <button @click ="changeValue" > 改变值button > {{form}} div >
js调用
new Vue({ el: '#wrap' , data:{ form: '这是form的值' , test: '我是粗体 ' , }, methods:{ changeValue(){ console .log(this .form) this .form = '值被我改变了,气不气?' } } })
Vue结构
class Vue { constructor (){} proxyData(){} observer(){} compile(){} compileText(){} } class Watcher { constructor (){} update(){} }
Vue constructor 初始化
class Vue { constructor (options = {}){ this .$el = document .querySelector(options.el); let data = this .data = options.data; Object .keys(data).forEach((key )=> { this .proxyData(key); }); this .methods = options.methods this .watcherTask = {}; this .observer(data); this .compile(this .$el); } }
上面主要是初始化操作,针对传过来的数据进行处理
proxyData 代理data
class Vue { constructor (options = {}){ ...... } proxyData(key){ let that = this ; Object .defineProperty(that, key, { configurable: false
, enumerable: true , get () { return that.data[key]; }, set (newVal) { that.data[key] = newVal; } }); } }
上面主要是代理
data
到最上层,
this.xxx
的方式直接访问
data
observer 劫持监听
class Vue { constructor (options = {}){ ...... } proxyData(key){ ...... } observer(data){ let that = this Object .keys(data).forEach(key => { let value = data[key] this .watcherTask[key] = [] Object .defineProperty(data,key,{ configurable: false , enumerable: true , get(){ return value }, set(newValue){ if (newValue !== value){ value = newValue that.watcherTask[key].forEach(task => { task.update() }) } } }) }) } }
同样是使用
Object.defineProperty
来监听数据,初始化需要订阅的数据。
把需要订阅的数据到
push
到
watcherTask
里,等到时候需要更新的时候就可以批量更新数据了下面就是;
遍历订阅池,批量更新视图。
set(newValue){ if (newValue !== value){ value = newValue that.watcherTask[key].forEach(task => { task.update() }) } }
compile 解析dom
class Vue { constructor (options = {}){ ...... } proxyData(key){ ...... } observer(data){ ...... } compile(el){ var nodes = el.childNodes; for (let i = 0 ; i < nodes.length; i++) { const node = nodes[i]; if (node.nodeType === 3 ){ var text = node.textContent.trim(); if (!text) continue ; this .compileText(node,'textContent' ) }else if (node.nodeType === 1 ){ if (node.childNodes.length > 0 ){ this .compile(node) } if (node.hasAttribute('v-model' ) && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA' )){ node.addEventListener('input' ,(() => { let attrVal = node.getAttribute('v-model' ) this .watcherTask[attrVal].push(new Watcher(node,this ,attrVal,'value' )) node.removeAttribute('v-model' ) return () => { this .data[attrVal] = node.value } })()) } if (node.hasAttribute('v-html' )){ let attrVal = node.getAttribute('v-html' ); this .watcherTask[attrVal].push(new Watcher(node,this ,attrVal,'innerHTML' )) node.removeAttribute('v-html' ) } this .compileText(node,'innerHTML' ) if (node.hasAttribute('@click' )){ let attrVal = node.getAttribute('@click' ) node.removeAttribute('@click' ) node.addEventListener('click' ,e => { this .methods[attrVal] && this .methods[attrVal].bind(this )() }) } } } }, compileText(node,type){ let reg = /\{\{(.*?)\}\}/g