侧边栏壁纸
博主头像
woku博主等级

成功的路上并不拥挤

  • 累计撰写 50 篇文章
  • 累计创建 13 个标签
  • 累计收到 3 条评论

vue数据劫持

woku
2022-03-11 / 0 评论 / 0 点赞 / 159 阅读 / 5,338 字

概述

vue官网对数据说明如下:

当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

一句话总结:vue实例里面的数据放在data里,为了方便可以直接通过实例来访问这个数据

var vm = new Vue({
  data: {
    a: 1,
    b: 2
  }
})

// 直接获取实例上的property
// 返回data中对应的字段
vm.a == vm.data.a

// 改变实例上的property
// data中对应的字段也会改变
vm.a = 10
vm.data.a  => 10

vm.data.b = 20
vm.b  => 20

initData

源码文件目录结构

创建Vue实例

import Vue from './Vue'
let vm = new Vue({
    data() {
        return {
            name: 'wyg',
            infoObj: {
                a: 1,
                b: 2
            },
            arr: [1, 2, 3, {
                x: 10,
                y: 20
            }],
            numberA: 10,
            numberB: 20,
        }
    }
})
console.log(vm)

Vue作为构造函数被调用,咱就创建这样一个构造函数

init数据

Vue/index.js

首先,调用Vue原型上的init方法进行初始化

将options挂载到vm上,后面可以直接在vm上获取options。

initState:初始化数据(包括data, computed, watch)

import { initState } from './init'
var Vue = (function() {
    function Vue(options) {
        this._init(options)
    }
    Vue.prototype._init = function(options) {
        var vm = this
        vm.$options = options
        initState(vm)
    }
    return Vue
})()

export default Vue

vue/init.js

initState初始化数据方法,如果$options里面有data数据,那么就initData进行数据代理

initData做的事情

  • 将数据挂载到vm._data上,避免直接使用用户传进来的$options.data

  • 进行数据劫持,对访问vm.a 进行拦截返回vm._data.a, 设置vm.a=‘xxx’进行拦截,让vm._data.a = 'xxx'

import { proxyData } from './proxy'
function initState(vm) {
    var data = vm.$options.data
    if (data) {
        initData(vm)
    }
}
function initData(vm) {
    var data = vm.$options.data
    vm._data = data = typeof data === 'function' ? data.call(vm) : data || {}
    for (var key in data) {
        proxyData(vm, '_data', key)
    }
}
export {
    initState
}

Vue/proxy.js

function proxyData(vm, target, key) {
    Object.defineProperty(vm, key, {
        // 获得这个实例上的 property,返回源数据中对应的字段
        get() {
            return vm[target][key]
        },
        // 设置 property 也会影响到原始数据
        set(newVal) {
            vm[target][key] = newVal
        }
    })
}
export {
    proxyData
}

observe

概述

observe是观察

代码层面就是去观察vm._data中数据的变化。当数据变化,视图将会产生“响应”

观察对象

在数据初始化时,就要观察vm._data。

我们在使用实例去获取某个数据时或者设置某个数据时,并不是纯粹的操作数据。在这个过程中,我们需要做一些其他的处理,比如更新视图,深度观察等操作。

vue/init.js

import { proxyData } from './proxy'
import observe from './observe'
function initState(vm) {
    var data = vm.$options.data
    if (data) {
        initData(vm)
    }
}
function initData(vm) {
    var data = vm.$options.data
    vm._data = data = typeof data === 'function' ? data.call(vm) : data || {}
    for (var key in data) {
        proxyData(vm, '_data', key)
    }
    // 对数据进行观察
    observe(vm._data)
}
export {
    initState
}

vue/observe.js

在这个文件的observe方法中,我们要判断要观察的data是不是一个对象,如果不是,则不需要进行观察

是一个对象,我们就有请观察者。

import Observer from './Observer'
function observe(data) {
    if (typeof data !== 'object' || data == null) return
    return new Observer(data)
}
export default observe

vue/Observer.js

要观察的数据:typeof data === 'object' ,可能会是对象,也可能是数组

先处理对象:

对象我们可以直接使用Object.defineProperty进行数据劫持

import defineReactiveData from './reactive'
function Observer(data) {
    if (Array.isArray(data)) {

    } else {
        this.walk(data)
    }
}
Observer.prototype.walk = function(data) {
    var keys = Object.keys(data)
    for (var i = 0; i < keys.length; i++) {
       // 对对象中的每一个属性,都要进行数据劫持
        var key = keys[i],
            value = data[key]
        defineReactiveData(data, key, value)
    }
}
export default Observer

vue/reactive.js

function defineReactiveData(data, key, value) {
    Object.defineProperty(data, key, {
        get() {
            console.log('响应式获取值', value)
            return value
        },
        set(newVal) {
            if (newVal === value) return
            console.log('响应式设置值', newVal)
            value = newVal
        }
    })
}
export default defineReactiveData

可以看到,_data中的每一个属性都有getter和setter,对数据的获取和设置都能劫持到。

深度监听

  • 现象
var data = {
  name: 'wyg',
  studyInfo: {
    js: 99,
    vue: 100,
    webpack: {
      a: 1,
      b: 2
    }
  }
}

vue实例数据如上,使用observe监听的时候给第一层的数据进行了劫持。

那如果第一层的数据的值又是一个对象呢,是不是也需要进行监听,然后给值的每一个属性都添加getter和setter。

上面的studyInfo属性的改变能劫持到,如果studyInfo下面的每一个属性改变,我们也需要劫持到。

  • 实现

vue/reactive.js

import observe from './observe'
function defineReactiveData(data, key, value) {
    // 对值进行深度监听,如果值是对象,递归。如果不是对象,observe函数直接返回
    observe(value)
    Object.defineProperty(data, key, {
        get() {
            console.log('响应式获取值', value)
            return value
        },
        set(newVal) {
            if (newVal === value) return
            console.log('响应式设置值', newVal)
            // 如果新设置的这个值也是一个对象,也要递归observe
            observe(newVal)
            value = newVal
        }
    })
}
export default defineReactiveData

观察数组

  • 现象

    Object.defineProperty对数组的处理方式不友好,只用来处理对象

var data = {
 arr: [1, 2, 3, {
   name: 'wyg',
   age: 26
 }]
}
var arr = [1, 2, 3]
arr.concat(1, 2)
console.log(arr)

arr调用concat方法,会返回一个新数组,而不是直接改变原数组,所以打印arr的时候还是[1, 2, 3]

可以将返回的新数组重新赋值给arr

var arr = [1, 2, 3]
arr = arr.concat(1, 2)
console.log(arr)

像concat这种方法就是会返回新数组。类似的还有slice

对应这种方法,arr = arr.concat() 这种操作会触发setter, 可以监听到

对于那些直接改变原数组的方法,如push、pop、sort这些方法,我们不需要重新赋值,那这种怎么实现监听呢?

  • 实现

    重新push,pop,sort这些方法,自己有这些方法就调用自己的了,然后在自己定义的方法里面就能做一些额外的操作。

    vue/config.js

const ARRAY_METHODS = [
   'push',
   'pop',
   'unshift',
   'shift',
   'splice',
   'sort',
   'reverse'
]
export {
   ARRAY_METHODS
}

以上方法会直接改变原数组

vue/array.js

import { ARRAY_METHODS }  from './config'
import observeArr from './observeArr'
const originArr = Array.prototype
const arrnMethods = Object.create(originArr)

ARRAY_METHODS.map(function(m) {
   arrnMethods[m] = function() {
       var args = Array.from(arguments)
       // 还是调用js原生的那些方法
       originArr[m].apply(this, args)
       // 对于数组新增的元素,我们也需要进行observe监听 
       var newArr
       switch (m) {
           case 'push':
           case 'unshift':
               newArr = args
               break;
           case 'splice':
               newArr = args.slice(2);
               break;
           default:
               break;
       }
       console.log('我在调用自己的方法', Array.from(arguments))
       // observeArr遍历数组每一项,进行监听
       newArr && observeArr(newArr)
   }
})

export {
   arrnMethods
}

vue/observeArr.js

import observe from "./observe"
function observeArr(arr) {
   for (var i = 0; i < arr.length; i++) {
       observe(arr[i])
   }
}

export default observeArr

array.js中导出的arrnMethods直接作为当前data的原型,如果调用data数据上的push、pop之类的方法就能被监测到。

vue/Observer.js

在之前处理对象的逻辑中,新增判断数组,然后做对数组监听的相关逻辑

if (Array.isArray(data)) {
data.proto = arrnMethods
observeArr(data)
} else {
this.walk(data)
}

import defineReactiveData from './reactive'
import { arrnMethods } from './array'
import observeArr from './observeArr'
function Observer(data) {
   if (Array.isArray(data)) {
       data.__proto__ = arrnMethods
       observeArr(data)
   } else {
       this.walk(data)
   }
}
Observer.prototype.walk = function(data) {
   var keys = Object.keys(data)
   for (var i = 0; i < keys.length; i++) {
       var key = keys[i],
           value = data[key]
       defineReactiveData(data, key, value)
   }
}
export default Observer
0

评论区