首页>>前端>>Vue->vue响应式(vue响应式原理面试)

vue响应式(vue响应式原理面试)

时间:2023-12-02 本站 点击:0

聊一聊 Vue3 中响应式原理

Vue.js 3.0 "One Piece" 正式发布已经有一段时间了,真可谓是千呼万唤始出来啊!

相比于 Vue2.x , Vue3.0 在新的版本中提供了更好的性能、更小的捆绑包体积、更好的 TypeScript 集成、用于处理大规模用例的新 API 。

在发布之前,尤大大就已经声明了响应式方面将采用 Proxy 对于之前的 Object.defineProperty 进行改写。其主要目的就是弥补 Object.defineProperty 自身的一些缺陷,例如无法检测到对象属性的新增或者删除,不能监听数组的变化等。

而 Vue3 采用了新的 Proxy 实现数据读取和设置拦截,不仅弥补了之前 Vue2 中 Object.defineProperty 的缺陷,同时也带来了性能上的提升。

今天,我们就来盘一盘它,看看 Vue3 中响应式是如何实现的。

The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object. MDN

Proxy - 代理,顾名思义,就是在要访问的对象之前增加一个中间层,这样就不直接访问对象,而是通过中间层做一个中转,通过操作代理对象,来实现修改目标对象。

关于 Proxy 的更多的知识,可以参考我之前的一篇文章 —— 初探 Vue3.0 中的一大亮点——Proxy ! ,这里我就不在赘述。

Vue3 中响应式核心方法就是 reactive 和 effect , 其中 reactive 方法是负责将数据变成响应式, effect 方法的作用是根据数据变化去更新视图或调用函数,与 react 中的 useEffect 有点类似~

其大概用法如下:

默认会执行一次,打印 Hello , 之后更改了 data.name 的值后,会在触发执行一次,打印 World 。

我们先看看 reactive 方法的实现~

reactive.js

首先应该明确,我们应该导出一个 reactive 方法,该方法有一个参数 target ,目的就是将 target 变成响应式对象,因此返回值就是一个响应式对象。

reactive 方法基本结构就是如此,给定一个对象,返回一个响应式对象。

其中 isObject 方法用于判断是否是对象,不是对象不需要代理,直接返回即可。

reactive 方法的重点是 Proxy 的第二个参数 handler ,它承载监控对象变化,依赖收集,视图更新等各项重大责任,我们重点来研究这个对象。

handler.js

在 Vue3 中 Proxy 的 handler 主要设置了 get , set , deleteProperty , has , ownKeys 这些属性,即拦截了对象的读取,设置,删除, in 以及 Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法。

这里我们偷个懒,暂时就考虑 set 和 get 操作。

handler.get()

get 获取属性比较简单,我们先来看看这个,这里我们用一个方法创建 getHanlder 。

这里推荐使用了 Reflect.get 而并非 tarGET@[key] 。

可以发现, Vue3 是在取值的时候才去递归遍历属性的,而非 Vue2 中一开始就递归 data 给每个属性添加 Watcher ,这也是 Vue3 性能提升之一。

handler.set()

同理 set 操作,我们也是用一个方法创建 setHandler 。

Reflect.set 会返回一个 Boolean 值,用于判断属性是否设置成功。

完事后将 handler 导出,然后在 reactive 中引入即可。

测试几组对象貌似没啥问题,其实是有一个坑,这个坑也跟数组有关。

如上例子,如果我们选择代理数组,在 setHandler 中打印其 key 和 value 的话会得到 3 4 , length 4 这两组值:

如果不作处理,那么会导致如果更新视图的话,则会触发两次,这肯定是不允许的,因此,我们需要将区分新增和修改这两种操作。

Vue3 中是通过判断 target 是否存在该属性来区分是新增还是修改操作,需要借助一个工具方法 —— hasOwnProperty 。

这里我们将上述的 createSetter 方法修改如下:

如此一来,我们调 push 方法的时候,就只会触发一次更新了,非常巧妙的避免了无意义的更新操作。

effect.js

光上述构造响应式对象并不能完成响应式的操作,我们还需要一个非常重要的方法 effect ,它会在初始化执行的时候存储跟其有关的数据依赖,当依赖数据发生变化的时候,则会再次触发 effect 传递的函数。

其基本雏形如下,入参是一个函数,还有个可选参数 options 方便后面计算属性等使用,暂时不考虑:

createReactiveEffect 就是为了将 fn 变成响应式函数,监控数据变化,执行 fn 函数,因此该函数是一个高阶函数。

createReactiveEffect 将原来的 fn 转变成一个 reactvieEffect , 并将当前的 effect 挂到全局的 activeEffect 上,目的是为了一会与当前所依赖的属性做好对应关系。

我们必须要将依赖属性构造成 { prop : [effect,effect] } 这种结构,才能保证依赖属性变化的时候,依次去触发与之相关的 effect ,因此,需要在 get 属性的时候,做属性的依赖收集,将属性与 effect 关联起来。

依赖收集 —— track

在获取对象的属性时,会触发 getHandler ,再次做属性的依赖收集,即 Vue2 中的发布订阅。

在 setHandler 中获取属性的时候,做一次 track(target, key) 操作。

整个 track 的数据结构大概是这样

目的就是将 target , key , effect 之间做好对应的关系映射。

打印 targetMap 的结构如下:

**触发更新 —— trigger **

上述已经完成了依赖收集,剩下就是监控数据变化,触发更新操作,即在 setHandler 中添加 trigger 触发操作。

这样一来,获取数据的时候通过 track 进行依赖收集,更新数据的时候再通过 trigger 进行更新,就完成了整个数据的响应式操作。

再回头看看我们先前提到的例子:

控制台会依次打印 Hello ***** effect ***** 以及 World ***** effect ***** , 分别是首次渲染触发跟更新数据重渲染触发,至此功能实现!

整体来说, Vue3 相比于 Vue2 在很多方面都做了调整,数据的响应式只是冰山一角,但是可以看出尤大团队非常巧妙的利用了 Proxy 的特点以及 es6 的数据结构和方法。另外, Composition API 的模式跟 React 在某些程度上有异曲同工之妙,这种设计模式让我们在实际开发使用中更加的方法快捷,值得我们去学习,加油!

最后附上仓库地址 github ,欢迎各位大佬批评斧正~

vue2响应式原理总结

vue组件实例化时,会对data属性深度遍历(遇到数组或者对象)为每一个属性添加数据劫持。数据劫持就是使用Object.defineProperty(de fai in pro pu tei)方法添加get/set方法。

在这个过程中会实例化一个Dep类。

1.在get拦截器里触发dep实例的depend方法,进行依赖收集,实质是在dep的实例属性sub数组中添加依赖这个属性的watcher实例。

2.在set拦截器里触发dep实例的notify方法,对收集到的所有依赖派发更新,(watcher的update方法)

vue组件实例化时会实例化一个渲染watcher,渲染watcher实例化过程会做两件事情。

1.创建vnode,在这个过程中,访问了data属性,触发了get方法,完成了依赖收集。

2.触发了子组件的实例化,子组件实例化又会重复上述数据劫持的过程。

这个过程就是对组件树的深度遍历。

结合组件生命周期来看整个过程,父组件会先触发created钩子,子组件后触发created钩子。而子组件mouted钩子会先执行,父组件的mouted钩子后执行。

分步骤记忆

1、实现页面不刷新的原理

2、页面视图刷新的原理

实现页面不刷新

1.hash

2.history

3.abstract:支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。

1.hash(哈希模式),#符号后边是浏览器行为,在改变的时候不对页面进行刷新(重新请求URL)(监听hashChange事件)

2.history模式,H5新增了pushState,replaceState连个新API,可以修改历史记录却不会使浏览器刷新页面。

视图更新原理

其原理就是vue的响应式更新dom的原理,m = v

m是数据,也就是在vue-router install时在根组件(root vue component)添加了_route属性,在匹配到对应路由后更新了_route属性值,继而触发了该属性值的渲染watcher,在继而触发dom更新。

两种模式的不同

1.部署时,history模式需要服务端处理所有可能的路径(例如配置nginx的配置文件),防止出现404。哈希模式则不需要。

2.URL表示不同。

v-model指令就是 v-bind:value 和 @input 的语法糖。

它即可以支持原生表单元素,也可以支持自定义组件

在自定义组件中其实际是这样的:

它的实现通过自定义render函数, 缓存了 vnode

Vue 在更新 DOM 时是异步执行的,只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。

如果同一个 watcher 被多次触发,只会被推入到队列中一次。在缓冲时会去除重复数据避免不必要的计算和 DOM 操作。

$nextTick(cb) 目的是在DOM 更新完成后传入的回调函数再被调用。

vue数组响应式原理

vue2中Object.defineProperty响应式只对对象有效,对数组无效,所以对数组做额外处理。我们知道,会改变数组本身的方法只有7个:sort, push, pop, slice, splice, shift, unshift,所以可以通过重写这些方法来达到数组响应式

解决方案:

1. 找到数组原型

2. 覆盖那些能够修改数组的更新方法,让他们在修改数组同时,还可以通知更新

3. 将得到的新的原型设置到数组实例原型上

4. 对数组内部元素实现响应式

// 实现数组响应式// 1. 替换数组原型中7个方法constoriginalProto=Array.prototype// 克隆体原数组原型constarrayProto=Object.create(originalProto)// 可修改数组的7个方法 , 'sort'constchangeMethods=['push','pop','shift','unshift','slice','splice','sort']//  2. 在克隆的原型上,覆盖那些能够修改数组的更新方法,让他们在修改数组同时,还可以通知更新changeMethods.forEach(method={arrayProto[method]=function(){// 进行原始操作originalProto[method].apply(this,arguments)// 覆盖操作:增加更新通知console.log(`数组正在执行${method}方法`);}})// 对象响应化functiondefineReactive(obj,key,value){Object.defineProperty(obj,key,{get(){console.log('获取'+key);returnvalue},set(newVal){if(newVal!==value){// console.log(newVal);// console.log(JSON.stringify(obj[key]));console.log(`正在改变${key}值:从${obj[key]}变为${newVal}`)value=newVal}}})}functionobserver(obj){// 不是对象或者为null,不做响应式,结束if(typeofobj!=='object'||obj===null)return;// 如果是数组,修改其实例的原型if(Array.isArray(obj)){// 3. 将得到的新的原型设置到数组实例原型上obj.__proto__=arrayProto// 4. 对数组内的元素,同样进行响应化for(leti=0;iobj.length;i++){// console.log(obj[i]);observer(obj[i])}// 如果是对象}else{Object.keys(obj).forEach(key={console.log(obj,key,obj[key]);defineReactive(obj,key,obj[key])})}}obj=[{a:1},2,7,5,3]observer(obj)obj.push(4)// 数组正在执行push方法obj.pop()// 数组正在执行pop方法obj[0].a=2// 获取a    // 正在改变a值:从1变为2obj.sort()// 数组正在执行sort方法console.log(obj);// [ 2, 3, 5, 7, { a: [Getter/Setter] } ]console.log(obj[4].a);// 获取a  // 2

链接:

Vue3.0 响应式原理

Vue3 使用 Proxy 对象重写响应式系统,这个系统主要有以下几个函数来组合完成的:

1、reactive:

接收一个参数,判断这参数是否是对象。不是对象则直接返回这个参数,不做响应式处理

创建拦截器对象 handler, 设置 get/set/deleteProperty

get

收集依赖(track)

返回当前 key 的值。

如果当前 key 的值是对象,则为当前 key 的对象创建拦截器 handler, 设置 get/set/deleteProperty

如果当前的 key 的值不是对象,则返回当前 key 的值

set

设置的新值和老值不相等时,更新为新值,并触发更新(trigger)

deleteProperty

当前对象有这个 key 的时候,删除这个 key 并触发更新(trigger)

返回 Proxy 对象

2、effect: 接收一个函数作为参数。作用是:访问响应式对象属性时去收集依赖

3、track:

接收两个参数:target 和 key

如果没有 activeEffect,则说明没有创建 effect 依赖

如果有 activeEffect,则去判断 WeakMap 集合中是否有 target 属性,

WeakMap 集合中没有 target 属性,则 set(target, (depsMap = new Map()))

WeakMap 集合中有 target 属性,则判断 target 属性的 map 值的 depsMap 中是否有 key 属性

depsMap 中没有 key 属性,则 set(key, (dep = new Set()))

depsMap 中有 key 属性,则添加这个 activeEffect

4、trigger:

判断 WeakMap 中是否有 target 属性

WeakMap 中没有 target 属性,则没有 target 相应的依赖

WeakMap 中有 target 属性,则判断 target 属性的 map 值中是否有 key 属性,有的话循环触发收集的 effect()

vue2数据响应式原理

vue2响应式原理由 Observer 类, Dep 类和 Watcher 类互相调用实现, Observer 类是把一个普通的object类变成每一层都能相应的类, Dep 类的作用是添加,移除,通知和收集订阅者, Watcher 类是订阅者,主要功能是把当数据改变的时候,去调用回调函数,修改dom节点

那么是怎么实现响应式的呢,首先是一个函数,要先转换为可响应的,那就需要用到 Observer 类

这个 observe 函数就是对 Observer 类做多了一层封装

而 Observer 类是通过 Object.defineProperty 来监控数据的获取和改变的

关键在于 defineReactive 方法,这个方法是对 Object.defineProperty 做了一层封装,并且对对象的每一层做递归调用,实现了每一层都有响应监控

但是是怎么知道现在要保存哪一个 Watcher 实例到订阅者数组里面的呢?其实就是用了这个 Dep.target , Dep.target 相当于 window.target ,全局只有一个,全局也能访问

首先得先讲一讲 Watcher 类,我们先回到上面的index.js,对象要让 Watcher 类进行监听,而 Watcher 有3个参数,第一个是监听的对象,第二个是监听的属性,比如 a.b.c.d ,第三个是属性改变后触发的回调函数

先来讲一下 parsePath ,这个在工具类里,作用是访问 a.b.c.d 这种链式属性

首先是触发了 Watcher 的 get() 方法,把当前实例保存在了 Dep.target 里面

然后在调用 parsePath 获取属性值的过程中,会挨个访问响应对象的属性,就会触发相应的 getter ,我们回到 defineReactive.js ,可以发现这时候相应属性的 getter 就会把 Dep.target 也就是相应的 Watcher 的实例保存在了 Dep 类的订阅者数组里面

最后,在改变属性的时候,相应属性的 setter 就会通知之前已经保存的订阅者数组,遍历触发回调

能说说vue的响应式原理吗?

Vue 是一个 MVVM 框架,核心是双向数据绑定,VM(视图模型)是作为 V(视图) 和 M(模型)的桥梁。下面是对 Vue 响应式(双向数据绑定)的理解,如果错误尽请指出,一起交流,共同进步。

Vue响应式原理核心是 数据劫持,采用 ES5 的 object.defineproperty 的 getter 和 setter 方法。从一个例子出发:

首先,在Vue初始化阶段,通过 observer 对 data 中的属性进行递归的劫持,包括 name、job_ undergo、a、b等

在 get阶段也就是初始化视图时,为每一个劫持的属性分配一个 依赖收集器,主要收集当前属性的观察者对象,例子中 name 属性在模板中有两处被使用,那么 name 属性的依赖收集器中就存放两个观察者对象

当点击按钮时,将 name 修改为 lisi 时,会触发 observer 的 setter 函数,将 value 更新为 lisi 最新值,然后通知依赖收集器数据发生了更新。

依赖收集就是发布订阅模式,依赖收集器会通知所有的观察者对象,当前name 属性有两个观察者对象。

观察者对象调用对应的回调函数进行相关的处理和DOM更新

以上是纯响应式原理的分析和总结,下面配一张流程图:


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Vue/9343.html