function Vue(options = {}) {
this.$options = options;
this._data = this.$options.data;
// /观测数据
new Observer(this._data);
this._data = this.$options.data;
console.log('data => :', this._data);
for (const key in this._data) {
Object.defineProperty(this, key, {
enumerable: true,
get() {
return this._data[key]
},
set(newVal) {
this._data[key] = newVal
}
})
}
new Compile(this.$options.el, this)
}
// 模板解析
function Compile(el, vm) {
vm.$el = document.querySelector(el)
const fragment = document.createDocumentFragment()
let child = null;
// 通过while循环遍历el下所有的节点 移入到 空白的fragment节点
// 然后 el节点就变成空的了
while (child = vm.$el.firstChild) {
console.log(child, '移动的节点有');
fragment.append(child)
}
/*
*捕获DOM中所有 {{ }}中的字符串
*对_data中的 键名 key进行匹配
*匹配成功的就进行替换
*/
replace(fragment);
// .然后把创建的节点插进根节点 就OK 了 模板编译完成
/*
虽然此时模板编译完成了;我们想要的界面都渲染出来了
但是页面却不是响应式的
*/
vm.$el.append(fragment)
//
function replace(fragment) {
// 正则匹配形如{{str}}的字符串
const pattern = /\{\{(.*)\}\}/
//遍历fragment所有的子节点
Array.from(fragment.childNodes).forEach(node => {
let text = node.textContent; //获取子节点的文本内容
//当子节点的nodeType为 3 (文本节点) && 正则匹配成功
if (node.nodeType === 3 && pattern.test(text)) {
// 正则匹配到的内容用对应的vm[key]替换
console.log(RegExp.$1, '匹配到的字符串为');
const key = RegExp.$1.trim()
//实例化一个订阅者 用来实现 {{ }} 的响应式 callback用来更新视图
new Watcher(vm, key, (newVal) => {
node.textContent = text.replace(pattern, newVal)
})
// 替换文本节点的内容为) _data中对应的字符串
node.textContent = text.replace(pattern, vm[key])
}
// 如果当前的子节点不是文本节点就进行递归处理 遍历所以的后代节点进行处理
if (node.childNodes && node.childNodes.length) {
replace(node)
}
})
}
}
/**
* 实现响应式 需要三个东西 Observer(观察者) Watcher(订阅者) Dep(充当eventHub的角色)
* Observer负责观察数据,当数据发生变化时,
* 通过Dep通知Watcher,Watcher收到数据变动的通知时,
* 能够拿到数据的最新值并执行相应回调更新视图。
*/
/**
* Dep具有一个属性连个方法 subs=[] addSub() 和 notify()
* subs 可当成一个存储器 存储着 sub (Wathcer的实例)
* addSub方法将sub添加到subs数组,
* notify方法遍历 subs 数组并调用每个子元素sub的update方法(此方法将更新视图)
*/
function Dep() {
this.subs = []
}
Dep.prototype.addSub = function (sub) {
this.subs.push(sub)
}
Dep.prototype.notify = function () {
this.subs.forEach(sub => sub.update())
}
/**
* Watcher的实现
*
* Compile函数中当子节点为文本节点且正则匹配成功时,除了替换文本内容外,
* 还必须调用Watcher,传递三个参数,vm, key和fn,
* vm为当前Vue实例,key为此节点中正则匹配到的key,fn为更新此节点文本内容的回调(需传参)。
* Watcher函数的最后,将实例this赋值给Dep.target,
* 这个实例将被添加到 Dep实例的subs数组中,这一步是在Observer中进行的。
*/
function Watcher(vm, key, fn) {
this.vm = vm
this.key = key
this.fn = fn
Dep.target = this
}
// 更新视图
Watcher.prototype.update = function () {
this.fn(this.vm[this.key])
}
// 数据劫持
function Observer(data) {
const dep = new Dep()
// 循环 _data 中所有的响应式变量
for (const key in data) {
let val = data[key]
/**
* Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
*/
Object.defineProperty(data, key, {
enumerable: true,
get() {
// 把 Watcher的实例添加进 Dep 的 subs 中
Dep.target && dep.addSub(Dep.target)
Dep.target = null
// 读取数据的值
return val
},
set(newVal) {
// 当属性值呗修改的时候就调用这个函数
// 执行 当前属性值对应的 Wathcer 实例的 update 方法 => 执行 new Watcher 的时候的 callback() 传入最新的当前值
if (newVal === val) {
return
}
val = newVal
dep.notify()
}
})
}
}
极简的MVVM实现
最新推荐文章于 2023-02-23 11:00:00 发布