var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
});
function Vue (options) {
// this => new Vue的实例
// 保证了不同实例之间的数据
this.data = options.data; // 每个实例的data=new时传入的data
// data = {text: 'hello world'}
var data = this.data;
// 为data内的"text"添加get()/set()
observe(data, this);
var id = options.el;
// 执行编译
var dom = new Compile(document.getElementById(id), this);
document.getElementById(id).appendChild(dom);
}
实现Vue构造函数,并实例化传参。
Observer
function observe (obj, vm) { // obj => data vm => this(实例)此时的this也包含data数据
// 普通遍历obj,得到所有key和value(obj[key])
Object.keys(obj).forEach(function(key) { // key => data的每个属性
defineReactive(vm, key, obj[key]); // obj[key] => data对应的每个属性值
})
}
function defineReactive(obj, key, val) { // obj => this(包含data)
var dep = new Dep(); // new出的实例包含一个数组和两个方法
Object.defineProperty(obj, key, {
get: function () {
// 添加订阅者watcher到主题对象Dep
if(Dep.target) {
// JS的浏览器单线程特性,保证这个全局变量在同一时间内,只会有同一个监听器使用
dep.addSub(Dep.target);
}
return val;
},
set: function (newVal) {
if(newVal === val) return;
// 从input输入的新值
val = newVal;
// 作为发布者发出通知
dep.notify();
}
})
}
监听实例的所有属性,如果发生变化则通过发布者(Dep)通知给订阅者(Watcher)。
Compile
// node => id节点 vm => this实例
function Compile(node, vm) {
if (node) {
this.$frag = this.nodeToFragment(node, vm);
return this.$frag;
}
}
Compile.prototype = {
nodeToFragment: function (node, vm) {
var self = this; // 缓存this
var frag = document.createDocumentFragment();
var child;
// 循环 尾递归
while(child = node.firstChild) {
// 当前元素的子节点
self.compileElement(child, vm);
// 将所有子节点添加到fragment中
frag.append(child);
}
return frag;
},
compileElement: function (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') {
// 获取v-model绑定的属性名
var name = attr[i].nodeValue;
// 监听input输入事件
node.addEventListener('input', function(e) {
// 给相应的data属性赋值,进而触发该属性的set方法 => Dep.notify() => Watcher.update()
vm[name] = e.target.value;
});
// node.value = vm[name]; // 将data的值赋给该node
// 完成了数据的初始化及建立Watcher
new Watcher(vm, node, name, 'value');
}
}
}
// 节点类型为text
if(node.nodeType === 3) {
if(reg.test(node.nodeValue)) {
// 获取匹配到的字符串
var name = RegExp.$1;
name = name.trim();
// node.value = vm[name]; // 将data的值赋给该node
// 完成了数据的初始化及建立Watcher
new Watcher(vm, node, name, 'nodeValue');
}
}
}
}
双向数据绑定
Vue采用数据劫持结合发布者-订阅者模式 #13 的方式,通过
Object.defineProperty()
来劫持各个属性的getter/setter,在数据发生变化时发布消息给订阅者,触发相应的监听回调。MVVM
实现Vue构造函数,并实例化传参。
Observer
监听实例的所有属性,如果发生变化则通过发布者(Dep)通知给订阅者(Watcher)。
Compile
解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者(Wathcer),一旦数据有变动,收到通知,更新视图。
Watcher
set() => Dep.notify() => Watcher.update()
更新视图Dep
Vue整体流程
1. 解析模板成render函数
v-mod el v-for v-on
都变成了JS逻辑2. 响应式开始监听
Object.defineProperty
3. 首次渲染,显示页面,且绑定依赖
vm._update(vnode)
vm.__patch__
4. data属性变化
updateComponent
updateComponent
重新执行vm._render()
patch
进行对比