流程图
![](https://img.haomeiwen.com/i25820166/4bb838814fb9f5b9.PNG)
捕获.PNG
- Observer实现对所有数据劫持
- Dep 负责收集每个key 的所有Watcher
- Watcher 作为观察者,数据发生变化,执行更新
- Compile 解析模板
第一步实现Observer ,劫持所有数据
//vue.js
// 劫持obj属性key的get和set,响应试的具体实现
function defineReactive(obj,key,val){
observe(val) // 将内部数据继续响应式处理
const dep = new Dep() // 负责收集当前key的所有watcher
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.addWatcher(Dep.target) // 收集target上的watcher
return val;
},
set(newVal) {
if (newVal !== val) {
// 如果传入newVal依然是obj,需要做响应式
observe(newVal);
val = newVal;
dep.notify() // 一旦值发生变化,通知所有watcher 更新
}
}
})
}
// 将对应的值构造成一个响应式数据
class Observe{
constructor(value){
if(typeof value === 'object'){
this.walk(value)
}
}
walk(obj){
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key])
})
}
}
// 递归将数据变成响应式
function observe(obj){
if (typeof obj !== 'object' || obj === null) {
return
}
new Observe(obj)
}
第二步实现Dep ,收集对象中指定key的watcher
// vue.js
class Dep{
constructor(){
this.watchers = [];
}
addWatcher(watcher){
this.watchers.push(watcher)
}
notify(){
this.watchers.forEach(watcher=>watcher.update());
}
}
第三步实现Watcher 保存对象上指定key 对象的更新函数
// vue.js
class Watcher{
// 作为观察者,传入需要观察对象的那个属性,并且属性发生变化时需要执行的更新函数
constructor(vm,key,updateFn){
this.vm = vm
this.key = key
this.updateFn = updateFn
// 创建watcher 时,触发get,让Dep收集当前Watcher
Dep.target = this
this.vm[this.key]
Dep.target = null
}
// 值发生变化,更新函数
update(){
this.updateFn .call(this.vm,this.vm[this.key]);
}
}
第四步Compile 实现编译器解析vue模板
Compile.js
class Compile{
constructor(el,vm){
this.$vm = vm
this.$el = document.querySelector(el)
if(this.$el){
// 执行编译
this.comile(el)
}
}
// 编译
compile(el){
// 遍历el树
const childNodes = el.childeNodes;
Array.from(childNodes).forEach(node=>{
if(this.isElement(node)){
// 是元素则编译元素
this.compileElement(node)
}else if(this.isInter(node)){
// 是文本则编译文本
this.compileText(node)
}
// 存在子节点,继续递归编译
if(node.childeNodes&&node.childeNodes.length > 0){
this.compile(node)
}
})
}
// 是元素
isElement(node){
return node.nodeType === 1
}
// 是文本
isInter(node){
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
// 是指令
isDirective(attr){
return attr.indexOf('v-') === 0
}
// 是事件
isEvent(dir) {
return dir.indexOf('@') === 0
}
// 编译元素,需要解析指令和事件
compileElement(node){
const nodeAttrs = node.attributes // 拿到节点上的所有属性
Array.from(nodeAttrs).forEach(attr=>{
// 拿到属性名和值 v-bind="aaa"
const attrName = attr.name // v-bind
const exp = attr.value // aaa
if(this.isDirective(attrName )){
// 是指令
const dir = attrName.substring(2); // bind
// 执行指令
this[dir] && this[dir](node, exp)
}
// 事件处理 @click
if (this.isEvent(attrName)) {
const dir = attrName.substring(1) // click
// 事件监听
this.eventHandler(node, exp, dir)
}
})
}
// 编译文本,需要解析数据
compileText(node){
// 更新到dom ,并且创建一个wathcer
this.update(node,RegExp.$1,'text')
}
// 更新数据,并创建watcher
update(node,exp,dir){
// 拿到指定的更新函数
const fn = this[dir+"Updater"]
// 执行一次
fn && fn(node,this.$vm[exp])
// 新建一个watcher ,后续数据变化自动响应式处理
new Watcher(this.$vm,exp,function(val){
fn && fn(node,val)
})
}
// v-text 指令
text(node, exp) {
this.update(node, exp, "text")
}
// v-html 指令
html(node, exp) {
this.update(node, exp, "html")
}
// v-model 指令
model(node, exp) {
// update 完成赋值与更新
this.update(node, exp, "model")
// 事件监听
node.addEventListener('input', e => {
this.$vm[exp] = e.target.value
})
}
// 文本更新
textUpdater(node,value){
node.textContent = value;
}
// model 更新
modelUpdater(node, value) {
node.value = value
}
// html更新
htmlUpdater(node, value) {
node.innerHTML = value
}
// 事件处理
eventHandler(node, exp, dir) {
const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp]
node.addEventListener(dir, fn.bind(this.$vm))
}
}
第五步 创建Vue类
// vue.js
// 将vm 上的属性的值代理到vm上,方便直接访问
proxy(vm,attr){
Object.keys(vm[attr]).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm[attr][key]
},
set(newVal) {
vm[attr][key] = newVal;
}
})
})
}
class Vue {
constructor(options){
this.$options = options
this.$data = options.data;
// 将数据响应化处理
observe(this.$data)
// 代理
proxy(this, '$data')
// 创建编译器
new Compile(options.el, this)
}
}
第六步使用自己的vue
//index.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">
<p>{{counter}}</p>
<p v-text="counter"></p>
<p v-html="desc"></p>
<input type="text" v-model="counter">
</div>
<script src="./compile.js"></script>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
counter: 1,
desc: "<span style='color:red'>哈哈</spam>"
},
methods: {
onClick() {
this.counter += 10
}
},
})
setInterval(() => {
app.counter++
}, 1000)
</script>
</body>
</html>
源码
网友评论