ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-06-05 14:34:36  阅读:183  来源: 互联网

标签:node el Vue newVal vm 响应 key 原理 data


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>

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

标签:node,el,Vue,newVal,vm,响应,key,原理,data
来源: https://www.cnblogs.com/earthZhang/p/16343873.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有