Vue2.x 的双向绑定原理及实现
作者:九旬
来源:SegmentFault 思否社区
Vue 数据双向绑定原理
Vue 是利用的 Object.defineProperty()
方法进行的数据劫持,利用 set、get 来检测数据的读写。
https://jsrun.net/RMIKp/embedded/all/light
MVVM 框架主要包含两个方面,数据变化更新视图,视图变化更新数据。
视图变化更新数据,如果是像 input 这种标签,可以使用 oninput 事件..
数据变化更新视图可以使用 Object.definProperty()
的 set 方法可以检测数据变化,当数据改变就会触发这个函数,然后更新视图。
实现过程
我们知道了如何实现双向绑定了,首先要对数据进行劫持监听,所以我们需要设置一个 Observer 函数,用来监听所有属性的变化。
如果属性发生了变化,那就要告诉订阅者 watcher 看是否需要更新数据,如果订阅者有多个,则需要一个 Dep 来收集这些订阅者,然后在监听器 observer 和 watcher 之间进行统一管理。
还需要一个指令解析器 compile,对需要监听的节点和属性进行扫描和解析。
因此,流程大概是这样的:
实现一个监听器 Observer,用来劫持并监听所有属性,如果发生变动,则通知订阅者。 实现一个订阅者 Watcher,当接到属性变化的通知时,执行对应的函数,然后更新视图,使用 Dep 来收集这些 Watcher。 实现一个解析器 Compile,用于扫描和解析的节点的相关指令,并根据初始化模板以及初始化相应的订阅器。
显示一个 Observer
Object.defineProperty()
通过递归的方式对所有属性都添加 setter、getter 方法进行监听。var library = {
book1: {
name: "",
},
book2: "",
};
observe(library);
library.book1.name = "vue权威指南"; // 属性name已经被监听了,现在值为:“vue权威指南”
library.book2 = "没有此书籍"; // 属性book2已经被监听了,现在值为:“没有此书籍”
// 为数据添加检测
function defineReactive(data, key, val) {
observe(val); // 递归遍历所有子属性
let dep = new Dep(); // 新建一个dep
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
if (Dep.target) {
// 判断是否需要添加订阅者,仅第一次需要添加,之后就不用了,详细看Watcher函数
dep.addSub(Dep.target); // 添加一个订阅者
}
return val;
},
set: function(newVal) {
if (val == newVal) return; // 如果值未发生改变就return
val = newVal;
console.log(
"属性" + key + "已经被监听了,现在值为:“" + newVal.toString() + "”"
);
dep.notify(); // 如果数据发生变化,就通知所有的订阅者。
},
});
}
// 监听对象的所有属性
function observe(data) {
if (!data || typeof data !== "object") {
return; // 如果不是对象就return
}
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
}
// Dep 负责收集订阅者,当属性发生变化时,触发更新函数。
function Dep() {
this.subs = {};
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach((sub) => sub.update());
},
};
实现 Watcher
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 缓存自己,用于判断是否添加watcher。
var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数
Dep.target = null; // 释放自己
return value;
},
};
// MyVue
proxyKeys(key) {
var self = this;
Object.defineProperty(this, key, {
enumerable: false,
configurable: true,
get: function proxyGetter() {
return self.data[key];
},
set: function proxySetter(newVal) {
self.data[key] = newVal;
}
});
}
实现 Compile
解析模板指令,并替换模板数据,初始化视图。 将模板指定对应的节点绑定对应的更新函数,初始化相应的订阅器。
function nodeToFragment(el) {
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while (child) {
// 将Dom元素移入fragment中
fragment.appendChild(child);
child = el.firstChild;
}
return fragment;
}
function compileElement (el) {
var childNodes = el.childNodes;
var self = this;
[].slice.call(childNodes).forEach(function(node) {
var reg = /\{\{(.*)\}\}/; // 匹配{{xx}}
var text = node.textContent;
if (self.isTextNode(node) && reg.test(text)) { // 判断是否是符合这种形式{{}}的指令
self.compileText(node, reg.exec(text)[1]);
}
if (node.childNodes && node.childNodes.length) {
self.compileElement(node); // 继续递归遍历子节点
}
});
},
function compileText (node, exp) {
var self = this;
var initText = this.vm[exp];
updateText(node, initText); // 将初始化的数据初始化到视图中
new Watcher(this.vm, exp, function (value) { // 生成订阅器并绑定更新函数
self.updateText(node, value);
});
},
function updateText (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
}
function compile(node) {
var nodeAttrs = node.attributes;
var self = this;
Array.prototype.forEach.call(nodeAttrs, function(attr) {
var attrName = attr.name;
if (isDirective(attrName)) {
var exp = attr.value;
var dir = attrName.substring(2);
if (isEventDirective(dir)) {
// 事件指令
self.compileEvent(node, self.vm, exp, dir);
} else {
// v-model 指令
self.compileModel(node, self.vm, exp, dir);
}
node.removeAttribute(attrName); // 解析完毕,移除属性
}
});
}
// v-指令解析
function isDirective(attr) {
return attr.indexOf("v-") == 0;
}
// on: 指令解析
function isEventDirective(dir) {
return dir.indexOf("on:") === 0;
}
完整版 myVue
class MyVue {
constructor(options) {
var self = this;
this.data = options.data;
this.methods = options.methods;
Object.keys(this.data).forEach(function(key) {
self.proxyKeys(key);
});
observe(this.data);
new Compile(options.el, this);
options.mounted.call(this); // 所有事情处理好后执行mounted函数
}
proxyKeys(key) {
// 将this.data属性代理到this上
var self = this;
Object.defineProperty(this, key, {
enumerable: false,
configurable: true,
get: function getter() {
return self.data[key];
},
set: function setter(newVal) {
self.data[key] = newVal;
},
});
}
}
https://ustbhuangyi.github.io/vue-analysis/v2/reactive/
https://juejin.cn/book/6844733705089449991/section/6844733705227862023
https://www.cnblogs.com/canfoo/p/6891868.html
评论