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响应式数据的资料请关注脚本之家其它相关文章!

相关文章

  • 利用Vue3实现可复制表格的方法详解

    利用Vue3实现可复制表格的方法详解

    表格是前端非常常用的一个控件,本文主要为大家介绍了Vue3如何实现一个简易的可以复制的表格,文中的示例代码讲解详细,需要的可以参考一下
    2022-12-12
  • Vue操作数组的几种常用方法小结

    Vue操作数组的几种常用方法小结

    本文主要介绍了Vue操作数组的几种常用方法小结,主要包括map、filter、forEach、find 和 findIndex 、some 和 every、includes、Array.from这几种方法,感兴趣的可以了解一下
    2023-09-09
  • 解决axios:"timeout of 5000ms exceeded"超时的问题

    解决axios:"timeout of 5000ms exceeded"

    这篇文章主要介绍了解决axios:"timeout of 5000ms exceeded"超时的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • 结合el-upload组件实现大文件分片上传功能

    结合el-upload组件实现大文件分片上传功能

    Element UI的el-upload上传组件相信各位小伙伴都已经非常熟悉,最近接了一个新需求,要求在el-upload组件基础上实现分片上传功能,即小于等于5M文件正常上传,大于5M文件切成5M每片上传,那么这个功能怎么实现呢?一起看看吧
    2022-09-09
  • 如何使用Vue的思想封装一个Storage

    如何使用Vue的思想封装一个Storage

    作为Web Storage API的接口,Storage 提供了访问特定域名下的会话存储或本地存储的功能,例如可以添加、修改或删除存储的数据项,这篇文章主要给大家介绍了关于如何使用Vue的思想封装一个Storage的相关资料,需要的朋友可以参考下
    2021-08-08
  • 如何巧用Vue.extend继承组件实现el-table双击可编辑(不使用v-if、v-else)

    如何巧用Vue.extend继承组件实现el-table双击可编辑(不使用v-if、v-else)

    这篇文章主要给大家介绍了关于如何巧用Vue.extend继承组件实现el-table双击可编辑的相关资料,不使用v-if、v-else,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-06-06
  • vue使用cesium创建数据白模方式

    vue使用cesium创建数据白模方式

    这篇文章主要介绍了vue使用cesium创建数据白模方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • axios+vue请求时携带cookie的方法实例

    axios+vue请求时携带cookie的方法实例

    做项目时遇到一个需求,后端需要在接口请求时,对用户登陆状态进行判断,需要在请求时携带Cookie,下面这篇文章主要给大家介绍了关于axios+vue请求时携带cookie的相关资料,需要的朋友可以参考下
    2022-09-09
  • vue中如何引入jQuery和Bootstrap

    vue中如何引入jQuery和Bootstrap

    本篇文章主要介绍了vue中如何引入jQuery和Bootstrap,详细的介绍了引入jQuery和Bootstrap的方法,有兴趣的可以了解一下。
    2017-04-04
  • vue中的$emit 与$on父子组件与兄弟组件的之间通信方式

    vue中的$emit 与$on父子组件与兄弟组件的之间通信方式

    本文主要对vue 用$emit 与 $on 来进行组件之间的数据传输。重点给大家介绍vue中的$emit 与$on父子组件与兄弟组件的之间通信方式,感兴趣的朋友一起看看
    2018-05-05

最新评论