vue的data属性数据劫持思路

vue的data属性数据劫持思路

1、 一开始初始化

使用入口

<body>
    <!-- 'hello' + arr + 'world' -->
    <div id="app" a=1 style="color:red;background:blue">
        hello {
     {
     arr}} world

    </div>
    
    <script src="dist/vue.js"></script>

    <script>
        // viewModel 数据模型
        // 典型的MVVM  view   vm    model

        let vm = new Vue({
     
            data() {
     
                //this = vm
                return {
     
                    arr: {
     name:'zf'}
                }
            }
        });
        vm.$mount('#app')

        // 数组没有监控 索引的变化 , 但是索引对应的内容是对象类型,需要被监控 Object.freeze
        // 用户很少通过索引操作数组 arr[82] = 1000 内部就想到不对索引进行拦截,因为消耗严重,内部数组不采用defineProperty

        // push shift pop unshift reverse sort splice   7个方法都是变异方法,就是会更改原数组
    </script>

解释:
也就是 new Vue()

function Vue(options) {
     
    // options 为用户传入的选项
    this._init(options); // 初始化操作, 组件
}

执行 _init , _init在initMixin函数定义了方法。 然后为了拿到 options,,
放在vm.$options上 执行initState(vm)

export function initMixin(Vue) {
      // 表示在vue的基础上做一次混合操作
    Vue.prototype._init = function(options) {
     
        // el,data
        const vm = this; // var that = this;
       
        vm.$options = options; // 后面会对options进行扩展操作

        // 对数据进行初始化 watch computed props data ...
        initState(vm); // vm.$options.data  数据劫持

        if(vm.$options.el){
     
            // 将数据挂载到这个模板上
            vm.$mount(vm.$options.el);
        }
    }
    Vue.prototype.$mount = function (el) {
     
        const vm = this;
        const options = vm.$options
        el = document.querySelector(el);
        vm.$el = el;
        // 把模板转化成 对应的渲染函数 =》 虚拟dom概念 vnode =》 diff算法 更新虚拟dom =》 产生真实节点,更新
        if(!options.render){
      // 没有render用template,目前没render
            let template = options.template;
            if(!template && el){
      // 用户也没有传递template 就取el的内容作为模板
                template = el.outerHTML;
                let render = compileToFunction(template);
                options.render = render;
            }
        }
        // options.render 就是渲染函数
        // 调用render方法 渲染成真实dom 替换掉页面的内容

        mountComponent(vm,el); // 组件的挂载流程
    }
}

2、 initState(vm) 在state.js方法定义

import {
      observe } from "./observer/index"; // node_resolve_plugin
import {
      isFunction } from "./utils";

export function initState(vm) {
      // 状态的初始化
    const opts = vm.$options;
    if (opts.data) {
     
        initData(vm);
    }
    // if(opts.computed){
     
    //     initComputed();
    // }
    // if(opts.watch){
     
    //     initWatch();
    // }
}

function proxy(vm,source,key){
     
    Object.defineProperty(vm,key,{
     
        get(){
     
            return vm[source][key];
        },
        set(newValue){
     
            vm[source][key] = newValue
        }
    })
}
function initData(vm) {
      //
    let data = vm.$options.data; // vm.$el  vue 内部会对属性检测如果是以$开头 不会进行代理
    // vue2中会将data中的所有数据 进行数据劫持 Object.defineProperty

    // 这个时候 vm 和 data没有任何关系, 通过_data 进行关联


    data = vm._data = isFunction(data) ? data.call(vm) : data;

    // 用户去vm.xxx => vm._data.xxx
    for(let key in data){
      // vm.name = 'xxx'  vm._data.name = 'xxx'
        proxy(vm,'_data',key); 
    }

    observe(data);
}

initData(vm): data做参数兼容后 放在vm._data 上 然后做代理处理,为了方便取值 比如取 name属性 不用vm._data.name , 直接可以vm.name取值,这一步的代理是为了简化书写方法。

接着执行observe(data);
操作如下 实际调用 new Observer(data)

export function observe(data) {
     
    // 如果是对象才观测
    if (!isObject(data)) {
     
        return;
    }
    if(data.__ob__){
     
        return;
    }
    // 默认最外层的data必须是一个对象
    return new Observer(data)
}

3、 new Observer(data):

兼容data是数组或者对象格式

如果是数组 就改写数组的__proto__, 即 data.proto = arrayMethods; 为的是7个变异方法的重写,方便有新增的内容要进行继续劫持
同时 this.observeArray(data) 是为了递归数组各项 然后递归观察 执行observe ,最好都是每项数据最终都是对象格式,执行this.walk()

new Observer(data)如果data是对象的属性 那么直接执行this.walk,

this.walk就是数据劫持了, 核心就是每一属性执行 defineReactive(data, key, data[key]);

class Observer {
     
    constructor(data) {
      // 对对象中的所有属性 进行劫持
        Object.defineProperty(data,'__ob__',{
     
            value:this,
            enumerable:false // 不可枚举的
        })
        // data.__ob__ = this; // 所有被劫持过的属性都有__ob__ 
        if(Array.isArray(data)){
     
            // 数组劫持的逻辑
            // 对数组原来的方法进行改写, 切片编程  高阶函数
            data.__proto__ = arrayMethods;
            // 如果数组中的数据是对象类型,需要监控对象的变化
            this.observeArray(data);
        }else{
     
            this.walk(data); //对象劫持的逻辑 
        }
    }
    observeArray(data){
      // 对我们数组的数组 和 数组中的对象再次劫持 递归了
        // [{a:1},{b:2}]
        data.forEach(item=>observe(item))
    }
    walk(data) {
      // 对象
        Object.keys(data).forEach(key => {
     
            defineReactive(data, key, data[key]);
        })
    }
}
// vue2 会对对象进行遍历 将每个属性 用defineProperty 重新定义 性能差
function defineReactive(data,key,value){
      // value有可能是对象
    observe(value); // 本身用户默认值是对象套对象 需要递归处理 (性能差)
    Object.defineProperty(data,key,{
     
        get(){
     
            return value
        },
        set(newV){
      
            // todo... 更新视图
            observe(newV); // 如果用户赋值一个新对象 ,需要将这个对象进行劫持
            value = newV;
        }
    })
}

4、 defineReactive(data,key,value) 就是 是对象该属性 重新了 get set

你可能感兴趣的:(vue技术)