最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 为什么 Vue3.0 要重写响应式系统

    正文概述 掘金(西岭老湿)   2021-01-26   402

    面试的时候经常被问到 响应式 相关的内容,而Vue3.0 更新后,面试官又有了新的武器; 面试官:为什么 Vue3.0 要重写响应式系统?

    懵逼树上懵逼果,懵逼树下你和我,面试官在问什么,我该怎么回答,完全不知道怎么回事; 有些经验的小伙伴可能会从解释 Proxy 的好处开始简单聊一下,比如: Proxy 是直接代理对象,而不是劫持对象的属性;更好的数组监控;这样的回答,勉强算是合格吧

    那到底应该怎么答呢?

    面试官背后的出题逻辑

    别急,咱们先整理一下思路,孙子兵法有云:“知己知彼,百战不殆”;面试就像打仗,你来我往,所以我们需要换位思考,想一想,为什么面试官会问这样一个问题?面试官想从这个问题里得到什么回答?这个问题可以考察哪些技术点? 想清楚这个问题,再回到自己身上,这些技术点,你都掌握了吗? 说得直白一点,面试就像考试,你需要先 读题、审题才能答好这道题; 为什么很多人认为 “面试造火箭,工作拧螺丝”?因为没有换位思考,没有想清楚面试题背后的逻辑;

    那我们想清楚这个逻辑之后,需要我们做的就是提取技术点,整理思路,做出对应解答;当然,前提是你需要具备这些技术能力 那么接下来,我就尝试拆解一下这个面试题了,提取其中的知识点。 对于你来说,就是要看看这些知识点,你都掌握了多少?

    为什么 Vue3.0 要重写响应式系统 ?

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

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

    其实基于这个面试题,背后还有很多技术点,上面这些,是与当前题目有直接关系的,实际面试中,很有可能基于这些技术点,在进行深入交流,这里就不扩展了,你能把现在这些问题理清楚,就算赚到了;

    Vue2.x 响应式

    其实关于这一点,在Vue 的官方文档中,早已经有过说明了,而且说得非常详细;官方文档:https://cn.vuejs.org/v2/guide/reactivity.html

    为什么 Vue3.0 要重写响应式系统

    当你把一个普通的 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,从而使它关联的组件重新渲染。
    

    我们使用官方给的一张图示,来梳理整个流程;

    为什么 Vue3.0 要重写响应式系统

    我们先来看一段代码 为什么 Vue3.0 要重写响应式系统

    响应式原理

    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来进行派发更新; 为什么 Vue3.0 要重写响应式系统

    虽然流程理清楚了,但是总感觉少点什么,怎么才能更通透呢?我们用代码来模拟整个的实现过程;

    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 源码解读

    首先找到响应式代码的处理位置:

    为什么 Vue3.0 要重写响应式系统

    为什么 Vue3.0 要重写响应式系统

    看完Vue2.x 响应式的代码,我们再回过头来思考最开始的问题,为什么 Vue3.0 要重写响应式系统 ? 为什么重写?如果之前好好的,重写就没有意义,那之前存在什么问题,换句话问就是 defineProperty 有什么问题?

    Object.defineProperty 的问题

    其实, defineProperty 的问题,在Vue2.x 的手册中,已经说过了;“哎,很多人就是不看文档啊” cn.vuejs.org/v2/guide/re… 下面分别使用 Vue2 和 Vue3 实现了一个小功能,代码一模一样,功能当然也一样,但是,在 Vue2 中就会有Bug,而运行在vue3中的,则没有任何问题;

    Vue2:

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <p v-for="(v, k) in users">
          {{ v.names }}
        </p>
        <button @click="changes">更新</button>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          users: [
            { id: 1, names: "路飞-v2" },
            { id: 2, names: "鸣人-v2" },
          ],
        };
      },
    
      methods: {
        changes() {
          // this.users[0] = {id:'0',names:'liuneng'}
          // this.users[1].names = 'lnsdsdfg'
          this.users[1] = { id: "1", names: "刘能-v2" };
        },
      },
    };
    </script>
    
    <style lang="stylus" scoped></style>
    

    Vue3:

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <p v-for="(v, k) in users">
          {{ v.names }}
        </p>
    
        <button @click="changes">更新</button>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          users: [
            { id: 1, names: "路飞-v3" },
            { id: 2, names: "鸣人-v3" },
          ],
        };
      },
    
      methods: {
        changes() {
          // this.users[0] = {id:'0',names:'liuneng'}
          // this.users[1].names = 'lnsdsdfg'
          this.users[1] = { id: "1", names: "刘能-v3" };
        },
      },
    };
    </script>
    

    其核心点在于 defineProperty 不能很好的实现对数组下标的监控,而在 Vue2 的实现代码中,没有更好的方案对此进行改善,尤大索性直接放弃了实现;关于这个问题,尤大也在 github 做过回应,截个图给大家看看; 为什么 Vue3.0 要重写响应式系统

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

    Composition API 的响应式系统

    ref 响应式

    <template>
      <!-- 不需要.value -->
      <button @click="addNu"> Composition API: {{nu}}</button>
    </template>
    
    <script>
    // 引入 ref 
    import {ref} from "vue"
    export default {
      setup() {
        // 定义 ref 响应式数据
        const nu = ref(1);
        // 定义函数
        function addNu(){
          nu.value++;
        }
    
        // 将数据和方法返回,即可在模板中直接使用
        return {
          nu,
          addNu
        };
      },
    };
    </script>
    

    reactive 响应式

    <template>
      <!-- 不需要.value -->
      <button @click="addNu"> Composition API: {{nu}}</button>
      <!-- reactive 响应式数据 -->
      <h2>{{revData.name}}</h2>
      <h3 @click="ageAdd">年龄:{{revData.age}}</h3>
      <p v-for="(v,k) in revData.skill"> {{v}} </p> 
    </template>
    
    <script>
    // 引入 ref 
    import {reactive, ref} from "vue"
    export default {
      setup() {
        // 定义 ref 响应式数据
        const nu = ref(1);
    
        // reactive 响应式数据
        const revData = reactive({
          name: '路飞',
          age: 22,
          skill:['橡胶机关枪','吃鸡腿'],
        })
    
        function ageAdd(){
          revData.age++
        }
    
        // 定义函数
        function addNu(){
          nu.value++;
        }
    
        // 将数据和方法返回,即可在模板中直接使用
        return {
          nu,
          addNu,
          revData,
          ageAdd
        };
      },
    };
    </script>
    

    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>
    

    起源地下载网 » 为什么 Vue3.0 要重写响应式系统

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元