欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue源码 - 2.数据劫持

程序员文章站 2022-07-03 18:20:25
新建src/index.jsimport { initMixin } from "./init"function Vue(options) { // 运行 Vue 的初始化操作 this._init(options)}// 通过引入文件的方式给 Vue 原型上添加方法initMixin(Vue)export default Vue新建src/init.jsimport { initState } from './state';// 在原型上添加一个 init 的方....
  1. 新建src/index.js
import { initMixin } from "./init"
function Vue(options) {
    // 运行 Vue 的初始化操作
    this._init(options)
}
// 通过引入文件的方式给 Vue 原型上添加方法
initMixin(Vue)
export default Vue
  1. 新建src/init.js
import { initState } from './state';
// 在原型上添加一个 init 的方法
export function initMixin(Vue) {
    // 初始化方法
    Vue.prototype._init = function(options) {
        // 数据的劫持
        const vm = this; // vue 中使用的 this.$options 指的就是用户传递的属性
        vm.$options = options
        // 初始化状态
        initState(vm)
    }
}
  1. 新建 src/state.js
import { observe } from "./observer/index"
export const initState = function(vm) {
    let opts = vm.$options
    console.log(opts);
    // vue 数据的来源 属性 、 方法 、 数据 、 计算属性 、 watch
    if(opts.props) {
        initProps(vm)
    }
    if(opts.methods) {
        initMethods(vm)
    }
    if(opts.data) {
        initData(vm)
    }
    if(opts.computed) {
        initComputed(vm)
    }
    if(opts.watch) {
        initWatch(vm)
    }
}
function initData(vm) {
    // 数据的初始化工作
    let data = vm.$options.data // 用户传递的 data
    /**
     * 1. data._data 是希望用户也能拿到 data 属性
     * 2. 如果 data 返回的是一个方法的话立即执行 data()
     */
    data = vm._data = typeof data == 'function' ? data.call(vm) : data
    // 对象劫持 用户改变了数据,我希望可以得到通知 - 刷新页面
    // MVVM 模式 数据改变可以驱动视图的变化
    // 核心方法 object.defineProperty() 给属性增加 get 、set 方法
    observe(data) // 响应式原理
}
  1. 新建 src/observer/index.js
/**
 * 1. 把 data 中的数据,都使用 Object.defineProperty 重新定义 es5 的方法
 * 2. Object.defineProperty 不能兼容 ie8 及其以下,所以 vue2 无法兼容 ie8 版本 
 */
import { isObject, def } from '../utils/index';
import { arrayMethods } from './array.js'
class Observer {
    constructor(value) {
        // vue 如果数据的层次过多,需要递归的去解析对象中的属性,依次增加 set 和 get 方法
        // 所以 vue3.0 中使用 proxy 方法,proxy不需要递归,也不需要给属性添加 get 和 set 方法,所以性能提升2-3倍
        def(value, '__ob__', this)
        if(Array.isArray(value)) {
            // 如果是数组的话并不会对索引进行观测,因为会导致性能问题
            // 前端开发很少去操作索引 push shift unshift
            // 如果数组里放的是对象我再监控
            value.__proto__ = arrayMethods // 重写数组的原型方法
            this.observerArray(value)
        } else {
            this.walk(value)
        }
    }
    observerArray(data) {
        data.forEach(item => {
            observe(item)
        })
    }
    walk(data) {
        let keys = Object.keys(data)
        keys.forEach(key => {
            defineReactive(data, key, data[key]) // 定义响应式数据
        });
    }
}

function defineReactive(data, key, value) {
    observe(value) // 递归实现深度检测
    Object.defineProperty(data, key, {
        get() { // 获取值的时候做一些操作
            return value
        },
        set(newValue) { // 设置值的时候也可以做一些操作
            if(newValue === value) return
            observe(newValue) // 继续劫持用户设置的值,因为有可能用户设置的值是一个对象
            value = newValue
        }
    })
}

export function observe(data) {
    let isObj = isObject(data)
    if(!isObj) {
        return
    }
    // 如果是对象的话就需要 观测数据
    return new Observer(data)
}
  1. 新建 src/utils/index.js
// 判断是否是对象
export function isObject(data) {
    return typeof data === 'object' && data !== null
}
// 不可枚举的响应式方法
export function def(data, key, value) {
    Object.defineProperty(data, key, {
        enumerable: false,
        configurable: false,
        value
    })
}

本文地址:https://blog.csdn.net/qq_35432512/article/details/107320531

相关标签: Vue