vue是采用数据劫持配合发布者-订阅者模式的方式,通过Object.definerProperty()
来劫持各个属性的setter和getter,在数据变动时,发布消息给依赖收集器,去通知观察者,做出对应的回调函数,去更新视图。
代码实现如下:
html代码部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>{{person.name}} -- {{person.age}}</h2>
<h3>{{person.fav}}</h3>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3>{{msg}}</h3>
<div v-text="msg"></div>
<div v-text="person.fav"></div>
<div v-html="htmlStr"></div>
<input type="text" v-model="msg">
<button v-on:click="gettest">on</button>
<button @click="gettest">@</button>
</div>
<script src="./Observer.js"></script>
<script src="./MVue.js"></script>
<script>
let vm = new MVue({
el: '#app',
data:{
person:{
name:"李华",
age: 16,
fav: '姑娘'
},
msg: '学习MVVM实现原理',
htmlStr: '<h1>htmlStr</h1>'
},
methods: {
gettest(){
this.person.name = '小李'
console.log(1,this)
}
},
})
</script>
</body>
</html>
MVue.js文件代码部分
const compileUtil = {
getVal(expr,vm){
// console.log(expr,vm)
return expr.split('.').reduce((data,currentVal)=>{
// console.log(data[currentVal])
return data[currentVal]
},vm.$data);
},
setVal(expr,vm,inputVal){
return expr.split('.').reduce((data,currentVal)=>{
// console.log(data[currentVal])
data[currentVal] = inputVal
},vm.$data);
},
getContentVal(expr, vm){
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
console.log(args)
return this.getVal(args[1], vm);
})
},
text(node, expr, vm){//expr:msg 学习MVVM原理
let value;
if(expr.indexOf('{{') !== -1){
value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
// 绑定观察者,将来数据发生变化 触发这里的回调 进行更新
new Watcher(vm, args[1], () => {
this.updater.textUpdater(node, this.getContentVal(expr,vm))
})
// console.log(args[1],vm)
return this.getVal(args[1],vm)
})
}else{
// const value = vm.$data[expr];
value = this.getVal(expr,vm);
}
this.updater.textUpdater(node,value)
},
html(node, expr, vm){
// const value = vm.$data[expr];
const value = this.getVal(expr,vm);
new Watcher( vm, expr, (newVal)=>{
this.updater.htmlUpdater(node, newVal)
})
this.updater.htmlUpdater(node,value)
},
model(node, expr, vm){
const value = this.getVal(expr,vm);
// 绑定更新函数 数据 => 视图
new Watcher( vm, expr, (newVal)=>{
this.updater.modelUpdater(node, newVal)
})
// 视图 => 数据 => 视图
node.addEventListener('input',(e)=>{
// 设置值
this.setVal(expr,vm,e.target.value)
})
this.updater.modelUpdater(node,value)
},
on(node, expr, vm, eventName){
let fn = vm.$options.methods && vm.$options.methods[expr]
node.addEventListener(eventName,fn.bind(vm),false)
},
// 更新的函数
updater:{
// text
textUpdater(node,value){
// console.log(value)
node.textContent = value
},
// html
htmlUpdater(node,value){
node.innerHTML = value
},
// model
modelUpdater(node,value){
node.value = value
}
}
}
class Compile{
constructor(el,vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
// 1.获取文档碎片对象 放入内存中会减少页面的回流和重绘
const fragment = this.node2Fragment(this.el);
// console.log(fragment)
// 2.编译模板
this.compile(fragment)
// 3.追加子元素到根元素
this.el.appendChild(fragment)
}
compile(fragment){
// 1.获取子节点
const childNodes = fragment.childNodes;
[...childNodes].forEach(child=>{
// console.log(child)
if(this.isElementNode(child)){
// 是元素节点
// 编译元素节点
// console.log('元素节点',child)
this.compileElement(child)
}else{
// 文本节点
// 编译文本节点
// console.log('文本节点',child)
this.compileText(child)
}
if(child.childNodes && child.childNodes.length){
this.compile(child)
}
})
}
compileElement(node){
// console.log(node)
const attributes = node.attributes;
// console.log(attributes)
[...attributes].forEach(attr=>{
// console.log(attr)
const { name,value } = attr;
// console.log(name);
if(this.isDirective(name)){//是一个指令 v-text v=html v-model v-on:click
const [,directive] = name.split('-'); //text html model on:click
const [dirName,eventName] = directive.split(':'); //text html model on
// console.log(dirName,node,value,this.vm,eventName)
// 更新数据 数据驱动视图
compileUtil[dirName](node,value,this.vm,eventName)
// 删除有指令的标签上的属性
node.removeAttribute('v-' + directive);
}else if(this.isEVentName(name)){ //@click='gettest'
let [,eventName] = name.split('@');
compileUtil['on'](node,value,this.vm,eventName)
}
})
}
compileText(node){
const content = node.textContent;
if(/\{\{(.+?)\}\}/.test(content)){
compileUtil['text'](node,content,this.vm)
}
}
isEVentName(attrName){
return attrName.startsWith('@');
}
isDirective(attrName){
return attrName.startsWith('v-');
}
node2Fragment(el){
// 创建文档碎片
const f = document.createDocumentFragment();
let firstChild;
while(firstChild = el.firstChild){
f.appendChild(firstChild)
}
return f;
}
isElementNode(node){
return node.nodeType === 1
}
}
class MVue {
constructor(options) {
this.$el = options.el
this.$data = options.data,
this.$options = options;
if(this.$el){
// 1.实现一个数据的观察者
new Observer(this.$data)
// 2.实现一个指令的解析器
new Compile(this.$el,this)
// 实现一个proxy
this.proxyData(this.$data)
}
}
proxyData(data){
for(const key in data){
Object.defineProperty(this,key,{
get(){
return data[key]
},
set(newVal){
data[key] = newVal
}
})
}
}
}
Observer.js文件代码
class Watcher{
constructor(vm,expr,cb){
this.vm = vm;
this.expr = expr;
this.cb = cb;
// 先把旧值保存起来
this.oldVal = this.getOldVal()
}
getOldVal(){
Dep.target = this;
const oldVal = compileUtil.getVal(this.expr,this.vm);
Dep.target = null
return oldVal;
}
update(){
const newVal = compileUtil.getVal(this.expr,this.vm);
if(newVal !== this.oldVal){
this.cb(newVal)
}
}
}
class Dep{
constructor(){
this.subs = [];
}
// 收集观察者
addSub(watcher){
this.subs.push(watcher)
}
// 通知观察者更新
notify(){
console.log("观察者",this.subs)
this.subs.forEach(w =>{
w.update()
})
}
}
class Observer{
constructor(data){
this.observe(data);
}
observe(data){
if(data && typeof data === 'object'){
// console.log(data)
Object.keys(data).forEach(key => {
this.defineReactive(data,key,data[key]);
});
}
}
defineReactive(obj,key,value){
// 递归遍历
this.observe(value)
const dep = new Dep()
// 劫持并监听所有的属性
Object.defineProperty(obj,key,{
enumerable: true,
configurable: false,
get(){
// 初始化
// 订阅数据变化时,往dep中添加观察者
Dep.target && dep.addSub(Dep.target)
return value;
},
set: (newVal) => {
this.observe(newVal);
if(newVal !== value){
value = newVal;
}
// 告诉Dep通知变化
dep.notify()
}
})
}
}