Vue双向绑定是面题的难点,之前看了很多视频都没有理解Vue双向绑定发布订阅的问题,终于在b站黑马视频找到讲的比较好的视频了(https://www.bilibili.com/video/BV1Dr4y1c7xS?from=search&seid=1122885673916184117)
源码 感兴趣的小伙伴可以去看视频,讲的很清楚了
代码语言:javascript
复制class Vue {
constructor(options) {
this.$data = options.data;
// 调用数据劫持的方法
Observe(this.$data);
// 属性代理
Object.keys(this.$data).forEach((key) => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return this.$data[key];
},
set(newValue) {
this.$data[key] = newValue;
},
});
});
Compile(options.el, this);
}
}
// 定义一个数据劫持方法
function Observe(obj) {
// 获取obj的每个属性
// 递归的终止条件
if (!obj || typeof obj !== "object") {
return;
}
const dep =new Dep()
Object.keys(obj).forEach((key) => {
// 需要为当前key所对应的属性,添加对应的属性,添加getter 和 setter
let value = obj[key];
Observe(value);
Object.defineProperty(obj, key, {
get() {
// Dep.target 只要执行了下面这一行,那么刚才的Watcher实例,
// 就会放到dep.sub 数组中了
Dep.target && dep.addSub(Dep.target)
return value;
},
set(newValue) {
value = newValue;
Observe(value);
// 通知执行
dep.notify();
},
});
});
}
// 对html 结构进行编译
function Compile(element, vm) {
vm.$el = document.querySelector(element);
// 创建文档碎片,提供dom操作性能
let fragment = document.createDocumentFragment();
while ((childNode = vm.$el.firstChild)) {
fragment.appendChild(childNode);
}
// 进行模版编译
replace(fragment, vm);
vm.$el.appendChild(fragment);
}
// 负责对dom 模版进行编译的方法
function replace(node, vm) {
// 定义匹配插值表达式
const regMustache = /{{s*(S )s*}}/;
if (node.nodeType === 3) {
// 注意:文本子节点,也是一个dom对象,如果获取文本子几点
const text = node.textContent;
const execResult = regMustache.exec(text);
if (execResult) {
const value = execResult[1].split(".").reduce((newObj, k) => {
return newObj[k];
}, vm);
node.textContent = text.replace(regMustache, value);
//在这个时候,创建Watcher类的实例
console.log(execResult[1])
new Watcher(vm,execResult[1],(newValue)=>{
node.textContent = text.replace(regMustache, newValue);
})
}
return;
}
// 证明不是文本节点,需要进行正则的替换
node.childNodes.forEach((node) => replace(node, vm));
}
// 依赖收集的类/收集watcher 订阅者的类
class Dep {
constructor() {
this.subs = [];
}
addSub(watcher) {
this.subs.push(watcher);
}
notify() {
this.subs.forEach((watcher) => watcher.update());
}
}
// 订阅者的类
class Watcher {
// cd 回调函数中,记录着当前 Wathcer 如何更新自己的文本内容
// 但是,知道如何更新自己还不行,还必须拿到最新数据
// 因此 还需要在new Watcher 期间,把vm也传递进来,因为vm中保存着最新的数据
// 除此之外,还需要知道,在vm身上从多的数据中,那个数据,才是当前自己所需要的数据
// 因此,必须在new Watcher 期间,指定 Watcher 对应的数据的名字
constructor(vm, key, cd) {
this.vm = vm;
this.key = key;
this.cd = cd;
// 把创建的watcher 实例存在Dep实例中
Dep.target =this
key.split('.').reduce((newObj,k)=>newObj[k],vm)
Dep.target=null
}
// watcher 的实例,需要有update函数,更新dom
update() {
const value = this.key.split('.').reduce((newObj,k)=>newObj[k],vm)
this.cd(value)
}
}