Vue源码学习defineProperty响应式数据原理实现

 更新时间:2022年09月06日 09:25:50   作者:i东东  
这篇文章主要为大家介绍了Vue源码学习defineProperty响应式数据原理实现,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

准备工作

接上文数据初始化完成之后,就可以对数据进行劫持。Vue2中对数据进行劫持采用了一个Api叫Object.defineProperty()

在这里需要提供一个方法去观测data变化,这个方法是一个核心模块(响应式模块),我们单独建一个文件夹来存放在/src/observe/index.js

// src/state.js
import { observe } from "./observe/index"
export function initState(vm){
    // 对数据需要进行劫持
    const opts = vm.$options //获取所有选项
    if (opts.data){
        initData(vm)
    }
}
function initData(vm){
    // 对数据进行代理
    let data = vm.$options.data
    // data可以是函数或者对象,根实例可以是对象,组件data必须是函数
    data = typeof data === 'function' ? data.call(vm) : data
    // 对数据进行劫持 Vue2采用的一个api object.defineproperty
    observe(data)
}
// src/observe/index.js
export  function observe(data){
  debugger
  console.log(data);
}

执行/dist/index.html,当控制台出可以输出{name: 'i东东', age: 18}说明前面的代码没有问题,接下来就可以开始下面的操作了。

第一步 对对象进行劫持

当拿到了data,就可以对data数据进行劫持,如果说他不是对象就不用劫持,所以还需要进行一个判断。

// src/observe/index.js
export  function observe(data){   // 对这个对象进行劫持
  if(typeof data !=='object'|| data == null){
    return // 只对对象进行劫持
  }
}

那么紧接着如何劫持这个对象呢?

如果一个对象被劫持过了,那么就不需要再次被劫持了(要判断一个对象是否被劫持过,可以增添一个实例,来判断是否被劫持过),所以在内部创造了一个类去观测数据,如果数据被观测过那他的实例就是这个类。

// src/observe/index.js
class Observer{
  constructor(data){ //所有数据
    this.walk(data) // 因为data是一个对象,所以就需要对data进行比遍历
  }
  walk(data){ // 循环对象 对属性依次劫持
     Object.keys(data).forEach(key=>defineReactive(data,key,data[key])) //重新定义属性
  }
}
export function defineReactive(target,key,value){ // 闭包  属性劫持
Object.defineProperty(target,key,{
  get(){ //取值的时候会执行get
    return value
  },
  set(newValue){ // 修改的时候执行set
    if(newValue === value) return
    value = newValue
  }
})
}
export  function observe(data){   // 对这个对象进行劫持
  if(typeof data !=='object'|| data == null){
    return // 只对对象进行劫持
  }
  return new Observer(data); // 对这个数据进行观测
}

因为要对每个属性进行劫持,但是Objece.defineProperty()只能劫持已经存在的属性,后增加的或者删除的是不知道的,(Vue2里面会为此单独写一些api 比如:setset setdelete),所以需要对data进行遍历 this.walk()对属性依次劫持,重新定义属性(性能会差,Vue3中proxy就会好很多),就可以调用defineReactive,因为这个方法可以单独去使用,所以直接导出。

完成之后执行index.html中console.log(vm),会发现vm上只有用户的选项,并没有刚才劫持过的属性,是因为在state.js中我们只是data传入了observe函数,所以就考虑,在vm上增加一个属性,叫_data,这样就相当于把_data对象放在了实例vm上,并且又把这个对象进行了观测,观测的时候依旧回去循环这个对象。

// src/state.js
function initData(vm){
    let data = vm.$options.data
    data = typeof data === 'function' ? data.call(vm) : data
    vm._data = data // 新增这一句
    observe(data)
}

这样再次输出,会发现控制台输出了_data,并且给age,name都增加上来get和set方法,现在说明这个事情就成了。

这个时候就可以通过vm._data.name进行取值

// dist/index.html
const vm = new Vue({
  data(){
    return {
      name:'i东东',
      age:18
    }
  }
})
vm._data.name = 'i东东修改'
console.log(vm._data.name); 
// 用户设置值了
// index.js:15 用户取值了
// index.html:29 i东东修改

第二步 修改取值方法

紧接着就会发现正常我们取值都是vm.name,但是上面的访问还是vm._data.name,所以下面需要将取值的方法进行一下优化。需要在state.js中将vm._data用vm代理。

// state.js
function proxy(vm,target,key){
    Object.defineProperty(vm,key,{
        get(){
            return vm[target][key]  // vm._data.name
        },
        set(newValue){
            vm[target][key] = newValue
        }
    })
}
function initData(vm){
    // 对数据进行代理
    let data = vm.$options.data
    data = typeof data === 'function' ? data.call(vm) : data
    vm._data = data
    observe(data)
    // 新增 将vm._data用vm代理
    for(let key in data){
        proxy(vm,'_data',key)
    }
}

这样在index.html中我们就可以用过vm.name重钢访问到数据,也可以通过vm.name = 'i东东修改'去设置值,虽然这样性能是不太好的,但是他用起来会很方便的。所以在这里面相当于代理了两次第一次把用户的数据进行了属性劫持,第二次就是proxy当取值和设置值的时候代理到某个人身上。

第三步 深度属性劫持

// index.html
const vm = new Vue({
  data(){
    return {
      name:'i东东',
      age:18,
      say:{
        hobby:'学习'
      }
    }
  }
})
console.log(vm);

假如说我再增加一个对象say,输出vm会发现hobby并没有被劫持,原因是因为我们只劫持了name、age、say三个属性,如果属性是个对象的话,我们就需要再次劫持。这样我们只需要在defineReactive()里面再次调用observe再次建立劫持,形成递归这样就可以完成对对象的深度属性劫持。

// src/observe/index.js
export function defineReactive(target,key,value){ // 闭包  属性劫持
  observe(value) // 新增 对所有的对象都进行属性接触
  Object.defineProperty(target,key,{
  get(){ //取值的时候会执行get
    console.log('用户取值了');
    return value
  },
  set(newValue){ // 修改的时候执行set
    console.log('用户设置值了');
    if(newValue === value) return
    value = newValue
  }
})
}

以上就是Vue源码学习defineProperty响应式数据原理实现的详细内容,更多关于Vue defineProperty响应式数据的资料请关注脚本之家其它相关文章!

相关文章

  • 关于SpringBoot与Vue交互跨域问题解决方案

    关于SpringBoot与Vue交互跨域问题解决方案

    最近在利用springboot+vue整合开发一个前后端分离的个人博客网站,所以这一篇总结一下在开发中遇到的一个问题,关于解决在使用vue和springboot在开发前后端分离的项目时,如何解决跨域问题。在这里分别分享两种方法,分别在前端vue中解决和在后台springboot中解决。
    2021-10-10
  • Vue.js之mixins混合组件详解

    Vue.js之mixins混合组件详解

    这篇文章主要介绍了Vue.js之mixins混合组件详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • vue3.0之Router的使用你了解吗

    vue3.0之Router的使用你了解吗

    这篇文章主要为大家详细介绍了vue3.0之Router的使用,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • vue+elementUI中使用Echarts之饼图问题

    vue+elementUI中使用Echarts之饼图问题

    这篇文章主要介绍了vue+elementUI中使用Echarts之饼图问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • Vue.js中用v-bind绑定class的注意事项

    Vue.js中用v-bind绑定class的注意事项

    关于数据绑定一个常见需求就是操作元素的class列表和它的内联样式。因为它们都是属性,我们可以用 v-bind 处理它们,但是使用v-bind绑定class的时候我们要有一些注意事项,下面这篇文章就给大家分享了下要注意的方面,希望能对大家有所帮助,下面来一起看看吧。
    2016-12-12
  • VUE3传值相关的6种方法总结

    VUE3传值相关的6种方法总结

    件间传参是vue开发过程中一个很常见的应用,对于我们后端开发来说,每次看到这种组件传参的代码就一头雾水,下面这篇文章主要给大家介绍了关于VUE3传值相关的6种方法,需要的朋友可以参考下
    2023-04-04
  • 使用Vue3+ts 开发ProTable源码教程示例

    使用Vue3+ts 开发ProTable源码教程示例

    这篇文章主要为大家介绍了使用Vue3+ts 开发ProTable源码示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • vue-editor-bridge报错的解决方案

    vue-editor-bridge报错的解决方案

    这篇文章主要介绍了vue-editor-bridge报错的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04
  • vue实现页面div盒子拖拽排序功能

    vue实现页面div盒子拖拽排序功能

    本文主要介绍了vue实现页面div盒子拖拽排序功能,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • 带你熟练掌握Vue3之Pinia状态管理

    带你熟练掌握Vue3之Pinia状态管理

    pinia是vue3官方的状态管理工具,当然vue2也可以用,vue2中的状态管理工具是vuex,vue3中不再使用vuex,推荐使用的是pinia,和vuex差不多,但比vuex更方便、更强、更好,下面这篇文章主要给大家介绍了关于Vue3之Pinia状态管理的相关资料,需要的朋友可以参考下
    2022-11-11

最新评论