微型Vue框架实现Part2——数据代理实现
【微型Vue框架实现Part2——数据代理实现】微型Vue框架构建Part1——基本目录结构
微型Vue框架构建Part3——$Mount方法实现
微型Vue框架构建Part4——Render渲染前
概述
- 使用Object.defineProperty()实现数据代理
- 数据代理实现原理概览
文章图片
- 对象嵌套对象
- 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的原型链被我们修改了,这样就实现了对数组内置方法的代理
- 我们在实现数据代理的时候会用到这个点
文章图片
- 实现对象嵌套对象的数据代理:
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;
推荐阅读
- android第三方框架(五)ButterKnife
- vue-cli|vue-cli 3.x vue.config.js 配置
- 2020-04-07vue中Axios的封装和API接口的管理
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- VueX--VUE核心插件
- vue组件中为何data必须是一个函数()
- Spring|Spring 框架之 AOP 原理剖析已经出炉!!!预定的童鞋可以识别下发二维码去看了
- 用npm发布一个包的教程并编写一个vue的插件发布
- vuex|vuex 基础结构
- Vue源码分析—响应式原理(二)