微型Vue框架实现Part2——数据代理实现

【微型Vue框架实现Part2——数据代理实现】微型Vue框架构建Part1——基本目录结构
微型Vue框架构建Part3——$Mount方法实现
微型Vue框架构建Part4——Render渲染前
概述

  • 使用Object.defineProperty()实现数据代理
  • 数据代理实现原理概览
    微型Vue框架实现Part2——数据代理实现
    文章图片
具有代表性的数据模型
  • 对象嵌套对象
  • Array类型的数据模型,且数组内置方法触发后会改变代理数据
    data: { name: "小明", age: 10, address: "家庭住址", info: { IDCard: "身份证号码", school: "学校名字", obj: { name: "sddd" } }, list: [{ a: 1, b: 2 }] }

    数据代理实现——代理单个数据
    /** * @param {*} key 需要代理的字段 —— 字符串类型 * @param {*} proxyObj代理对象 —— Object类型 * @param {*} originObject 原数据对象 —— Object类型 * @returns */ function proxyKey(key, proxyObj, originObject) { Object.defineProperty(proxyObj, key, { configurable: true, // 允许增删代理对象的key键,原数据也会同步 get: function () { return originObject[key]; }, set: function (val) { return (originObject[key] = val); }, }); return proxyObj; } // 代码实践 const prpxyObj = {}; let data = https://www.it610.com/article/{ a: 20, b: 30 } window.test = proxyKey("a", prpxyObj, data)

代理数组方法
  • 目的:修改数组对象的原型链,改变数组内置方法的执行体,如push方法、pop方法
  • Object.defineProperty()中的value属性:代理属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等),即你可控制返回结果。
    const arrProxyFunc = new Array(); /** * obj:代理对象 * proxyFunction:需要被代理的数组方法 */ function proxyArrayFunctionCall(obj, proxyFunction) { Object.defineProperty(obj, proxyFunction, { enumerable: true, configurable: true, // 允许删除代理数据的内容 value: function (...args) { // 映射Array原型链上的方法, const arrProxyFunc = arrayProtoType[proxyFunction]; // 改变this指向 const result = arrProxyFunc.apply(this, args); return result; }, }); }/** *array: 需要被代理的数组方法——其原型链将会完全覆盖 */ function proxyArray(array) { // 创建代理对象——新的数组原型链,完全覆盖之前的原型链 const obj = { eleType: "Array", pop() {}, push(){} }; // 代理自定义的方法 proxyArrayFunctionCall(obj, "pop", ); proxyArrayFunctionCall(obj, "push"); /** * 覆盖原本原型链——这样就实现了数组方法的代理 * 完全覆盖之前数组的原型链,不再支持内置的数组方法,只能使用obj对象提供的方法 */ array.__proto__ = obj; return array; }// 代码调试 const arr = []; window.arr = arr; window.test = proxyArray(arr);

  • 可以看到,无论是arr的原型链被我们修改了,这样就实现了对数组内置方法的代理
  • 我们在实现数据代理的时候会用到这个点
    微型Vue框架实现Part2——数据代理实现
    文章图片
实现Vue框架中的数据代理
  • 实现对象嵌套对象的数据代理:proxyObject()方法中可以看到代码片段
  • 实现数组类型的数据代理:proxyArray()
  • 命名空间:getNameSpace(),该方法是为了保证在对象嵌套对象的情况下,数据源不会取错。如代理obj对象下的test,代理num这个key时,它在原数据的取值应是obj.test.num。所以,当实现其代理时命名空间就是字符串obj.test
    let obj = { a: 10, test: { num: 20 } }

  • Object.getOwnPropertyNames(): 返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
  • 我们在实现代理数组方法时用到了Object.getOwnPropertyNames,映射数组原型链上所有的内置方法。
/** * 数据代理 */ const arrayProtoType = new Array(); function getNameSpace(nameSpace = null, key = null) { if (nameSpace && !key) { return nameSpace; } else if (key && !nameSpace) { return key; } else { return `${nameSpace}.${key}`; } }function getObjectKey(obj) { return Object.keys(obj); }/** * * @param {*} key 需要代理的字段 * @param {*} proxyObj代理对象 * @param {*} originObject 原数据对象 * @returns */ function proxyKey(key, proxyObj, originObject) { Object.defineProperty(proxyObj, key, { configurable: true, get: function () { return originObject[key]; }, set: function (val) { return (originObject[key] = val); }, }); return proxyObj; }/** * 代理对象 * @param {} option * @returns */ function proxyObject(vm, option, nameSpace) { let proxyObj = {}; const proxyAllKey = getObjectKey(option); for (let i = 0; i < proxyAllKey.length; i++) { const key = proxyAllKey[i]; const val = option[key]; proxyKey(key, proxyObj, option); // 对象嵌套对象或是数组类型的情况 if (val instanceof Object || val instanceof Array) { option[key] = constructorProxy(vm, val, getNameSpace(nameSpace, key)); } } return proxyObj; }/** * 代理Array原型链上的某个内置方法 * @param {*} vm * @param {*} obj // 代理对象 * @param {*} proxyFunction // 需要代理的数组方法 * @param {*} nameSpace */ function proxyArrayFunctionCall(vm, obj, proxyFunction, nameSpace) { Object.defineProperty(obj, proxyFunction, { enumerable: true, configurable: true, // 允许删除代理数据的内容 value: function (...args) { const arrProxyFunc = arrayProtoType[proxyFunction]; const result = arrProxyFunc.apply(this, args); return result; }, }); }/** * 代理数组——覆盖数组对象本来的原型链 * 目的:当时触发代理对象的pop、push...方法时,执行我们自定义的内容 *也就是 proxyArrayFunctionCall 函数中 value的返回值 * @param {*} vm * @param {*} array * @param {*} nameSpace */ function proxyArray(vm, array, nameSpace) { /** * 获取 Array 原型链上的所有内置方法的key * 如常用的数组方法:push、pop、unshift... */ const arrayProtoTypeAllKey = Object.getOwnPropertyNames( arrayProtoType.__proto__ ); const obj = { eleType: "Array", ...arrayProtoTypeAllKey, }; arrayProtoTypeAllKey.forEach((functionName) => { proxyArrayFunctionCall(vm, obj, functionName, nameSpace); }); // 覆盖原本原型链——这样就实现了数组方法的代理 array.__proto__ = obj; return array; }function constructorProxy(vm, options, nameSpace = "") { let proxy = {}; if (options instanceof Array) { proxy = new Array(options.length); for (let i = 0; i < options.length; i++) { if (options[i] instanceof Object) { proxy[i] = proxyObject(vm, options[i], nameSpace); } else { proxy[i] = proxyKey(options[i], {}, options[i]); } }proxy = proxyArray(vm, options, nameSpace); } else if (options instanceof Object) { proxy = proxyObject(vm, options, nameSpace); } else { throw new Error("Error"); } return proxy; } export default constructorProxy;

    推荐阅读