目标:理解Vue实现双向数据绑定的主要原理。
最终需求:
- 1. view -> data 视图变化影响数据
- 2. data -> view 数据变化影响视图
- 3. 简单实现Vue的部分指令解析,
{{}}
、v-html
、v-model
、v-on
、v-text
-
结构
原理图.png
实现compile
class Compile{
constructor(el,vm){
// 保存vm
this.$vm = vm;
// 获取el
vm.$el = document.querySelector(el);
// 创建文档碎片
let fragment = document.createDocumentFragment();
// 将DOM节点移入内存
while(vm.$el.firstChild){
let child = vm.$el.firstChild;
fragment.appendChild(child);
}
// 编译fragment
this.replace(fragment);
// 将fragment放回el
vm.$el.appendChild(fragment);
}
replace(fragment){
// 遍历fragment
Array.from(fragment.childNodes).forEach((node)=>{
// 获取节点的文本内容
let text = node.textContent;
// 正则表达式用于匹配大括号
let reg = /\{\{(.*)\}\}/;
// 编译文本节点
if(node.nodeType === 3 && reg.test(text)){
// 解决name.age的情况
let arr = RegExp.$1.split('.');
let val = this.$vm;
arr.forEach((k)=>{
val = val[k];
});
new Watcher(this.$vm,RegExp.$1,function(newVal){
node.textContent = text.replace(/\{\{(.*)\}\}/,newVal);
});
node.textContent = text.replace(/\{\{(.*)\}\}/,val);
}
// 编译元素节点
if(node.nodeType === 1){
// 取出所有属性
let attrs = node.attributes;
// 转化为真数组,遍历所有属性
Array.from(attrs).forEach((attr)=>{
// 取出属性名和属性值
let name = attr.name;
let exp = attr.value;
// 判断是否以'v-'开头
if(name.indexOf('v-') == 0){
// 截取'v-'后面的属性名
let dir = name.slice(2);
// 处理绑定事件指令'v-on'
if(dir.indexOf('on') === 0){
let event = dir.split(':')[1];
let method = this.$vm.$option.methods && this.$vm.$option.methods[exp];
if(method && event){
node.addEventListener(event,method,false);
}
}
// 处理指令v-text
else if(dir.indexOf('text') === 0){
node.textContent = exp;
}
// 处理指令v-model
else if(dir.indexOf('model') === 0){
node.value = this.$vm[exp];
}
// 处理指令v-html
else if(dir.indexOf('html') === 0){
node.innerHTML = exp;
}
}
// 创建watcher,当data发生变化触发view发生变化
new Watcher(this.$vm,exp,(newVal)=>{
node.value = newVal;
})
// 绑定事件,当view发生变化触发data发生变化
node.addEventListener('input',(e)=>{
this.$vm[exp] = e.target.value;
})
})
}
// 递归编译所有子节点
if(node.childNodes){
this.replace(node);
}
})
}
}
实现watcher
class Dep{
constructor(){
// 订阅者队列
this.subs = [];
}
addSub(sub){// 添加watcher
this.subs.push(sub);
}
notify(){// 调用notify执行所有watcher的update方法
this.subs.forEach(sub=>sub.update());
}
}
class Watcher{
constructor(vm,exp,fn){
// 更新回调
this.fn = fn;
// 保存vm
this.vm = vm;
// 保存表达式
this.exp = exp;
// 触发get方法
Dep.target = this;
let arr = exp.split('.');
let val = vm;
arr.forEach((k)=>{
val = val[k];
})
Dep.target = null;
}
update(){ // 执行更新
let arr = this.exp.split('.');
let val = this.vm;
arr.forEach((k)=>{
val = val[k];
})
this.fn(val);
}
}
实现observer
function observe(data){
if(!data || typeof data !== 'object'){
return
}
return new Observer(data);
}
class Observer{
constructor(data){
let dep = new Dep();
for(let key in data){
let val = data[key];
// 监听data内每一个属性值
observe(val);
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get(){
// watcher内触发get方法,添加watcher到订阅者队列
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal){
// 值不变时,不需要修改
if(val === newVal){
return
}
val = newVal;
observe(newVal);
// 修改值时通知dep调用发布函数
dep.notify();
}
})
}
}
}
将data代理到vm上
class MVVM{
constructor(option = {}){
this.$option = option;
let data = this._data = this.$option.data;
// 初始化MVVM时,监视data
observe(data);
// 数据代理,将this._data代理到MVVM实例上
for(let key in data){
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return this._data[key];
},
set(newVal){
this._data[key] = newVal;
}
})
}
// 编译指令和{{}}
new Compile(option.el,this);
}
}
使用
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>myVue</title>
</head>
<body>
<div id="app">
{{name}}
<button v-on:click="hello" >点击</button>
<p v-text="ee"></p>
<p v-html="<span>hello</span>"></p>
<input type="text" v-model="name">
</div>
<script src="compile.js"></script>
<script src="observer.js"></script>
<script src="watcher.js"></script>
<script src="mvvm.js"></script>
<script>
var vm = new MVVM({
el:'#app',
data:{
name: 'tom' ,
age:21
},
methods:{
hello(){
console.log('hello');
}
}
})
</script>
</body>
</html>
网友评论