Vue2 中,响应式实现的核心就是 ES5 的 Object.defineProperty(obj, prop, descriptor)。通过 Object.defineProperty() 劫持 data 和 props 各个属性的 getter 和 setter , getter 做依赖收集, setter 派发更新。整体来说是一个 数据劫持 + 发布-订阅者模式。
- vue 初始化阶段( beforeCreate 之后 create 之前),遍历 data/props,调用 Object.defineProperty 给每个属性加上 getter、setter。
- 每个组件、每个 computed 都会实例化一个 watcher (当然也包括每个自定义 watcher ),订阅渲染/计算所用到的所用 data/props/computed
- 一旦数据发生变化, setter 被调用,会通知渲染 watcher 重新计算、更新组件。
响应式原理简单实现,设计五个类:
- Vue:处理 options 传入的参数,把 data 中的成员转成 getter/setter
- Observer:递归处理 data 所有属性,劫持数据 get/set 添加依赖/更新视图
- Dep:添加 watcher ,数据变化通知所有 watcher
- Watcher:实例化时往 dep 中添加自己,数据变化 dep 通知 watcher 更新视图
- Compiler:解析指令/插值表达式,遇到模板依赖数据,添加 watcher
Vue
功能:
- 记录传入的选项,设置 data,el
- 把 data 中的属性注入到 Vue 实例,转换成 getter/setter
- 调用 observer 监听 data 中所有属性的变化
- 调用 compiler 解析指令 / 插值表达式
类属性方法:
- $options
- $el
- $data
- _proxyData()
class Vue {
constructor(options) {
this.$options = options || {};
this.$data = options.data || {};
this.$el =
typeof options.el === "string"
? document.querySelector(options.el)
: options.el;
this._proxyData(this.$data);
this.initMethods(options.methods || {});
new Observer(this.$data);
new Compiler(this);
}
_proxyData(data) {
Object.keys(data).forEach((key) => {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get() {
return data[key];
},
set(newValue) {
if (newValue === data[key]) return;
console.log("set -> newValue", newValue);
data[key] = newValue;
},
});
});
}
initMethods(methods) {
Object.keys(methods).forEach((key) => {
this[key] = methods[key];
});
}
}
Observer
功能:
- 数据劫持
- 负责把 data 中的成员转换成 getter/setter
- 负责把多层属性转换成 getter/setter
- 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
- 添加 Dep 和 Watcher 的依赖关系
- 数据变化发送通知
类属性方法:
- walk(data) 遍历 data
- defineReactive(data, key, value) 响应式处理
class Observer {
constructor(data) {
this.walk(data);
}
walk(data) {
if (typeof data !== "object" || data === null) {
return;
}
Object.keys(data).forEach((key) => {
this.defineReactive(data, key, data[key]);
});
}
defineReactive(obj, key, val) {
let self = this;
const dep = new Dep();
this.walk(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newValue) {
if (newValue === val) return;
val = newValue;
self.walk(newValue);
dep.notify();
},
});
}
}
Dep
功能:
- 收集依赖,添加观察者 (watcher)
- 通知所有观察者
类属性方法:
- subs 存放所有观察者
- addSub() 添加观察者
- notify() 事件发生时,调用所有观察者的 update 方法
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
if (sub && sub.update) {
this.subs.push(sub);
}
}
notify() {
this.subs.forEach((sub) => {
sub.update();
});
}
}
Watcher
功能:
- 当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图
- 自身实例化的时候往 dep 对象中添加自己
类属性方法:
- vm vue 实例
- key data 中的属性名称
- cb
- oldValue
- update()
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this;
this.oldValue = vm[key];
Dep.target = null;
}
update() {
const newValue = this.vm[this.key];
if (newValue === this.oldValue) {
return;
}
this.cb(newValue);
}
}
Compiler
功能:
- 负责编译模板,解析指令 / 插值表达式
- 负责页面的首次渲染
- 当数据变化后重新渲染视图
类属性方法:
- el
- vm
- compile(el) 编译原生的指令
- compileElement(node)
- compileText(node) 编译差值表达式
- isDirective(attrName) 判断是否是以 v - 开头的指令
- isTextNode(node)
- isElementNode(node)
class Compiler {
constructor(vm) {
this.vm = vm;
this.el = vm.$el;
this.compile(this.el);
}
compile(el) {
let childNodes = el.childNodes;
Array.from(childNodes).forEach((node) => {
if (this.isTextNode(node)) {
this.compileText(node);
} else if (this.isElementNode(node)) {
this.compileElement(node);
}
if (node.childNodes && node.childNodes.length) {
this.compile(node);
}
});
}
isTextNode(node) {
return node.nodeType === 3;
}
isElementNode(node) {
return node.nodeType === 1;
}
compileText(node) {
let reg = /\{\{(.+)\}\}/;
let value = node.textContent;
if (reg.test(value)) {
let key = RegExp.$1.trim();
console.log("compileText -> key", key);
node.textContent = value.replace(reg, this.vm[key]);
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue;
});
}
}
compileElement(node) {
Array.from(node.attributes).forEach((attr) => {
let attrName = attr.name;
if (this.isDirective(attrName)) {
attrName = attrName.substr(2);
if (attrName.includes("on:")) {
const tmp = attrName.split(":");
const name = tmp[1];
this.onHandler(node, attr.value, name);
} else {
this.update(node, attr.value, attrName);
}
}
});
}
isDirective(attrName) {
return attrName.startsWith("v-");
}
update(node, key, attrName) {
let fn = this[attrName + "Updater"];
fn && fn.call(this, node, this.vm[key], key);
}
textUpdater(node, value, key) {
node.textContent = value;
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue;
});
}
modelUpdater(node, value, key) {
node.value = value;
new Watcher(this.vm, key, (newValue) => {
node.value = newValue;
});
node.addEventListener("input", () => {
this.vm[key] = node.value;
});
}
htmlUpdater(node, value, key) {
node.innerHTML = value;
new Watcher(this.vm, key, (newValue) => {
node.innerHTML = newValue;
});
}
onHandler(node, value, name) {
let modifier = "";
if (name.includes(".")) {
const tmp = name.split(".");
name = tmp[0];
modifier = tmp[1].trim();
}
// 动态时间处理:v-on:[event]="doThis"
if (name.startsWith("[")) {
name = name.slice(1, -1);
name = this.vm[name];
}
let third_params = false;
if (modifier === "capture") {
third_params = true;
} else if (modifier === "passive") {
third_params = { passive: true };
}
const cb = (e) => {
if (modifier === "stop") {
e.stopPropagation();
}
if (modifier === "prevent") {
e.preventDefault();
}
let methodName = value;
let args = [e];
// 处理内联语句 传递额外参数
if (value.endsWith(")")) {
const tmp = value.split("(");
methodName = tmp[0];
args = tmp[1]
.slice(0, -1)
.split(",")
.map((item) => {
item = item.trim();
console.log("onHandler -> item", item, typeof item);
if (item === "$event") {
return e;
}
if (item.startsWith('"') || item.startsWith("'")) {
console.log("onHandler -> item", item);
return item.slice(1, -1);
}
return this.vm[item];
});
}
this.vm[methodName](...args);
if (modifier === "once") {
node.removeEventListener(name, cb, third_params);
}
};
node.addEventListener(name, cb, third_params);
}
}
码云地址
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!