skip to content
天真笔录

vue数据响应式原理

/ 4 min read

new Vue

class Vue {
	constructor(options) {
		this.$options = options;
		this.$data = options.data;
		observe(options.data); // 为data做变化侦测
		proxy(this); // 挂载data到vue实例 this.counter === this.$data.counter

		new Compiler(options.el, this);
	}
}

new Vue({
	el: "#app",
	data: {
		counter: 1,
		desc: "<span>天真</span>",
	},
});

变化侦测

observe

function observe(obj) {
	if (typeof obj !== "object" || obj === null) return obj;

	new Observe(obj);
}

Observe

class Observe {
	constructor(obj) {
		// vue中数组与obj是单独处理
		if (Array.isArray(obj)) {
			// 数组处理
		} else {
			this.walk(obj);
		}
	}

	walk() {
		Object.keys(obj).forEach((key) => {
			defineReactive(obj, key, obj[key]);
		});
	}
}

defineReactive

// 针对Object类型
function defineReactive(obj, key, val) {
	observe(obj); // 解决对象嵌套

	Object.defineProperty(obj, key, {
		get() {
			console.log("get", val);
			return val;
		},
		set(newVal) {
			if (newVal === val) return val;
			console.log("set:", newVal);
			val = newVal; // 闭包
		},
	});
}

/* 测试 */
const obj = {};
defineReactive(obj, "name", "tianzhen");
obj.name; // 触发get
obj.name = "zhentian"; // 触发set

proxy

function proxy(vm) {
	Object.keys(vm.$data).forEach((key) => {
		Object.defineProperty(vm, key, {
			get() {
				return vm.$data[key];
			},
			set(v) {
				vm.$data[key] = v;
			},
		});
	});
}

模版编译

<div id="app">
	<p>{{counter}}</p>
	<button @click="add"></button>
	<p v-html="desc"></p>
</div>

compile

class Compiler {
	constructor(el, vm) {
		this.$el = document.querySelector(el);
		this.$vm = vm;
		this.$methods = vm.$methods;

		this.$el && this.compile(this.$el);
	}

	compile(node) {
		const childNodes = node.childNodes;
		Array.from(childNodes).forEach((n) => {
			if (this.isElement(n)) {
				// 编译元素
				this.compileElement(n);
				// 元素子节点递归
				if (n.childNodes.length > 0) {
					this.compile(n);
				}
			} else if (this.isInterpolation(n)) {
				// 编译文本插值
				this.compileText(n);
			}
		});
	}

	isElement(node) {
		return node.nodeType === 1;
	}

	isInterpolation(node) {
		return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
	}

	// 编译文本插值 {{xxoo}}
	compileText(node) {
		// node.textContent = this.$vm[RegExp.$1]
		this.update(node, RegExp.$1, "text");
	}

	compileElement(node) {
		const attrs = node.attributes;
		Array.from(attrs).forEach((attr) => {
			const attrName = attr.name;
			const exp = attr.value;
			if (this.isDirective(attrName)) {
				let dir = attrName.slice(2);
				this[dir] && this[dir](node, exp);
			}

			if (this.isEvent(attrName)) {
				const event = attrName.slice(1);
				this.doEvent(node, event, exp);
			}
		});
	}

	isEvent(name) {
		return name.startsWith("@");
	}

	isDirective(name) {
		return name.startsWith("k-");
	}

	doEvent(node, event, exp) {
		node.addEventListener(event, () => {
			this.$methods[exp].call(this.$vm);
		});
	}

	text(node, exp) {
		// node.textContent = this.$vm[exp]
		this.update(node, exp, "text");
	}

	html(node, exp) {
		// node.innerHTML = this.$vm[exp]
		this.update(node, exp, "html");
	}

	/*
    动态值编译统一处理为update函数,方便进行依赖收集
    依赖其实就是一个个watcher
  */
	update(node, exp, dir) {
		/*------初始化-----*/
		const fn = this[dir + "Updater"];
		const val = this.$vm[exp];
		fn && fn(node, val);

		/*------更新---------*/
		new Watcher(this.$vm, exp, (val) => {
			fn && fn(node, val);
		});
	}

	textUpdater(node, val) {
		node.textContent = val;
	}

	htmlUpdater(node, val) {
		node.innerHTML = val;
	}
}

Watcher

class Watcher {
	constructor(vm, key, updater) {
		this.$vm = vm;
		this.$key = key;
		this.updater = updater; // 收集的更新函数

		Dep.target = this;
		this.$vm[this.$key]; // 触发一次get,使自动收集依赖
		Dep.target = null; // 相同依赖只收集一次;一个key对应一个dep
	}

	update() {
		this.updater.call(this.$vm, this.$vm[this.$key]);
	}
}

修改上面的defineReactive

function defineReactive(obj, key, val) {
	observe(obj);
	const dep = new Dep();
	Object.defineProperty(obj, key, {
		get() {
			Dep.target && dep.add(Dep.target);
			return val;
		},
		set(newVal) {
			if (newVal === val) return val;
			val = newVal; // 闭包
			dep.notify(); // 更新
		},
	});
}

Dep

依赖收集到Dep中,更新也是使用dep更新

class Dep {
	constructor() {
		this.deps = [];
	}

	// 此处的dep是一个watcher实例
	add(dep) {
		this.deps.push(dep);
	}

	notify() {
		this.deps.forEach((dep) => dep.update());
	}
}