vue2.0|具体实现篇|数据劫持

853 阅读1分钟

前言

  • 文分【思路篇】和【实现篇】,本文为实现篇,建议看两个窗口同步阅读,或请先阅读-》vue2.0|思路篇|数据劫持
  • 第一二阶段是框架搭建,代码见思路篇

第三阶段:实现初步数据劫持,监听用户对对象属性的赋值取值操作,触发同时执行自定义逻辑

​ 定义observe方法,其参数是一个对象,非对象会被直接return;对象则会创建Observe实例,在构造中执行递归操作

observe/index.js
export function observe(data){
    console.log(data);
    if(typeof data !== 'object' && data !== null){
        return;
    }
    return new Observer(data);
}
class Observer {
    constructor(value){
       
        this.walk(value);
    }
    walk(data){
        let keys = Object.keys(data);
        keys.forEach(key=>{
            defineReactuve(data,key,data[key]);
        })
    }
}
function defineReactuve(data,key,value){
    // 实现递归
    observe(value)
    Object.defineProperty(data,key,{
        get(){
            console.log('用户取值');
            return value
        },
        set(newValue){
            console.log('用户赋值');
            if(value !== newValue){
                // 在set中对新值也进行监听操作:对新赋值的对象进行观测
                observe(newValue);
                value = newValue
            }
        }
    }
    )
}
state.js
function initData(vm) {
    let data = vm.$options.data;
    vm._data = data = typeof data == 'function' ? data.call(vm) : data;
    observe(data);
}

第四阶段 :数组数据劫持

不对数组的属性进行遍历,而是采用函数劫持的方式进行数组的劫持,即重写数组原型上会改写本身的七个方法,且遍历数组中是对象的元素进行深度观测,从而实现数组监听;

数组内对象的劫持

遍历数组中是对象的元素调用观测方法
observer/array.js
class Observer {
    constructor(data){

        if (Array.isArray(data)) {
            // 新增属性,声明此属性已被观测
            Object.defineProperty(data,"__ob__",{
                enumerable:false,
                configurable: false,
                value:this
            })

            // 只能拦截数组的方法,但对数组中的每一项 无法监听 需要观测
            data.__proto__ = arrayMethods;
            this.observerArray(data)
            // console.log(data,arrayMethods);

        } else {
            this.walk(data)
        }
    }
    walk(data){
        let keys = Object.keys(data);
        keys.forEach(key=>{
            defineReactuve(data,key,data[key]);
        })
    }
    observerArray(value) {
        for (let i  = 0; i < value.length; i++) {
            observe(value[i])
        }
    }
}
function defineReactuve(data,key,value){
    // 实现递归
    observe(value)
    Object.defineProperty(data,key,{
        get(){
            console.log('用户取值');
            return value
        },
        set(newValue){
            console.log('用户赋值');
            if(value !== newValue){
                // 对新赋值的对象进行观测
                observe(newValue);
                value = newValue
            }
        }
    }
    )

数组方法的劫持

获取数组原型,重写七个方法,将此对象导出
observer/array.js
import { observe } from "./index.js";

// 拦截用户调用的push shift unshift pop reverse sort splice
// 获取数组原型
let oldArrayProtoMethods = Array.prototype;
// 重写七个方法,将此新原型对象导出
export let arrayMethods = Object.create(oldArrayProtoMethods);

let methods = ['push','shift','unshift', 'pop', 'reverse', 'sort', 'splice'];


methods.forEach(method=>{
    // console.log(arrayMethods,method);

    arrayMethods[method] = function (...args){
        let r = oldArrayProtoMethods[method].apply(this,args)

        // todo
        let inserted;
        let ob = this.__ob__;
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args;break;
            case "splice":
                inserted = args.slice(2)
            default:
                break;
        }
        console.log('数组更新方法 == 去渲染页面');
        return r;

    }
})

在Observer构造中判断,如果数据是数组特殊处理(observerArray),将数据的原型执行自定义对象
observer/index.js
class Observer {
    constructor(data){

        if (Array.isArray(data)) {
            // 新增属性,声明此属性已被观测
            Object.defineProperty(data,"__ob__",{
                enumerable:false,
                configurable: false,
                value:this
            })

            // 只能拦截数组的方法,但对数组中的每一项 无法监听 需要观测
            data.__proto__ = arrayMethods;
            this.observerArray(data)
            // console.log(data,arrayMethods);

        } else {
            this.walk(data)
        }
    }
    walk(data){
        let keys = Object.keys(data);
        keys.forEach(key=>{
            defineReactuve(data,key,data[key]);
        })
    }
    observerArray(value) {
        for (let i  = 0; i < value.length; i++) {
            observe(value[i])
        }
    }
}
function defineReactuve(data,key,value){
    // 实现递归
    observe(value)
    Object.defineProperty(data,key,{
        get(){
            console.log('用户取值');
            return value
        },
        set(newValue){
            console.log('用户赋值');
            if(value !== newValue){
                // 对新赋值的对象进行观测
                observe(newValue);
                value = newValue
            }
        }
    }
    )
}
对有新增功能的方法进行处理,新增值进行观测处理
observer/array.js
methods.forEach(method=>{
    arrayMethods[method] = function (...args){
        let r = oldArrayProtoMethods[method].apply(this,args)

        // 以数组形式保存新增元素
        let inserted;
        // __ob__在Observer类的构造中赋值,1. 声明对应属性已被观测 2. 使得Observer类上的observerArray方法可以在此处被获取到
        let ob = this.__ob__;
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args;break;
            case "splice":
                inserted = args.slice(2)
            default:
                break;
        }
		// 如果新增元素数组存在 则进行数组观测
        if(inserted) ob.observerArray(inserted)
        console.log('数组更新方法 == 去渲染页面');
        return r;

    }
})

第五阶段 :小优化,代理实现用户直接从vm上处理数据

通过Object.defineProperty进行一层代理,遍历_data在vm上对所有key定义getset,这样当用户取值赋值时就是操作_data

state.js
function proxy(vm,data,key) {
    Object.defineProperty(vm,key,{
        get(){
            return vm[data][key];
        },
        set(newValue){
            vm[data][key] = newValue;
        }
    })
}
function initData(vm) {
    let data = vm.$options.data;
    vm._data = data = typeof data == 'function' ? data.call(vm) : data;

    // 进行代理,实现直接从实例上处理数据
    for (const key in data) {
       proxy(vm,'_data',key)
    }

    observe(data);
}

最终实现