一圖勝千言
原理:
Vue采用數(shù)據(jù)劫持結合發(fā)布者-訂閱者模式的方法幢尚,通過Object.defineProperty()來劫持各個屬性的setter,getter屬性,在數(shù)據(jù)變動話户魏,通知訂閱者付秕,觸發(fā)更新回調函數(shù)字旭,重新渲染視圖
關鍵要素
- observer 實現(xiàn)對vue各個屬性進行監(jiān)聽
function observer(obj, vm){
Object.keys(obj).forEach(function(key){
defineReactive(vm, key, obj[key])
})
}
// Object.defineProperty改寫各個屬性
function defineReactive( obj, key, val ) {
// 每個屬性建立個依賴收集對象,get中收集依賴,set中觸發(fā)依賴哟沫,調用更新函數(shù)
var dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function() {
// 收集依賴 Dep.target標志
Dep.target && dep.addSub(Dep.target)
return val
},
set: function(newVal){
if(newVal === val) return
// 觸發(fā)依賴
dep.notify()
val = newVal
}
})
}
-Dep實現(xiàn)
function Dep(){
this.subs = []
}
Dep.prototype = {
constructor: Dep,
addSub: function(sub){
this.subs.push(sub)
},
notify: function(){
this.subs.forEach(function(sub){
sub.update() // 調用的Watcher的update方法
})
}
}
- compiler 實現(xiàn)對vue各個指令模板的解析器,生成AST抽象語法樹锌介,編譯成Virtual Dom嗜诀,渲染視圖
// 編譯器
function compiler(node, vm){
var reg = /\{\{(.*)\}\}/;
// 節(jié)點類型為元素
if(node.nodeType ===1){
var attr = node.attributes;
// 解析屬性
for(var i=0; i< attr.length;i++){
if(attr[i].nodeName == 'v-model'){
var _value = attr[i].nodeValue
node.addEventListener('input', function(e){
//給相應的data屬性賦值,觸發(fā)修改屬性的setter
vm[_value] = e.target.value
})
node.value = vm[_value] // 將data的值賦值給node
node.removeAttribute('v-model')
}
}
new Watcher(vm,node,_value,'input')
}
// 節(jié)點類型為text
if(node.nodeType ===3){
if(reg.test(node.nodeValue)){
var name = RegExp.$1;
name = name.trim()
new Watcher(vm,node,name,'input')
}
}
}
- Watcher 連接observer和compiler,接受每個屬性變動的通知孔祸,綁定更新函數(shù)隆敢,更新視圖
function Watcher(vm,node,name, nodeType){
Dep.target = this; // this為watcher實例
this.name = name
this.node = node
this.vm = vm
this.nodeType = nodeType
this.update() // 綁定更新函數(shù)
Dep.target = null //綁定完后注銷 標志
}
Watcher.prototype = {
get: function(){
this.value = this.vm[this.name] //觸發(fā)observer中的getter監(jiān)聽
},
update: function(){
this.get()
if(this.nodeType == 'text'){
this.node.nodeValue = this.value
}
if(this.nodeType == 'input') {
this.node.value = this.value
}
}
}
完整實現(xiàn)
function Vue(options){
this.date = options.data
var data = this.data
observer(data, this) // 監(jiān)測
var id = options.el
var dom = nodeToFragment(document.getElmentById(id),this) //生成Virtual Dom
// 編譯完成后,生成視圖
document.getElementById(id).appendChild(dom)
}
function nodeToFragment(node, vm){
var flag = document.createDocumentFragment()
var child
while(child = node.firstChild){
compiler(cild, vm)
flag.appendChild(child)
}
return flag
}
// 調用
var vm = new Vue({
el: "app",
data: {
msg: "hello word"
}
})
參考文獻:
https://juejin.im/post/5b2f0769e51d45589f46949e
https://juejin.im/post/5b80e60de51d4557b85fc8fc