用了Vue也有两年时间了,一直以来都是只知其然,不知其所以然,为了能更好的使用Vue不被Vue所奴役,学习一下Vue底层的基本原理。
Vue官网有一段这样的介绍:当你把一个普通的JavaScript对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter。Object.defineProperty是ES5中一个无法shim的特性,这也就是为什么Vue不支持 IE8 以及更低版本浏览器。
通过这一段的介绍不难可以得出,Vue是通过Object.defineProperty对实例中的data数据做了挟持并且使用Object.defineProperty的getter/setter并对其进行处理之后完成了数据的与视图的同步。
这张图应该不会很陌生,熟悉Vue的同学如果仔细阅读过Vue文档的话应该都看到过。猜想一下Vue使用Object.defineProperty做为ViewModel,对数据进行挟持之后如果View和Model发生变化的话,就会通知其相对应引用的地方进行更新处理,完成视图的与数据的双向绑定。
下面举个例子:
html:
<div></div>
javaScript:
var obj = {}; Object.defineProperty(obj,"name",{ get() { return document.querySelector("#name").innerHTML; }, set(val) { document.querySelector("#name").innerHTML = val; } }) obj.name = "Aaron";
通过上面的代码使用Object.defineProperty对Obj对象中的name属性进行了挟持,一旦该属性发生了变化则会触发set函数执行,做出响应的操作。
扯了这么多,具体说一下Vue实现的原理。
需要数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者。
需要指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。
MVVM入口函数,整合以上三者,实现数据响应。
接下来的文章将沿着这个思路一步一步向下进行,以便完成一个简单的Vue类,完成数据与视图的实时更新。
<div> <p>{{name}}</p> <p q-text="name"></p> <p>{{age}}</p> <p>{{doubleAge}}</p> <input type="text" q-model="name"/> <button @click="changeName">点击</button> <div q-html="html"></div> </div> <script> new QVue({ el:"#app", data:{ name:"I am test", age:12, html:"<button>这是一个后插入的按钮</button>" }, created(){ console.log("开始吧,QVue"); setTimeout(() => { this.name = "测试数据,更改了么"; },2000) }, methods:{ changeName(){ this.name = "点击啦,改变吧"; this.age = 1000000; } } }) </script>
以上代码则是需要完成的功能,保证所有功能全部都能实现。
首先我们要考虑的是,要创建一个Vue的类,该类接收的是一个options的对象,也就是我们在实例化Vue的时候需要传递的参数。
class QVue { constructor(options){ // 缓存options对象数据 this.$options = options; // 取出data数据,做数据响应 this.$data = options.data || {}; } }
通过上面的代码可以看出了,为什么我们可以在Vue实例上通过this.$data拿到我们所写的data数据。
对数据已经进行了缓存之后,接下来要做的事情就是对数据进行观察,达到数据变化之后能够做出对虚拟Dom的操作。
class QVue { constructor(options){ this.$options = options; // 数据响应 this.$data = options.data || {}; // 监听数据变化 this.observe(this.$data); // 主要用来解析各种指令,比如v-modal,v-on:click等指令 new Compile(options.el,this); // 执行生命周期 if(options.created){ options.created.call(this); } } // 观察数据变化 observe(value){ if(!value || typeof value !== "object"){ return; } let keys = Object.keys(value); keys.forEach((key)=> { this.defineReactive(value,key,value[key]); // 代理data中的属性到vue实例上 this.proxyData(key); }) } // 代理Data proxyData(key){ Object.defineProperty(this,key,{ get(){ return this.$data[key]; }, set(newVal){ this.$data[key] = newVal; } }) } // 数据响应 defineReactive(obj,key,val){ // 解决数据层次嵌套 this.observe(val); const dep = new Dep(); Object.defineProperty(obj, key,{ get(){ // 向管理watcher的对象追加watcher实例 // 方便管理 Dep.target && dep.appDep(Dep.target); return val; }, set(newVal){ if(newVal === val){ return; } val = newVal; // console.log(`${key}更新了:${newVal}`) dep.notify(); } }) } }