一图胜千言
原理:
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"
}
})
参考文献:
https://juejin.im/post/5b2f0769e51d45589f46949e
https://juejin.im/post/5b80e60de51d4557b85fc8fc