该文章简单模拟Vue响应式原理(MVVM),笔者还只是个大三的学生,文章有很多不到之处欢迎各位大佬指出错误,多多交流。
这是Vue官方文档的响应式原理图
官方文档的东西对于初学者来说都有点晦涩难懂,本文就将以一种简单的方式来聊聊Vue的响应式原理。
问题提出
Vuejs是数据驱动型,数据发生改变界面也会刷新改变。但这并不是理所当然,在其内部做了很多复杂的操作。
-
思路
-
首先要搞懂Vue内部是如何监听数据的改变?
通过Object.definePropety这个方法来监听数据的改变。
这个方法的第三个参数的 Setters 和 Getters是关键。详情
-
已经监听了数据的改变,Vue是如何知道要通知哪些元素界面发生刷新呢?
通过发布订阅者模式。
Observer会监听所有的data属性并且给他们创建一一对应的Dep对象.
Compile 会解析模板中指令(同时也会将界面初始化)。 一个指令就创建一个Watcher对象, 然后添加到相对应的Dep对象的订阅中绑定更新函数。如果一个属性的value发生改变Observer就会通知所对应的Dep对象调用notify()通知所有的订阅者 更新界面。
详情看下图
PS:画图技术渣渣,见谅。
-
具体实现
笔者只是简单模拟,还没有达到可以剖析源码的程度。
- 先用正则做下Mustache语法(双大括号)转化。
//render.js
var render = function(template, data) {
const reg = /\{\{(\w+)\}\}/; //不做贪婪匹配
if (reg.test(template)) { //退出条件 false
//是否需要编译
// vue源码模板编译用的正则方法
const key = reg.exec(template)[1]
// console.log(key)
template = template.replace(reg, data[key]);
return render(template, data) //递归渲染
}
// template.replace(/{{(.)+/)
return template
}
- 上代码(代码有挺详细的注释)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
<script src="./render.js"></script>
<script>
const obj = { //模拟数据
name: '张三',
age: '18',
sex: '男'
}
// 发布订阅者模式
class Dep {
constructor() {
this.subs = []
}
addSub(watch) { //添加订阅方法
this.subs.push(watch)
}
notify() { //通知订阅者更新
this.subs.forEach(item => {
item.update();
})
}
}
class Watch {
constructor(name) {
this.name = name;
}
update() {
console.log(this.name + 'update')
//更新视图
document.getElementById('app').innerHTML = render(template,obj)
}
}
Object.keys(obj).forEach(key => { //遍历obj对象
let dep = new Dep; //创建发布者对象
let value = obj[key];
Object.defineProperty(obj, key, {//监听数据
set: function(newValue) { //数据改变
console.log( '数据' + key + '改变了')
// 更新数据
value = newValue
dep.notify() //通知订阅者更新
},
get: function() { //获取数据
console.log('数据' + key + '加入了响应式系统')
let w = new Watch(value)
dep.addSub(w);
return value
}
})
})
obj.message = "没用的"
//待编译的模板
var template = '我是{{name}}, {{name}} 年龄 {{age}},性别 {{sex}} 。 {{message}} '
document.getElementById('app').innerHTML = render(template,obj);
</script>
</body>
</html>
- 效果图如下
obj.message = "没用的"这个数据没在初始化中,所有没有打印。即不是响应式属性。
Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性。详情看文档声明响应式属性。
最后
笔者是一个入门前端不久的小白,这篇文章挺浅显的,可能也有一些错误,希望大佬们多多给点建议。如果这篇文章对你有那么一点点小帮助的话,不妨点个赞吧。