翻看源码: 在其工程目录src下发现以下文件夹:前端
1. /compiler 目录是编译模板;
2. /core 目录是vue.js的核心
3. /planforms 目录是针对核心模块的‘平台’模块 (web weex)
4. /server 目录是处理服务端渲染;
5. /sfc 目录是处理单文件.vue;
6. /shared 目录是提供全局用到的工具函数
复制代码
vue.js的组成是有core + 对应的‘平台’补充代码构成(独立构建和运行时构建只是platforms下web平台的两种选择)vue
双向绑定(响应式原理)所涉及到的技术node
1. Object.defineProperty
2. Observer
3. Watcher
4. Dep
5. Directive
复制代码
1. Object.definePropertygit
var obj = {};
var a;
Object.defineProperty(obj,'a',{
get: function(){
console.log('get val');
return a;
},
set: function(newVal){
console.log('set val:' + newVal);
a = newVal;
}
});
obj.a // get val;
obj.a = '111'; // set val:111
复制代码
双向数据绑定github
2. Observerweb
观察者模式是软件设计模式的一种。
在此种模式中,一个目标对象管理全部相依于它的观察者对象,而且在它自己的状态改变时主动发出通知。
这一般透过呼叫各观察者所提供的 方法来实现。此种模式一般被用来实时事件处理系统。
订阅者模式涉及三个对象:发布者、主题对象、订阅者,三个对象间的是一对多的关系, 每当主题对象状态发生改变时,其相关依赖对象都会获得通知,并被自动更新。
例如:
复制代码
vue里边怎么操做的呢?设计模式
3. watcher浏览器
4. Depbash
5. Directiveweex
function Vue(options) {
this.data = options.data;
var data = this.data;
observe(data, this);
var id = options.el;
var dom = new Compile(document.getElementById(id), this);
// 编译完成后,将dom返回到app中
document.getElementById(id).appendChild(dom);
}
复制代码
是经过上述方法实例化的一个对象;可是里边有两个未知生物 observe ? Compile?
<!--定义 observe方法 -->
function observe(obj, vm) {
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
})
}
function defineReactive(obj, key, val) {
var dep = new Dep();
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;
val = newVal;
console.log(val);
// 做为发布者发出通知
dep.notify();
}
})
}
复制代码
有oberver方法,就是初始观察者,去遍历它自身的属性,而后defineReactive(定义一些反应)(key + value + vm),而后给vm设置key的value(即set和get方法); ** Dep()???**
function Dep() {
this.subs = [];
}
Dep.prototype = {
// 添加订阅事件
addSub: function (sub) {
this.subs.push(sub);
},
// 添加发布通知事件
notify: function () {
this.subs.forEach(function (sub) {
sub.update();
})
}
}
复制代码
明白了,在观察者看来,我须要有人订阅个人消息,添加一个dep对象(主题对象);而后我给它添加订阅,而后我做为消息的发出者给订阅者发消息;
function Compile(node, vm) {
if (node) {
this.$frag = this.nodeToFragment(node, vm);
return this.$frag;
}
}
Compile.prototype = {
nodeToFragment: function (node, vm) {
var self = this;
var frag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
self.compileElement(child, vm);
frag.append(child); // 将全部子节点添加到fragment中
}
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') {
var name = attr[i].nodeValue; // 获取v-model绑定的属性名
node.addEventListener('input', function (e) {
// 给相应的data属性赋值,进而触发该属性的set方法
vm[name] = e.target.value;
});
// node.value = vm[name]; // 将data的值赋给该node
new Watcher(vm, node, name, 'value');
}
};
}
//节点类型为text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; // 获取匹配到的字符串
name = name.trim();
// node.nodeValue = vm[name]; // 将data的值赋给该node
new Watcher(vm, node, name, 'nodeValue');
}
}
},
}
复制代码
编译,就是建立节点,而后根据节点类型,而后去进行赋值操做, watcher???
function Watcher(vm, node, name, type) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.type = type;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function() {
this.get();
var batcher = new Batcher();
batcher.push(this);
// this.node[this.type] = this.value; // 订阅者执行相应操做
},
cb:function(){
this.node[this.type] = this.value; // 订阅者执行相应操做
},
// 获取data的属性值
get: function() {
this.value = this.vm[this.name]; //触发相应属性的get
}
}
复制代码
就是订阅者模式中的订阅者了,接收四个参数,vm(实例),node(节点),name(属性key), type(节点类型了);作三个操做,get(获取值),update(值),cb(保持原值); ** Batcher????**
/**
* 批处理构造函数
* @constructor
*/
function Batcher() {
this.reset();
}
/**
* 批处理重置
*/
Batcher.prototype.reset = function () {
this.has = {};
this.queue = [];
this.waiting = false;
};
/**
* 将事件添加到队列中
* @param job {Watcher} watcher事件
*/
Batcher.prototype.push = function (job) {
if (!this.has[job.name]) {
this.queue.push(job);
this.has[job.name] = job;
if (!this.waiting) {
this.waiting = true;
setTimeout(() => {
this.flush();
});
}
}
};
/**
* 执行并清空事件队列
*/
Batcher.prototype.flush = function () {
this.queue.forEach((job) => {
job.cb();
});
this.reset();
};
复制代码
这里边就是批量处理的函数了,事件队列的相关知识了,执行三个操做
如今咱们回过头去看整个事件,串起来就是vue的实现机制了....
源码看:github地址
未完待续……