MVVM
m:model 数据层
v:view 视图层
vm: 数据双向绑定
封装mvvm的思路
1.实现一个Observer,对数据进行劫持,监听数据的属性变更,并在变动时进行notify
Object.defineProperty(vm,key,{
set(){},
get(){}
})
2.实现一个compile,对指令进行解析,初始化视图,并且订阅数据的变更,绑定好更新函数:
- 解析指令,将指令模板中的变量替换成数据,对视图进行初始化操作
- 订阅数据的变化,绑定好更新函数
- 接收到数据变化,通知视图进行view update
3.实现一个watcher,将其作为以上两者的一个中介点,一方面接收Observer通过Dep传递过来的数据变化,一方面通知compile进行view update:
- 通过Dep接收数据变动的通知,实例化的时候将自己添加到dep中
- 数据变更时,接收dep的notify,调用自身update方法,触发compile中的绑定的更新函数,进而更新视图
代码实现:
//html片段
<body>
<div id="app">
<input type="text" v-model='user.name'>
nihao
{{user.age}}
{{info}}
<h2>{{user.name}}</h2>
<h3>{{user.name}}</h3>
<li>{{user.name}}</li>
<p>{{user.name}}</p>
</div>
</body>
<script src="./mvvm.js"></script>
<script>
let vm = new Vue({
el:"#app",
data:{
user:{
name:"你好",
age:11
},
info:"aaaaaaaa"
}
})
//js片段
class Vue{
constructor(options){
this.$el = options.el;
this.$data = options.data;
if(this.$el){
//劫持数据
new Observer(this.$data);
console.log(this.$data)
//编译模板
new Complie(this.$el,this)
}
}
}
//
class Dep{
constructor(){
this.watchers = [];
}
add(watcher){
this.watchers.push(watcher)
}
notify(){
this.watchers.forEach(item=>{
item.updater()
})
}
}
//观察者
//vm.$watcher(vm,'user.name',(newValue)=>{})
class Watcher{
constructor(vm,expr,callback){
this.vm = vm;
this.expr = expr;
this.callback = callback;
//获取上一次的值
this.oldValue = this.get();
}
get(){
Dep.target = this;
let value = complieUtil.getValue(this.expr,this.vm);
Dep.target =null;
return value;
}
//跟新数据
updater(){
let newValue = complieUtil.getValue(this.expr,this.vm);
if(newValue != this.oldValue){
this.callback(newValue)
}
}
}
//劫持数据
class Observer{
constructor(data){
this.observer(data)
}
//劫持
observer(data){
if(data && typeof data == 'object'){
for (const key in data) {
this.observerData(data,key,data[key])
}
}
}
//添加set get 函数
observerData(data,key,value){
this.observer(value)
let dep = new Dep()
Object.defineProperty(data,key,{
set(newValue){
console.log('设置值')
if(value != newValue){
value = newValue
dep.notify();
}
},
get(){
Dep.target && dep.add(Dep.target)
return value;
}
})
}
}
//编译模板
class Complie{
constructor(el,vm){
this.vm = vm;
//判断el 是元素节点
this.el = this.isElementNode(el) ? el :document.querySelector(el);
//把app下所有的元素保存在内存文档片段
let fragment = this.elementToFragment(this.el);
// console.log(fragment)
//编译内存片段
this.complie(fragment);
//把编译好的内存片段从放在浏览器中
this.el.appendChild(fragment)
// console.log(fragment)
}
//编译内存文档的方法
complie(node){
//获取内存片段下所有的子节点
let childNodes = node.childNodes;
[...childNodes].forEach(child=>{
//判断是文本还是元素
if(this.isElementNode(child)){
//元素节点
this.complieElement(child);
this.complie(child);
}else{
//文本节点
this.complieText(child);
}
})
}
//complieElement编译 -v
complieElement(node){
let attrs = node.attributes;
[...attrs].forEach(attr=>{
//判断是 v-开头的指令
let {name,value:expr} = attr
if(this.isDirective(name)){
let [,directive] = name.split('-');
complieUtil[directive](node,expr,this.vm)
}
})
}
//complieText 编译 {{}}
complieText(node){
let textContent = node.textContent;
if(/\{\{(.+?)\}\}/.test(textContent)){
//编译{{}} 语法
complieUtil.contentTxet(node,textContent,this.vm)
}
}
//判断是否是 -v开头的属性名
isDirective(attr){
console.log(attr)
console.log(attr.startsWith('v-'))
return attr.startsWith('v-');
}
//elementToFragment保存在内存文档片段函数
elementToFragment(el){
//创建一个内存片段
let fragment = document.createDocumentFragment();
let firstChild;
while (firstChild = el.firstChild) {
fragment.appendChild(firstChild);
}
return fragment;
}
//isElementNode
isElementNode(el){//判断是否是元素节点
return el.nodeType == 1;
}
}
//工具类
let complieUtil={
//获取值得方法
getValue(expr,vm){
return expr.split('.').reduce((prev,cur)=>{
return prev[cur];
},vm.$data)
},
model(node,expr,vm){
let value = this.getValue(expr,vm)
let fn = this.updater.complieModel;
//生成观察者
new Watcher(vm,expr,(newValue)=>{
fn(node,newValue)
})
fn(node,value)
},
text(){
},
html(){
},
//contentTxet//编译{{}}语法的方法
contentTxet(node,text,vm){
let value = text.replace(/\{\{(.+?)\}\}/g,(...arg)=>{
//生成观察者
new Watcher(vm,arg[1],(newValue)=>{
fn(node,newValue)
})
return this.getValue(arg[1],vm)
})
let fn = this.updater.complieText;
fn(node,value)
},
//更新数据
updater:{
//v-model
complieModel(node,value){
node.value = value
},
//{{}}
complieText(node,value){
node.textContent = value;
}
}
}