vue的原理相信很多同窗都已經看過,源碼分析也有許多大牛分析過。不過不知道你們有沒有這種的感受,原理看起來很明白了,可是源碼一看就懵。最近在學習尤大在frontend masters的課。(Advanced Vue.js Features from the Ground Up)忽然以爲有點豁然開朗。那爲何不能從Ground Up開始實現一個vue.js呢。因此決定按照尤大課上的思路寫一寫。做者說過vue是漸進式的框架,那麼我想也用漸進式的思路去寫寫。每一步都去解決一點問題。vue
咱們將響應式的實現分爲3步,最終實現一個可以雙向數據綁定的Vuenode
本文代碼下載git
一、轉化:convert函數實現es6
目標:實現b的值可以跟隨a值自動變化?github
let data = {
a1 : 1,
a2 : 2
}
b = data.a1*10;
data.a1 = 2;
console.log(b) //20 b不須要從新複製就能夠自動跟隨data.a1變化
複製代碼
分析:數組
代碼:bash
function convert(obj){
let arr = Object.keys(obj); // 將對象中的key值取出
arr.forEach((key)=>{ // 每一個屬性用 Object.defineProperty轉化
let inertValue = obj[key]; // 保存初始值
Object.defineProperty(obj,key,{
get(){
return inertValue; // 訪問時給出key的值
},
set(newValue){
inertValue = newValue; // 賦值時將新值賦給inertValue
b = data.a1*10; // 執行相關響應式操做
}
})
})
}
convert(data)
複製代碼
問題:首先咱們還須要把全部依賴data.a1的操做手動寫到set函數中去,其次在給data中其餘屬性(如data.a2)賦值時也會觸發set中的操做app
二、依賴的處理框架
class Dep{
constructor(){
this.subscribers = []
}
depend(){
this.subscribers.push(saveFunction);
}
notify(){
this.subscribers.forEach(fn=>fn())
}
}
let saveFunction;
function autorun(fn){
saveFunction = fn;
fn();
saveFunction = null;
}
複製代碼
function convert(obj){
Object.keys(data).forEach((key)=>{
let dep = new Dep(); //爲每一個key建立一個dep實例
let inertValue = obj[key];
Object.defineProperty(obj,key,{
get(){
dep.depend() //當取值時,收集依賴
return inertValue;
},
set(newValue){
inertValue = newValue;
dep.notify() //當設置值時,觸發依賴
}
})
})
}
let data = {
a1 : 1,
a2 : 2
}
convert(data)
autorun(()=>{b = data.a1*10;}) //將全部響應式的操做經過autorun去執行
data.a1 = 2;
console.log(b) //20, b值能夠跟隨a值去自動響應
複製代碼
問題:當咱們在set中觸發依賴的函數執行時,就會從新出發get收集依賴?frontend
depend(){
if(!saveFunction){
this.subscribers.push(saveFunction);
}
}
複製代碼
問題:如今咱們已經很好的完成了對數據的響應式處理,若是操做的是視圖咱們該如何綁定呢?
三、編譯:數據與視圖進行綁定
目標:實現一個數據綁定
<div id = "app">
<div>{{message}}</div>
<input v-modle = "message" >
</div>
let vm = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
複製代碼
分析:
function Vue (options){
Observe(options.data,this) //將data數據進行轉化
let dom = document.getElementById(options.el); //獲模版的dom元素
let newDom = compile(dom,this); //對dom元素進行編譯
dom.appendChild(newDom); //添加到文本中
}
複製代碼
function compile(node,vm){
let frag = document.createDocumentFragment();
let child = node.firstElementChild;
while(child){
compileElement(child,vm)
child = child.nextElementSibling;
}
return frag;
}
複製代碼
function compileElement(node,vm){
let attr = node.attributes;
for(let i = 0; i<attr.length ; i++){
let name = attr[i].nodeValue;
console.log(name)
switch (attr[i].nodeName) {
case "v-model":
node.addEventListener("input",(e)=>{
vm[name] = e.target.value;
console.log(e.target.value)
})
autorun(()=>{ node.value = vm[name]})
break;
}
}
let reg = /\{\{(.*)\}\}/;
if(reg.test(node.innerHTML)){
var name = RegExp.$1;
name = name.trim();
autorun(()=>{ node.innerHTML = vm[name]})
}
}
複製代碼
問題:咱們發現autorun函數執行都是對dom的屬性值的修改,因此咱們能夠進行進一步的抽象
class Watcher{
constructor(node,type,vm,name){
Dep.target = this;
this.node = node;
this.name = name;
this.type = type;
this.vm = vm;
this.update();
Dep.target = null;
}
update(){
this.node[this.type] = this.vm[this.name];
}
}
new Watcher(node,"value" ,vm ,name )
//autorun(()=>{ node.value = vm[name]})
new Watcher(node,"innerHTML" ,vm ,name )
//autorun(()=>{ node.value = vm[name]})
複製代碼
如今咱們已經實現了Vue的響應式,是否是更加深入理解vue的MVVM模式是如何實現的啦。
以上就是Vue響應式的核心原理,編譯過程與vue1x版本相似,由於vue.2x引用了虛擬Dom過程會比較複雜。會在下一篇專門去講。
問題:這裏你們思考一個問題,在這一版本中,Vue直接用watcher操做的Dom, 若是咱們綁定的數據是一個數組,當只更改數組中的一小部分,此時Dom從新渲染的效率會不會很低。咱們該如何改進,哈哈哈,固然是使用虛擬dom啦~下一節會詳細講解,下面是下一節的原理圖。