Vue的雙向綁定原理
Vue采用資料劫持結合釋出者-訂閱者模式的方法,通過Object.defineProperty()來劫持各個屬性的setter,getter屬性,在資料變動話,通知訂閱者,觸發更新回調函數,重新渲染視圖
關鍵元素
observer 實作對vue各個屬性進行監聽
function observer(obj, vm){
Object.keys(obj).forEach(function(key){
defineReactive(vm, key, obj[key])
})
}
// Object.defineProperty改寫各個屬性
function defineReactive( obj, key, val ) {
// 每個屬性建立個依賴收集對象,get中收集依賴,set中觸發依賴,調用更新函數
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
// 觸發依賴
dep.notify()
val = newVal
}
})
}
-Dep實作
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 實作對vue各個指令模闆的解析器,生成AST抽象文法樹,編譯成Virtual Dom,渲染視圖
function compiler(node, vm){
var reg = /\{\{(.*)\}\}/;
// 節點類型為元素
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屬性指派,觸發修改屬性的setter
vm[_value] = e.target.value
})
node.value = vm[_value] // 将data的值指派給node
node.removeAttribute('v-model')
}
}
new Watcher(vm,node,_value,'input')
}
// 節點類型為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,接受每個屬性變動的通知,綁定更新函數,更新視圖
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() // 綁定更新函數
Dep.target = null //綁定完後登出 标志
}
Watcher.prototype = {
get: function(){
this.value = this.vm[this.name] //觸發observer中的getter監聽
},
update: function(){
this.get()
if(this.nodeType == 'text'){
this.node.nodeValue = this.value
}
if(this.nodeType == 'input') {
this.node.value = this.value
}
}
}
完整實作
function Vue(options){
this.date = options.data
var data = this.data
observer(data, this) // 監測
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"
}
})