- 首先来定义Vue函数,我们新建MVVM.js,如下所示:
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);
}
- 我们再来看Compile做了什么,以下是Compile.js
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(); // 创建一个dom片段
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绑定的属性名(text)
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
let uid = 0;
function Watcher(vm, node, name, type) {
//new Watcher(vm, node, name, 'nodeValue');
Dep.target = this;
this.name = name; //text
this.id = ++uid;
this.node = node; //当前的节点
this.vm = vm; //vm
this.type = type; //nodeValue
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function () {
this.get();
if(!batcher){
batcher = new Batcher();
}
batcher.push(this);
//this.node[this.type] = this.value; // 订阅者执行相应操作
},
cb: function () {
//最终实际虚拟dom处理的结果 只处理一次
console.log("dom update");
this.node[this.type] = this.value; // 订阅者执行相应操作
},
// 获取data的属性值
get: function () {
this.value = this.vm[this.name]; //触发相应属性的get
}
}
- Dep 发布订阅
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function (sub) {
this.subs.push(sub);
},
notify: function () {
this.subs.forEach(function (sub) {
sub.update();
})
}
}
- 观察者模式
function defineReactive(vm, key, val) {
var dep = new Dep();
Object.defineProperty(vm, key, {
get: function () {
if (Dep.target) {
// JS的浏览器单线程特性,保证这个全局变量在同一时间内,只会有同一个监听器使用
dep.addSub(Dep.target);
}
return val;
},
set: function (newVal) {
if (newVal === val) return;
val = newVal;
// 作为发布者发出通知
dep.notify();
}
})
}
function observe(obj, vm) {
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
})
}
- 批处理
function Batcher() {
this.reset();
}
/**
* 批处理重置
*/
Batcher.prototype.reset = function () {
this.has = {};
this.queue = [];
this.waiting = false;
};
/**
* 将事件添加到队列中
* @param job {Watcher} watcher事件
*/
Batcher.prototype.push = function (job) {
let id = job.id;
if (!this.has[id]) {
console.log(batcher);
this.queue.push(job);
//设置元素的ID
this.has[id] = true;
if (!this.waiting) {
this.waiting = true;
if ("Promise" in window) {
Promise.resolve().then( ()=> {
this.flush();
})
} else {
setTimeout(() => {
this.flush();
}, 0);
}
}
}
};
/**
* 执行并清空事件队列
*/
Batcher.prototype.flush = function () {
this.queue.forEach((job) => {
job.cb();
});
this.reset();
};
- vue模版如下:
<!DOCTYPE html>
<head></head>
<body>
<div id="app">
<input type="text" id="a" v-model="text">
{{text}}
</div>
<script>
window.batcher = "";
</script>
<script src="src/Batcher.js"></script>
<script src="src/Dep.js"></script>
<script src="src/Observe.js"></script>
<script src="src/Watcher.js"></script>
<script src="src/Compile.js"></script>
<script src="src/MVVM.js"></script>
<script>
var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
});
for(var i=0;i<100;i++){
vm["text"] = i;
}
</script>
</body>
</html>
https://ustbhuangyi.github.io/vue-analysis/
http://hcysun.me/vue-design/art/2vue-constructor.html
ie6,7,8用san.js
__defineGetter__
__defineSetter__
defineProperty不能监测数组,所以vue3用Proxy
网友评论