Vue原理解析

从以下几个方面,深入理解vue底层原理

  • Vue工作机制
  • Vue响应式的原理
  • 依赖收集与追踪
  • 编译compile
Vue工作机制

在new Vue()之后。Vue会进行初始化,初始化生命周期、事件、props、methods、data、computed与watch等。其中最重要的是通过Object.defineProperty设置setter与getter,用来实现响应式依赖收集

初始化之后调用$mount 指定挂载组件,启动编译器,编译器再生成渲染函数即更新函数与依赖收集,生成虚拟DOM(一个js对象),当数据修改时,先修改虚拟DOM中的数据,然后数组做diff,最后汇总所有的diff,做真实DOM更新。
在这里插入图片描述

Vue响应式的原理defineProperty
	class Vue {
		constructor(options) {
			this._data = options.data;
			this.observer(this._data);
		}
		observer(value) {
			if (!value || typeof value !== "object") {
			return;
		}
		Object.keys(value).forEach(key => {
			this.defineReactive(value, key, value[key]);
		});
		}
		defineReactive(obj, key, val) {
			Object.defineProperty(obj, key, {
				enumerable: true /* 属性可枚举 */,
				configurable: true /* 属性可被修改或删除 */,
				get() {
					return val;
				},
				set(newVal) {
					if (newVal === val) return;
					this.cb(newVal);
				}
			});
		}
		cb(val) {
			console.log("更新数据了", val);
		}
	}
	let o = new Vue({
		data: {
			test: "I am test."
		}
	});
	o._data.test = "hello";
依赖收集与追踪
	new Vue({
		template:
			`<div>
			<span>{{text1}}</span>
			<span>{{text2}}</span>
			<div>`,
		data: {
			text1: 'name1'
		},
		created(){
			this.text1="hello"
		}
	})

text1被修改,所以视图更新,但是text2视图没用到,所以不需要更新,如何实现呢,就需要我们的依赖收集

	// 依赖收集
	class Dep {
		constructor () {
			// 存数所有的依赖
			this.deps = []
		}
		// 在deps中添加一个监听器对象
		addDep (dep) {
			this.deps.push(dep)
		}
		// 通知所有监听器去更新视图
		notify () {
			this.deps.forEach((dep) => {
				dep.update()
			})
		}
	}
	class Watcher {
		constructor () {
			// 在new一个监听器对象时将该对象赋值给Dep.target,在get中会用到
			Dep.target = this
		}
		// 更新视图的方法
		update () {
			console.log("视图更新啦~")
		}
	}

我们在增加了一个 Dep 类的对象,用来收集 Watcher 对象。读数据的时候,会触发 reactiveGetter 函数把当前的Watcher 对象(存放在 Dep.target 中)收集到 Dep 类中去。
写数据的时候,则会触发 reactiveSetter 方法,通知Dep 类调用 notify 来触发所有 watcher 对象的update 方法更新对应视图

	constructor(options) {
		this._data = options.data
		this.observer(this._data)
		// 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
		new Watcher();
		// 在这里模拟render的过程,为了触发test属性的get函数
		console.log('模拟render,触发test的getter', this._data.test);
	}
	defineReactive(obj, key, val) {
		const dep = new Dep()
		Object.defineProperty(obj, key, {
			enumerable: true,
			configurable: true,
			get: function reactiveGetter() {
				// 将Dep.target(即当前的Watcher对象存入Dep的deps中)
				dep.addDep(Dep.target)
				return val
			},
			set: function reactiveSetter(newVal) {
				if (newVal === val) return
				// 在set的时候触发dep的notify来通知所有的Watcher对象更新视图
				dep.notify()
			}
		})
	}

编译compile

核心逻辑 获取dom,遍历dom,获取{{}}、k-和@开头的 ,设置响应式
在这里插入图片描述

目标功能

	<body>
		<div id="app">
			<p>{{name}}</p>
			<p k-text="name"></p>
			<p>{{age}}</p>
			<p>
			{{doubleAge}}
			</p>
			<input type="text" k-model="name">
			<button @click="changeName">呵呵</button>
			compile.js
			<div k-html="html"></div>
		</div>
		<script src='./compile.js'></script>
		<script src='./kaikeba-vue.js'></script>
		<script>
			let kaikeba = new Vue({
				el:'#app',
					data: {
					name: "I am test.",
					age:12,
					html:'<button>这是一个按钮</button>'
				},
				created(){
					console.log('开始啦')
					setTimeout(()=>{
						this.name='我是蜗牛'
					}, 1500)
				},
				methods:{
					changeName(){
						this.name = '哈喽,世界'
						this.age=1
						this.id = 'xx'
						console.log(1,this)
					}
				}
			})
		</script>
	</body>

compile.js

	// el: 元素
	// vm: vue的实例
	class Compile {
		constructor(el,vm) {
			this.$vm = vm
			// 拿到需要遍历的宿主
			this.$el = document.querySelector(el)
			if (this.$el) {
				// 将$el内部的东西转化为文档碎片Fragment,不能直接大量操作DOM
				this.$fragment = this.node2Fragment(this.$el)
				// 开始编译
				this.compileElement(this.$fragment)
				// 将编译好的html追加至$el(追加)
				this.$el.appendChild(this.$fragment)
			}
		}
		node2Fragment(el) {
			// 新建文档碎片 dom接口
			let fragment = document.createDocumentFragment()
			let child
			// 将原生节点拷贝到fragment
			// 此处做的是移动操作,循环一次,el中的元素就少一个
			while (child = el.firstChild) {
				fragment.appendChild(child)
			}
			return fragment
		}
		compileElement(el) {
			let childNodes = el.childNodes
			Array.from(childNodes).forEach((node) => {
				let text = node.textContent
				// 表达式文本
				// 就是识别{{}}中的数据
				let reg = /\{\{(.*)\}\}/
				// 按元素节点方式编译
				if (this.isElementNode(node)) {
					this.compile(node)
				} else if (this.isInterpolation(node) && reg.test(text)) {
					// 文本 并且有{{}}
					this.compileText(node, RegExp.$1)
				}
				// 遍历编译子节点,递归
				if (node.childNodes && node.childNodes.length) {
					this.compileElement(node)
				}
			})
		}
		compile(node) {
			let nodeAttrs = node.attributes
			Array.from(nodeAttrs).forEach( (attr)=>{
				// 规定:指令以 v-xxx 命名
				// 如 <span v-text="content"></span> 中指令为 v-text
				let attrName = attr.name // v-text
				let exp = attr.value // content
				if (this.isDirective(attrName)) {
					let dir = attrName.substring(2) // text
					// 普通指令
					this[dir] && this[dir](node, this.$vm, exp)
				}
				// @
				if(this.isEventDirective(attrName)){
					let dir = attrName.substring(1) // text
					this.eventHandler(node, this.$vm, exp, dir)
				}
			})
		}
		compileText(node, exp) {
			// node.textContent = this.$vm.$data[RegExp.$1]
		    // RegExp.$1 为{{name}}中的name,数据只会初始化一次,
		    // 如果数据发生变化是没用的,需要更新函数
			this.text(node, this.$vm, exp)
		}
		isDirective(attr) {
			// k-
			return attr.indexOf('k-') == 0
		}
		isEventDirective(dir) {
			// @
			return dir.indexOf('@') === 0
		}
		isElementNode(node) {
			return node.nodeType == 1
		}
		// 插值文本
		isInterpolation(node) {
			return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
		}
		text(node, vm, exp) {
			this.update(node, vm, exp, 'text')
		}
		html(node, vm, exp) {
			this.update(node, vm, exp, 'html')
		}
		// 双向绑定
		model(node, vm, exp) {
			this.update(node, vm, exp, 'model')
			let val = vm.exp
			node.addEventListener('input', (e)=>{
				let newValue = e.target.value
				vm[exp] = newValue
				val = newValue
			})
		}
		// 核心函数:根据指令dir,来确定更新函数
		update(node, vm, exp, dir) {
			let updaterFn = this[dir + 'Updater']
			// 初始化数据,只走一次
			updaterFn && updaterFn(node, vm[exp])
			// 需要依赖收集
			new Watcher(vm, exp, function(value) {
				updaterFn && updaterFn(node, value)
			})
		}
		// 事件处理
		eventHandler(node, vm, exp, dir) {
			let fn = vm.$options.methods && vm.$options.methods[exp]
			if (dir && fn) {
				node.addEventListener(dir, fn.bind(vm), false)
			}
		}
		textUpdater(node, value) {
			node.textContent = value
		}
		// 动态插入html
		htmlUpdater(node, value) {
			node.innerHTML = value
		}
		// 将文本的值映射到input框中
		modelUpdater(node, value) {
			node.value = value
		}
}
入口文件
	class Vue {
		constructor(options) {
			// 数据响应化
			this.$data = options.data
			this.$options = options
			this.observer(this.$data)
			
			// 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
			// 将实例挂在Dep的静态属性target上,静态属性会覆盖
			// new Watcher()
			// 在这里模拟render的过程,为了触发test属性的get函数
			console.log('模拟render,触发test的getter', this.$data)
			
			// created执行
			if(options.created){
				options.created.call(this)
			}
			this.$compile = new Compile(options.el, this)
		}
		observer(value) {
			if (!value || (typeof value !== 'object')) {
				return
			}
			//给每个$data上的属性,数据响应化
			Object.keys(value).forEach((key) => {
				// 代理data中的属性到vue实例上
				this.proxyData(key)
				// 数据响应化
				this.defineReactive(value, key, value[key])
			})
		}
		defineReactive(obj, key, val) {
			this.observe(val) // 为对象时,需要深层次数据响应
			
			// 每个key都有一个独立的依赖管理器,在适时将watcher放入(obj有几个key就有多少个Dep)
			const dep = new Dep()
			Object.defineProperty(obj, key, {
				enumerable: true,
				configurable: true,
				get() {
					// dep.addDep(new Watcher()) // 在读取属性时,将watcher添加到依赖管理器
					// 将Dep.target(即当前的Watcher对象存入Dep的deps中)
					Dep.target && dep.addDep(Dep.target)
					return val
				},
				set(newVal) {
					if (newVal === val) return
					val = newVal
					// console.log(key + '属性更新了' + newVal + '可以编译模板,更新页面数据')
					// 在set的时候触发dep的notify来通知所有的Watcher对象更新视图
					// 依赖收集与追踪,通知依赖管家,告诉所有watcher可以更新了
					dep.notify()
				}
			})
		}			
	
		proxyData(key) {
			Object.defineProperty(this, key, {
				configurable: false,
				enumerable: true,
				get() {
					return this.$data[key]
				},
				set(newVal) {
					this.$data[key] = newVal
				}
			})
		}
	}

依赖收集 Dep

	class Dep {
		constructor() {
			// 存数所有的依赖(watcher)
			this.deps = []
		}
		// 在deps中添加一个监听器对象
		addDep(dep) {
			this.deps.push(dep)
		}
		depend() {
			Dep.target.addDep(this)
		}
		// 通知所有监听器去更新视图
		notify() {
			this.deps.forEach((dep) => {
				dep.update()
			})
		}
	}

监听器

	// 监听器
	class Watcher {
		constructor(vm, key, cb) {
			// 在new一个监听器对象时将该对象赋值给Dep.target,在get中会用到
			// 将 Dep.target 指向自己
			// 然后触发属性的 getter 添加监听
			// 最后将 Dep.target 置空
			this.cb = cb
			this.vm = vm
			this.key = key
			this.value = this.get()
		}
		get() {
			// Dep.target指向实例,静态属性target,会覆盖之前的new watcher
			Dep.target = this
			// 触发geeter,添加依赖
			let value = this.vm[this.key]
			Dep.target = null
			return value
		}
		// 更新视图的方法
		update() {
			this.cb.call(this.vm, this.value)
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值