面试的时候常常被问到 响应式 相干的内容,而Vue3.0 更新后,面试官又有了新的武器;

面试官:为什么 Vue3.0 要重写响应式零碎?

懵逼树上懵逼果,懵逼树下你和我,面试官在问什么,我该怎么答复,齐全不晓得怎么回事;

有些教训的小伙伴可能会从解释 Proxy 的益处开始简略聊一下,比方: Proxy 是间接代理对象,而不是劫持对象的属性;更好的数组监控;

这样的答复,勉强算是合格吧

那到底应该怎么答呢?

面试官背地的出题逻辑

别急,咱们先整顿一下思路,孙子兵法有云:“知己知彼,百战不殆”;面试就像打仗,你来我往,所以咱们须要换位思考,想一想,为什么面试官会问这样一个问题?面试官想从这个问题里失去什么答复?这个问题能够考查哪些技术点? 想分明这个问题,再回到本人身上,这些技术点,你都把握了吗?

说得直白一点,面试就像考试,你须要先 读题、审题能力答好这道题;

为什么很多人认为 “面试造火箭,工作拧螺丝”?因为没有换位思考,没有想分明面试题背地的逻辑;

那咱们想分明这个逻辑之后,须要咱们做的就是提取技术点,整顿思路,做出对应解答;当然,前提是你须要具备这些技术能力

那么接下来,我就尝试拆解一下这个面试题了,提取其中的知识点。

对于你来说,就是要看看这些知识点,你都把握了多少?

为什么 Vue3.0 要重写响应式零碎 ?

为什么重写?如果之前好好的,重写就没有意义,那之前存在什么问题,当初是怎么解决的?就是关键点了;

不晓得你对 Vue2.x 的响应式把握多少,是不是欠下了技术的债呢?没关系,我来帮你还债,先梳理 Vue2.x 的响应式;

其实基于这个面试题,背地还有很多技术点,下面这些,是与以后题目有间接关系的,理论面试中,很有可能基于这些技术点,在进行深刻交换,这里就不扩大了,你能把当初这些问题理分明,就算赚到了;

Vue2.x 响应式

其实对于这一点,在Vue 的官网文档中,早曾经有过阐明了,而且说得十分具体;官网文档:https://cn.vuejs.org/v2/guide/reactivity.html

当你把一个一般的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并应用 Object.defineProperty把这些 property 全副转为 getter/setter。Object.defineProperty 是 ES5 中一个无奈 shim 的个性,这也就是 Vue 不反对 IE8 以及更低版本浏览器的起因。
这些 getter/setter 对用户来说是不可见的,然而在外部它们让 Vue 可能追踪依赖,在 property 被拜访和批改时告诉变更。这里须要留神的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以倡议装置 vue-devtools 来获取对检查数据更加敌对的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会告诉 watcher,从而使它关联的组件从新渲染。

咱们应用官网给的一张图示,来梳理整个流程;

咱们先来看一段代码

响应式原理

data 中的 obj 就是一个一般的 JavaScript 对象,通过点击 Click 按钮,将获取到的随机数赋值给 this.message ,而 this.message 指向的就是 data 中 obj 对象的 message 属性;当message 产生数据扭转时,页面中 H1 标签的内容会随之扭转,这个过程就是就是响应式的;那么Vue 是如何实现的呢?

首先,Vue 外部应用 Object.defineProperty() 将 Data 中的每一个成员都转换为 getter / setter 的模式;getter 用来依赖收集,setter 用来派发更新;而模板内容,最终会被编译为 render 函数,在 render 函数中,咱们能发现 _v(_s(message)) message 被拜访了,就会触发 getter 来进行依赖收集,而在代码中的点击事件中,一旦事件处理程序被触发执行,那么 message 则会被批改,就会触发 setter来进行派发更新;

尽管流程理分明了,然而总感觉少点什么,怎么能力更通透呢?

咱们用代码来模仿整个的实现过程;

defineProperty 模仿代码

defineProperty 的根本用法,间接看手册就行了:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

咱们来看看代码:

<div id="app">     hello </div> <script>     // 模仿 Vue 中的 data 选项     let data = {       msg: 'hello'     }     // 模仿 Vue 的实例     let vm = {}     // 数据劫持:当拜访或者设置 vm 中的成员的时候,做一些干涉操作     Object.defineProperty(vm, 'msg', {       // 可枚举(可遍历)       enumerable: true,       // 可配置(能够应用 delete 删除,能够通过 defineProperty 从新定义)       configurable: true,       // 当获取值的时候执行       get () {         console.log('get: ', data.msg)         return data.msg       },       // 当设置值的时候执行       set (newValue) {         console.log('set: ', newValue)         if (newValue === data.msg) {           return         }         data.msg = newValue         // 数据更改,更新 DOM 的值         document.querySelector('#app').textContent = data.msg       }     })     // 测试     vm.msg = 'Hello World'     console.log(vm.msg) </script>

你没有看错,加上正文,一共 36行代码,这就是 Vue2.x 对响应式实现的整个流程;
持续实现多个数据的响应式

<body>  <div id="app">    hello  </div>  <script>    // 模仿 Vue 中的 data 选项    let data = {      msg: 'hello',      count: 10    }    // 模仿 Vue 的实例    let vm = {}    proxyData(data)    function proxyData(data) {      // 遍历 data 对象的所有属性      Object.keys(data).forEach(key => {        // 把 data 中的属性,转换成 vm 的 setter/setter        Object.defineProperty(vm, key, {          enumerable: true,          configurable: true,          get () {            console.log('get: ', key, data[key])            return data[key]          },          set (newValue) {            console.log('set: ', key, newValue)            if (newValue === data[key]) {              return            }            data[key] = newValue            // 数据更改,更新 DOM 的值            document.querySelector('#app').textContent = data[key]          }        })      })    }    // 测试    vm.msg = 'Hello World'    console.log(vm.msg)  </script></body>

下面的代码只是模仿了 响应式 的原理,但Vue在实现中,必定不会那么简略,接下来,咱们看一下源码呀……

Vue2 源码解读

首先找到响应式代码的解决地位:

要害地位作用源码地位
function Vue () {}Vue 构造函数core/instance/index.js:8
Vue.prototype._init初始化组件实例对象core/instance/init.js:16
initState初始化组件状态相干成员core/instance/state.js:48
initData初始化用户传入的 data 数据core/instance/state.js:112
observe察看 datacore/observer/index.js:110
要害地位作用源码地位
observe察看 datacore/observer/index.js:110
class ObserverObserver 逻辑core/observer/index.js:37
walk遍历对象成员别离解决core/observer/index.js:64
defineReactive为组件实例定义响应式数据core/observer/index.js:135
Object.defineProperty拦挡数据的拜访和批改core/observer/index.js:157

看完Vue2.x 响应式的代码,咱们再回过头来思考最开始的问题,为什么 Vue3.0 要重写响应式零碎 ?

为什么重写?如果之前好好的,重写就没有意义,那之前存在什么问题,换句话问就是 defineProperty 有什么问题?

Object.defineProperty 的问题

其实, defineProperty 的问题,在Vue2.x 的手册中,曾经说过了;“哎,很多人就是不看文档啊”
https://cn.vuejs.org/v2/guide/reactivity.html#%E5%AF%B9%E4%BA%8E%E6%95%B0%E7%BB%84
上面别离应用 Vue2 和 Vue3 实现了一个小性能,代码截然不同,性能当然也一样,然而,在 Vue2 中就会有Bug,而运行在vue3中的,则没有任何问题;

Vue2:

Vue3:

其外围点在于 defineProperty 不能很好的实现对数组下标的监控,而在 Vue2 的实现代码中,没有更好的计划对此进行改善,尤大索性间接放弃了实现;对于这个问题,尤大也在 github 做过回应,截个图给大家看看;

那么,Vue 目前还没有解决的问题,Vue3中显然是曾经解决的了,问题是,Vue3 是如何解决的呢?后面在 Vue3 的代码中咱们应用的是传统的 Options Api 来实现的数据响应式, 而在 Vue3 中全新的 Composition Api 也实现了响应式零碎,咱们先来感受一下 Composition Api 的根底用法

Composition API 的响应式零碎

ref 响应式

reactive 响应式

Vue3 中的响应式是如何实现的呢?关键点在于Proxy 函数;

Proxy 实现原理

应用 Proxy 实现的响应式代码,要比应用 defineProperty 的代码简略得多,因为 Proxy 人造的可能对整个对象做监听,而不须要对数据行遍历后做监听,同时也就解决了数组下标的问题;
咱们来一段模仿代码看一下:

<div id="app">    hello  </div>  <script>    // 模仿 Vue 中的 data 选项    let data = {      msg: 'hello',      count: 0    }    // 模仿 Vue 实例    const vm = new Proxy(data, {      // 执行代理行为的函数      // 当拜访 vm 的成员会执行      get (target, key) {        console.log('get, key: ', key, target[key])        return target[key]      },      // 当设置 vm 的成员会执行      set (target, key, newValue) {        console.log('set, key: ', key, newValue)        if (target[key] === newValue) {          return        }        target[key] = newValue        document.querySelector('#app').textContent = target[key]      }    })    // 测试    vm.msg = 'Hello World'    console.log(vm.msg)  </script>