Vue 2.x 响应式原理(二)

Vue 响应式原理模拟

接上一步
模拟一个 简易版的 vue
整体分析

  • Vue 基本结构
  • 打印 Vue 实例观察
  • 整体结构

Vue 要实现的

  • 功能
    • 负责接收初始化的参数(选项)
    • 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
    • 负责调用 observer 监听 data 中所有属性的变化
    • 负责调用 complier 解析指令/差值表达式
  • 结构 vue 类
    • $options
    • $el
    • $data
    • _proxyData() 私有

Vue 类.js

class Vue {
	constructor(options) {
		// 1. 通过属性保存选项的数据
		this.$options = options || {}
		this.$data = options.data || {}
		this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
		// 2. 把 data 中的成员转换成 getter 和 setter,注入到 vue 实例中
		this._proxyData(this.$data)
		// 3. 调用 observer 对象,监听数据的变化
		new Observer(this.$data)
		// 4. 调用 compiler 对象,解析指令和差值表达式
		new Compiler(this)
	}
	_proxyData(data) {
		// 遍历 data 中的所有属性
		Object.keys(data).forEach((key) => {
			// 把 data 中的属性注入到 vue 实例中
			Object.defineProperty(this, key, {
				enumerable: true,
				configurable: true,
				get() {
					return data[key]
				},
				set(newVal) {
					if (newVal === data[key]) return
					data[key] = newVal
				}
			})
		})
	}
}

Observer 类.js

// 负责数据劫持
// 把 $data 中的成员转换成 getter/setter
class Observer {
	constructor(data) {
		this.walk(data)
	}
	// 遍历 data 对象的所有属性
	walk(data) {
		// 1. 判断 data 是否是对象
		if (!data || typeof data !== 'object') return
		// 2. 遍历 data 对象的所有属性
		Object.keys(data).forEach((key) => {
			this.defineReactive(data, key, data[key])
		})
	}
	// 定义响应式成员
	defineReactive(obj, key, val) {
		let that = this
		// 负责收集依赖,并发送通知
		let dep = new Dep()
		// 如果 val 是对象,把 val 内部的属性转换成响应式数据
		this.walk(val)
		Object.defineProperty(obj, key, {
			enumerable: true,
			configurable: true,
			get() {
				// 收集依赖
				Dep.target && dep.addSub(Dep.target)
				return val
			},
			set(newVal) {
				if (newVal === val) return
				val = newVal
				that.walk(newVal)
				// 发送通知
				dep.notify()
			}
		})
	}
}

Compile 类.js

class Compiler {
	constructor(vm) {
		this.el = vm.$el
		this.vm = vm
		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)
			}
			// 判断 node 节点,是否有子节点,就递归
			if (node.childNodes && node.childNodes.length) {
				this.compile(node)
			}
		})
	}
	// 编译元素节点,处理指令
	compileElement(node) {
		// 遍历所有的属性节点
		Array.from(node.attributes).forEach((attr) => {
			let attrName = attr.name
			// 判断是否是指令
			if (this.isDirective(attrName)) {
				// v-text --> text
				attrName = attrName.substr(2)
				let key = attr.value
				this.update(node, key, attrName)
			}
		})
	}
	update(node, key, attrName) {
		let updateFn = this[attrName + 'Updater']
		updateFn && updateFn.call(this, node, this.vm[key], key)
	}

	// 处理 v-text 指令
	textUpdater(node, value, key) {
		node.textContent = value
		new Watcher(this.vm, key, (newVal) => {
			node.textContent = newVal
		})
	}
	// v-model
	modelUpdater(node, value, key) {
		node.value = value
		new Watcher(this.vm, key, (newVal) => {
			node.value = newVal
		})
		// 双向绑定
		node.addEventListener('input', () => {
			this.vm[key] = node.value
		})
	}

	// 编译文本节点,处理差值表达式
	compileText(node) {
		let reg = /\{\{(.+?)\}\}/
		let value = node.textContent
		if (reg.test(value)) {
			let key = RegExp.$1.trim()
			node.textContent = value.replace(reg, this.vm[key])

			// 创建 watcher 对象,当数据改变更新视图
			new Watcher(this.vm, key, (newVal) => {
				node.textContent = newVal
			})
		}
	}
	// 判断元素属性是否是指令
	isDirective(attrName) {
		return attrName.startsWith('v-')
	}
	// 判断节点是否是文本节点
	isTextNode(node) {
		return node.nodeType === 3
	}
	// 判断节点是否是元素节点
	isElementNode(node) {
		return node.nodeType === 1
	}
}

Watcher 类.js

class Watcher {
	constructor(vm, key, cb) {
		this.vm = vm
		// data 中的属性名称
		this.key = key
		// 回调函数负责更新视图
		this.cb = cb

		// 把 watcher 对象记录到 Dep 类的静态属性 target
		Dep.target = this
		// 触发 get 方法,在 get 方法中调用 addSub
		this.oldValue = vm[key]
		Dep.target = null
	}
	// 当数据发生变化的时候更新视图
	update() {
		let newVal = this.vm[this.key]
		if (this.oldValue === newVal) return
		this.cb(newVal)
	}
}

Dep 类.js

class Dep {
	constructor() {
		// 存储所有的观察者
		this.subs = []
	}
	// 添加观察者
	addSub(sub) {
		if (sub && sub.update) {
			this.subs.push(sub)
		}
	}
	// 发送通知
	notify() {
		this.subs.forEach((sub) => {
			sub.update()
		})
	}
}

引入js

按相应的引入依赖,引入进 index.html 页面中,然后写相应的例子就行

<div id="app">
	<h1>差值表达式</h1>
	<h3>{{msg}}</h3>
	<h4>{{count}}</h4>
	<h1>v-text</h1>
	<div v-text="msg"></div>
	<h1>v-model</h1>
	<input type="text" v-model="msg" />
	<input type="text" v-model="count" />
</div>
<script src="./js/dep.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/compiler.js"></script>
<script src="./js/observer.js"></script>
<script src="./js/vue.js"></script>
<script>
	let vm = new Vue({
		el: '#app',
		data: {
			msg: {
				info: '123'
			},
			count: 100,
			person: { name: 'zhang' }
		}
	})
</script>

以上是一个超简单的响应式例子,还存在一些问题,不过理解一下对应的响应式原理流程就行

posted @ 2022-06-05 14:01  地灵  阅读(50)  评论(0编辑  收藏  举报