深入浅出Vue变化侦测
2020/3/18 11:01:41
本文主要是介绍深入浅出Vue变化侦测,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1.前言
我们都知道vue是个很优秀的框架,官网上也说明了是一个渐进式框架。那么什么是渐进式框架呢?
所谓渐进式,就是把框架分层。如图所示:
视图层渲染作为最核心部分,其特性之一就是响应式系统,视图会随着状态的变化而变化。这也是我最喜欢Vue的地方,视图里任何一个地方,都可以用一种状态(变量)来表示。
从状态生成DOM,在输出到用户界面显示一整套过程叫做渲染。vue在运行时不断地重新渲染。而响应式系统赋予了框架重新渲染的能力,其重要组成部分就是变化侦测。学会了变化侦测,更有利于接下来对api的原理学习,接下来我们便开始从0到1实现一个变化侦测逻辑。
2.目录
3.1 什么是变化侦测
3.2 如何追踪变化
3.3 什么是依赖,如何收集依赖
3.4 依赖收集在哪里
3.5 依赖是谁,什么是watcher?
3.6递归侦测所有key
3.7 object的问题
3.1什么是变化侦测
上面我们说过,渲染就是Vue会自动通过状态生成DOM,并输出到页面上。Vue的渲染过程是声明式,我们可以通过模板来描述状态与DOM之间的映射关系。
在网页运行时,通过各种用户交互,Vue内部的数据状态会不断改变,此时页面也会不断渲染。但是,我们又怎么知道哪些状态发生了怎么样的改变?这就是变化侦测,只要是状态一改变,我们的vue就能知道,通过跟新的状态去渲染视图。
3.2如何追踪变化
关于追踪,在JavaScript中,我们如何知道一个对象改变了呢?
- Object.defineProperty
- ES6的proxy
在Vue3之前,我们还是使用Object.defineProperty
我们知道,Object.defineProperty用来侦测变化会有很多缺陷,并且在Vue3之后都用Proxy重写这部分代码了,那么我们还有必要学习这部分吗?其实我觉得很有必要,我们毕竟是学习原理和思想的,通过对原理的探索,我们更能领会牛人解决问题的思想,在以后的编程路上,还是很有必要的。
知道了如何追踪对象的变化,那么我们就可以写出以下代码:
function defineReactive (data, key, val) { Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { return val }, set: function (newval) { if (val === newval) { return } val = newval } }) } 复制代码
我们定义了一个函数来封装了一下Object.defineProperty。其作用就是定义一个响应式数据,封装后我们只需传递data,key,val就行了。那么如何追踪变化?每当我们从data中的key读取数据时,get函数触发了,在设置data的key数据时,set就被触发了。
3.3什么是依赖,如何收集呢?
上面只是对Object.defineProperty进行封装了一下,但实际上并没什么作用,真正有用的是收集依赖。现在我们就有两个问题了:
- 什么是依赖?
- 如何收集依赖?
我们先回头思考一下,什么是响应式。就是数据改变了,视图自动更新。所以我们要去观察数据,当数据的属性发生变化时,我们就可以通知曾经使用了该数据的地方,这些地方,就被称作为依赖,举个例子:
<template> <div> <p>{{name}}</p> //一个依赖 <h2 v-text='name'></h2> //另一个依赖 </div> </template> 复制代码
模板中,有两个地方是用了数据name,所以就有两个依赖。当数据改变时,我们就要向这两个依赖发送通知。
通过变化侦测中,在读取数据的时候,会触发getter,所以就通过在getter函数中去收集依赖,数据发生变化时,就需要在setter中触发依赖,所以我们可以把defineReactive函数改造一下:
function defineReactive (data, key, val) { let dep=[] //新增,依赖收集器 Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { dep.push(window.target) //新增,收集依赖,假设window.target就是一个依赖 return val }, set: function (newval) { if (val === newval) { return } val = newval dep.notify() //新增,通知依赖数据改变,关于dep后面会讲,这里只是抽象的表示需要做的事情 } }) } 复制代码
3.4依赖收集在哪里
我们可以封装一个类Dep,专门帮助我们管理依赖。在这个类中,我们可以收集依赖,删除依赖,通知依赖等等,其代码如下:
export default class dep { constructor () { this.subs = [] } addSub (sub) { this.subs.push(sub) } removeSub (sub) { remove(this.sub, sub) } depend () { if (window.target) { this.addSub(window.target) } } notify () { const subs = this.subs.slice() for (let i=0,l = subs.length;i<1;i++) { subs[i].update() } } } function remove (arr, item) { if (arr.length) { const index = arr.indexOf(item) if (index > -1) { return arr.splice(index, 1) } } } 复制代码
- subs:用数组作为一个容器,收集依赖
- addSub:添加依赖
- removeSub:移除依赖
- depend:假设window.target是一个依赖,判断是否有这个依赖,进而执行添加依赖操作
- notify:通知每个依赖,数据变化了,要执行每个依赖的更新视图方法。
之后我们还需要改造一下defineReactive:
function defineReactive (data, key, val) { let dep = new dep() //创建依赖收集器 Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { dep.depend() //判断是否有依赖 return val }, set: function (newval) { if (val === newval) { return } val = newval dep.notify() //通知每个依赖 } }) } 复制代码
3.5依赖是谁,什么是watcher?
在上面演示里,我们将winodw.target代表依赖,作用就是数据改变了,依赖就接受到了通知,然后再去通知其他地方。所以我们需要封装一个类,就叫watcher把。watcher实例就是一个一个的依赖。代码如下:
export default class watcher{ constructor(vm,exp,cb){ this.vm=vm this.getter=parsePath(exp) this.cb=cb this.value=this.get() } get(){ window.target=this let value=this.getter.call(this.vm,this.vm) window.target=undefined return value } update(){ const oldValue=this.value this.value=this.get() this.cb.call(this.vm,this.value,oldValue) } } 复制代码
watcher接受三个参数:
- vm:vue实例
- exp:{{}}这里面的表达式,还有v-text和v-html中的表达式
- cb:真正的更新DOM的函数(知道作用就行,后面模板解析会详细讲解) 其它参数:
- getter:通过parsePath函数解析表达式,获取表达式的值
- value:get方法返回值
- get:通过getter获取表达式的值
- update:更新视图
现在关于对象变化侦测基本原理都已近说完了,可能你现在还是感觉很懵,接下来我将整个过程从头来顺一下:
初始化过程:
响应式过程:
3.6递归侦测所有key
上面,整个变化侦测功能都已近实现,但是,只能侦测数据中某一个属性,我们希望能够把数据中所有属性都要侦测到,于是我们就要封装一个observer类.通过递归的形式,把data数据中所有属性都变成响应式。代码如下:
class Observer { constructor(value) { this.value = value if(!Array.isArray(value) { this.walk(value) } } walk (obj) { const keys = Object.keys(obj) for(let i = 0; i < keys.length; i++) { definedReactive(obj, keys[i], obj[keys[i]]) } } } function definedReactive(data, key, value) { if(typeof val === 'object') { new Observer(value) } let dep = new Dep() Object.defineProperty(data, key, { enumberable: true, configurable: true, get: function () { dep.depend() return value }, set: function (newVal) { if(value === newVal) { return } value = newVal dep.notify() } }) } 复制代码
简单理解:
3.6Object的问题
由于Object类型数据是通过setter/getter来追踪的,所以在有些语法中,即使数据改变,vue也追踪不到。
什么情况无法侦测:
- 新增属性
- 删除属性
解决办法通过vue提供的两个API——vm.$set和vm.$delete。后续会慢慢讲解的。
总结
只有懂得变化侦测原理,我们才能更深入的去学习vue的api,也能大大的帮助我们阅读源码。
每天去较真一个api,每天多一些进步。
这篇关于深入浅出Vue变化侦测的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04package.json 文件位置在哪?-icode9专业技术文章分享
- 2024-10-01Craco.js学习:从入门到实践指南
- 2024-10-01Create-React-App学习:入门与实践指南
- 2024-10-01CSS-in-JS学习:从入门到实践指南
- 2024-09-30JSX语法学习:从入门到初步掌握
- 2024-09-30Mock.js学习:入门教程与实战演练
- 2024-09-30React Hooks学习:从入门到实践
- 2024-09-30受控组件学习:React中的基础入门教程
- 2024-09-29JS定时器教程:初学者必看指南
- 2024-09-29JS对象教程:初学者的全面指南