当前位置:   article > 正文

Vue2源码解析(2)——响应式详解_vue2响应式源码

vue2响应式源码

一:前言

        众所周知,Vue框架是一个渐进式的响应式框架,它可以做到数据的实时监听以及动态修改,而在vue的底层中究竟是如何实现的呢?在本篇文章中将会一一介绍如何监听数据的变化,以及两层数据劫持等代码实现。本文是基于(1)来接着写的,有时间的小伙伴可以看一下(1)哦,当然不看也不会有太大的影响。链接如下

        vue2源码解析(1)——rollup详解-CSDN博客

二:响应式源码 

1、项目目录

        以下是项目的目录,其中dist文件夹是打包后生成的,index.html是新增的,也是实际的主页。

        src文件夹下,index.js是打包后的入口文件,即去使用new Vue()时运行index.js文件,其余的是响应式的一些监听方法。接下来会对这些一一讲解。

2、src下的index.js文件

         该文件是使用new Vue()时所调用的文件,这里主要是进行一个初始化的方法,比如挂载一些用户选项等,目前这里写的是一个初始化data的方法,代码如下。

  1. import { initMixin } from "./init"
  2. function Vue(options){ // options就是用户的选项
  3. // debugger//可以调试代码
  4. this._init(options)//默认调用_init()
  5. }
  6. initMixin(Vue)//扩展了init方法
  7. export default Vue

3、src下的init.js文件

        该文件是一个initMixin方法,用于进一步封装初始化的方法,为了方便理解,这里我们将this使用vm来代替,并且把用户的选项挂载到vm实例上,然后进行一个状态的初始化。

  1. import { initState } from "./state"
  2. //这是个初始化的文件,通过这方法传入Vue然后进行初始化操作,方便解耦
  3. export function initMixin(Vue){//给Vue增加init方法的
  4. Vue.prototype._init = function(options){ // 用于初始化操作
  5. // 在vue中,vm.$options 就是获取用户的配置
  6. // 很多api都是这样,使用$XXX来写,所以$开头都是vue自己的属性
  7. const vm = this
  8. vm.$options = options//将用户的选项挂载到实例上,即这些是data里的数据
  9. //初始化状态
  10. initState(vm)
  11. }
  12. }

4、src下的state.js文件

         这个文件是用于初始化状态,首先进入initState()方法,判断用户的data里是否有数据,有数据的时候才会进行初始化。

        而后在initData()中,由于Vue2的data可能是返回一个对象,也可能是返回一个方法,这是无法确定的,所以先进行数据的处理,将数据处理成一个对象。并且在这个方法中我们进行第一次劫持,将字段添加,使用户可以直接this.name调用而不需要this._data.name。而后进行数据的监听。

没有进行劫持的时候,调用data里的name实例,需要:this._data.name

进行第一次劫持后,只需要this.name就可以调用了

  1. import { observe } from "./observe/index";
  2. export function initState(vm){
  3. const opts = vm.$options;//获取所有的选项
  4. // if(opts.props){
  5. // initProps()
  6. // }
  7. if(opts.data){
  8. initData(vm)
  9. }
  10. }
  11. function proxy(vm,target,key){
  12. Object.defineProperty(vm,key,{ // vm.name
  13. get(){
  14. return vm[target][key] // vm._data.name
  15. },
  16. set(newValue){
  17. vm[target][key] = newValue
  18. }
  19. })
  20. }
  21. function initData(vm){
  22. // debugger
  23. //假如用户输入的是函数,这里就是判断后变成对象
  24. let data = vm.$options.data;//data可能是函数,也可能是对象
  25. data = typeof data === 'function' ? data.call(vm) : data//这样就处理了data数据
  26. // console.log(data)
  27. //data是用户的对象,将放回的对象放在了实例的_data上,并且进行观测
  28. vm._data = data
  29. //对数据进行劫持 vue2里采用了一个api defineProperty
  30. observe(data)
  31. // 将vm._data用vm来代理
  32. for(let key in data){
  33. proxy(vm,'_data',key)
  34. }
  35. }

5、src下observe的index.js文件 

         该文件主要是对数据进行劫持,给对象里的属性添加get和set方法,并且判断是否已经进行过劫持。实现思路如下:

        首先进入observe()方法,进行一系列判断后,如果没有被劫持过,那么我们去new一个Observe()类的对象,这时候会调用这个类里面的构造器,其中传入的参数data就是我们在state.js中处理过一次的对象。

        在这个构造器里,我们先将他本身挂载到一个不可枚举的__ob__实例上,然后判断这个数据是否为数组,这时候有两种情况如下:

        1. 是数组的时候,我们需要重写常用的七个变异方法,这里重写的代码会放在下面第六条。重写完方法后,由于数组中是可以存放对象的,那么我们也要将数组里的对象劫持,所以定义一个for循环,将数组里的数据判断是不是对象,如果是对象的话重新进行劫持。

        注意:因为在oberve()方法中进行过判断,所以当数组里存放的不是对象的时候会return出去。

        2.是对象的时候,这种情况就比较简单了,只需要进行数据劫持,赋予get和set方法就好了,当然同理对象中可能嵌套对象,所以我们也是进行for循环来判断。

        注意:由于这里是“重新定义属性”,所以在性能方面就有落后,因此Vue2的性能是不如Vue3的。

  1. import { newArrayProto } from "./array";
  2. class Observer {
  3. constructor(data) {
  4. // Object.defineProperty只能劫持已存在的属性,后面增加或者删除的属实是无法劫持的(vue2里会为此单独写一些api,比如$set $delete)
  5. Object.defineProperty(data,'__ob__',{
  6. value:this,
  7. enumerable:false // 将__ob__变成不可枚举(循环的时候无法获取)
  8. });
  9. // data.__ob__ = this; // 给数据加了一个表示,如果数据上有__ob__,则说明该数据被检测过 将Observe的实例放在data的对象上
  10. if (Array.isArray(data)) {//判断,如果是一个数组,就不需要劫持了
  11. //这里重写数组中的七个变异方法,这几个方法是可以修改数组本身的
  12. data.__proto__ = newArrayProto // 需要保留数组原有的特性,并且可以重写部分方法
  13. this.observerArray(data) // 如果数组中放的是对象,可以监听到数据的变化
  14. } else {
  15. this.walk(data);
  16. }
  17. }
  18. walk(data) { // 让这个data循环对象,让属性依次劫持
  19. // '重新定义'属性,因为是重新定义,所以性能受到影响
  20. Object.keys(data).forEach(key => defineReactive(data, key, data[key]));
  21. }
  22. observerArray(data) { // 观测数组
  23. data.forEach(item => observe(item))
  24. }
  25. }
  26. // 第一个参数,要定义的是谁,key值,value值
  27. export function defineReactive(target, key, value) { // 闭包 属性劫持
  28. observe(value) // 采用递归 如果这个值是对象,会递归再次进行一次劫持
  29. Object.defineProperty(target, key, {
  30. get() { // 取值的时候执行get
  31. return value
  32. },
  33. set(newValue) { // 修改的时候执行set
  34. // 判断相同的时候,就不进行操作,不同的时候,进行赋值
  35. if (newValue === value) return
  36. value = newValue
  37. }
  38. })
  39. }
  40. export function observe(data) {
  41. //对这个对象进行劫持
  42. //先判断是不是对象
  43. if (typeof data !== 'object' || data == null) {
  44. return;//只对对象进行劫持
  45. }
  46. if(data.__ob__ instanceof Observer){ // 如果有这个实例,说明已经被代理过了
  47. return data.__ob__
  48. }
  49. //如果一个对象被劫持过了,那就不惜要再次劫持了(要判断是否被劫持过,可以增添一个实例来判断是否被劫持)
  50. return new Observer(data);
  51. }

6、src下observe的array.js文件

         这个文件是重写了数组的变异方法,所有能够修改原数组的方法都是变异方法,下面一共是七个,因为不能直接覆盖数组的原型,所以我们采取新数组的方式进行重写。

  1. //对数组中的部分方法进行重写
  2. let oldArrayProto = Array.prototype; // 获取数组的原型
  3. // newArrayProto.__proto__ = oldArrayProto
  4. export let newArrayProto = Object.create(oldArrayProto)
  5. let methods = [//找到所有的变异(能修改原数组的方法都是变异方法)方法
  6. 'push',
  7. 'pop',
  8. 'shift',//向后追加
  9. 'unshift',
  10. 'reverse',//反序
  11. 'sort',//排序
  12. 'splice'//删除
  13. ] // concat slice都不会改变原来的数组
  14. methods.forEach(method=>{
  15. // arr.push(1,2,3)
  16. newArrayProto[method] = function(...args){//这里重写了数组的方法
  17. // 内部调用原来的方法,这叫做函数的劫持 ,切片变成
  18. // push.call(arr)
  19. const result = oldArrayProto[method].call(this,...args)//这里的this就是19行的arr,
  20. // console.log(method)
  21. //需要对新增的数据再次进行劫持
  22. let inserted
  23. let ob = this.__ob__;
  24. switch(method){
  25. case 'push':
  26. case 'unshift':
  27. inserted = args;
  28. break;
  29. case 'splice':
  30. inserted = args.slice(2);
  31. break
  32. default:
  33. break;
  34. }
  35. console.log(inserted) // 新增的内容
  36. if(inserted){
  37. // 对新增的数组再次进行观测
  38. ob.observerArray(inserted);
  39. }
  40. return result
  41. }
  42. })

三:总结

        vue2的响应式可以说是入门源码的第一步,所以是一个十分重要的知识点,学习源码可以让我们对编写代码有一个更加清晰的了解和认知,同时在开发过程中遇到了bug能够更容易发现产生的原因。好啦本文大概就这些内容,如果有写的不好的地方希望能够指正出来,祝大家能够写出自己满意的代码~需要代码的可以私聊,暂时还没有上传git。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/248166
推荐阅读
相关标签
  

闽ICP备14008679号