学习https://juejin.im/entry/583bd53ca22b9d006dce11d7讲解的vue双向绑定实现原理
html
<div id="app">
<input class="input" v-model="text" type="text" value="">
{{text}}
<p>{{text}}</p>
</div>
<script src="./vue双向数据绑定原理.js"></script>
js
// 定义vue类
function _vue(options) {
this.data = options.data;
let data = this.data;
observe(data, this);
let id = options.el;
let dom = nodeToFragment(document.getElementById(id), this);
document.getElementById(id).appendChild(dom);
}
// 处理子节点并放入documentFragment
function nodeToFragment(node, vm) {
let fragment = document.createDocumentFragment();
let child;
while(child = node.firstChild){
compile(child, vm);
fragment.appendChild(child);
}
return fragment;
}
// 节点分类处理,初始化节点内数据,绑定view => model
function compile(node, vm) {
let reg = /\{\{(.*)\}\}/;
// 元素节点
if(node.nodeType === 1) {
let attr = node.attributes;
for (let attrItem of attr) {
if (attrItem.nodeName === 'v-model') {
let name = attrItem.nodeValue;
node.addEventListener('keyup', (e)=>{
vm[name] = e.currentTarget.value;
})
node.value = vm[name];
node.removeAttribute('v-model');
}
}
for (let item of node.childNodes) {
compile(item, vm);
}
}
// 文本节点
if(node.nodeType === 3) {
if(reg.test(node.nodeValue)) {
let name = RegExp.$1;
name = name.trim();
// node.nodeValue = vm[name];
new Watcher(vm, node, name);
}
}
}
// 设置访问器属性
function defineReactive(obj, key, val) {
let dep = new Dep();
Object.defineProperty(obj, key, {
get: function (param) {
if (Dep.target) dep.addSub(Dep.target);
return val;
},
set: function (newVal) {
if (newVal === val) return;
val = newVal;
dep.notify();
}
})
if (val instanceof Object){
observe(val, obj)
}
}
function observe(obj, vm) {
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
})
}
// 订阅者列表
function Dep() {
this.subs = [];
}
Dep.prototype.notify = function () {
this.subs.forEach(function (subs) {
subs.update();
})
}
Dep.prototype.addSub = function (watcher) {
this.subs.push(watcher)
}
// 订阅者类
function Watcher(vm, node, name) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function (){
this.get();
this.node.nodeValue = this.value;
},
get: function (){
this.value = this.vm[this.name];
}
}
let vm = new _vue({
el: 'app',
data: {
text: 'hello world!'
}
})