ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 2110|回复: 5

[原创] 改装Excel对象的原型,增删改固有成员

[复制链接]

TA的精华主题

TA的得分主题

发表于 2021-8-20 14:44 | 显示全部楼层 |阅读模式
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 wrove 于 2021-8-21 05:09 编辑

全在代码里了:
  1. class ModifyRangeTypeExample {
  2.         static AddMember() {
  3.                 let rng = new Range('B3');
  4.                 //1.通过 __proto__ 对象增加成员,是可行的;
  5.                 //2.添加方法时,不能建议使用箭头函数,因为在它内部
  6.                 //  this is undefined,除非你增加的方法不需要访问
  7.                 //  实例本身
  8.                 ActiveCell.__proto__.AddressR1C1 = function(){
  9.                         return this.Address(false, false, xlR1C1);
  10.                 }
  11.                 Console.log(rng.AddressR1C1());
  12.                 Console.log(rng.Address(false, false, xlR1C1));
  13.                
  14.                 //3.可以添加常字段形式的属性,每个实例访问到的此成
  15.                 //  员将是一样的值
  16.                 ActiveCell.__proto__.ABCDE = 12345;
  17.                 Console.log(rng.ABCDE);
  18.                 Console.log(ActiveCell.ABCDE);
  19.                
  20.                 //4.也可以添加带有 getter/setter 的非字段形式的属性
  21.                 //4.1.通过 Object.create() 来实现 Data 属性
  22.                 ActiveCell.__proto__ = Object.create(
  23.                         ActiveCell.__proto__, {
  24.                                 Data : {
  25.                                         get() {
  26.                                                 return this.Value();
  27.                                         },
  28.                                         set(value) {
  29.                                                 this.Value2 = value;
  30.                                         },
  31.                                         configurable : true
  32.                                 }
  33.                         });
  34.                 Console.log(rng.Data);
  35.                 rng.Data = 'Data Property';
  36.                 Console.log(rng.Data);
  37.                
  38.                 //4.2.通过 Object.defineProperty() 来实现 Bold 属性
  39.                 Object.defineProperty(ActiveCell.__proto__,
  40.                         'Bold', {
  41.                                 get() {
  42.                                         return this.Font.Bold;
  43.                                 },
  44.                                 set(value) {
  45.                                         this.Font.Bold = value;
  46.                                 },
  47.                                 configurable : true
  48.                         });
  49.                 let isBold = rng.Font.Bold;
  50.                 Console.log('isBold?:' + isBold);
  51.                 rng.Bold = !isBold;
  52.                 Console.log('isBold?:' + rng.Bold);
  53.                 Console.log('isBold?:' + rng.Font.Bold);
  54.                
  55.                 //4.3.通过 Object.defineProperties 来实现
  56.                 //    Italic/Underline 属性
  57.                 Object.defineProperties(ActiveCell.__proto__,
  58.                         {
  59.                                 Italic : {
  60.                                         get() {
  61.                                                 return this.Font.Italic;
  62.                                         },
  63.                                         set(value) {
  64.                                                 this.Font.Italic = value;
  65.                                         },
  66.                                         configurable : true
  67.                                 },
  68.                                 Size : {
  69.                                         get() {
  70.                                                 return this.Font.Size;
  71.                                         },
  72.                                         set(value) {
  73.                                                 this.Font.Size = value;
  74.                                         },
  75.                                         configurable : true
  76.                                 }
  77.                         });
  78.                 //Italic Property Test
  79.                 let isItalic = rng.Font.Italic;
  80.                 Console.log('isItalic?:' + isItalic);
  81.                 rng.Italic = !isItalic;
  82.                 Console.log('isItalic?:' + rng.Italic);
  83.                 Console.log('isItalic?:' + rng.Font.Italic);
  84.                 //Size Property Test
  85.                 Console.log('font size?:' + rng.Font.Size);
  86.                 rng.Size = 9;
  87.                 Console.log('new font size?:' + rng.Size);
  88.                 Console.log('new font size?:' + rng.Font.Size);
  89.                                
  90.                 //5.通过属性描述符来定义属性
  91.                 /*描述符(https://segmentfault.com/a/1190000003882976):
  92.                 有数据型描述符和存取型描述符。
  93.                 两种类型的描述符都可以有 configurable 和 enumerable 描述符
  94.                 configurable : 它是 true 时,表明属性可能会改变,也可能被删除,
  95.                                            默认值是 false.
  96.                 enumerable : 它是 true 时,属性可以被 for...in 枚举到。
  97.                 数据型描述符,特有的是 value 和 writable 描述符:
  98.                 value : 提供属性值
  99.                 writable : 它是 true 时,属性值可变;默认值是 false
  100.                 存取型描述符(上面 4.1-4.3 都是),特有的是 get 和 set 描述符
  101.                 get : 给属性提供 getter,返回值用作属性值;如果不提供它,则为 undefined
  102.                 set : 给属性提供 setter,用来修改属性值,有惟一参数;不提供为 undefined
  103.                 */
  104.                 Object.defineProperty(ActiveCell.__proto__,
  105.                         'Smile', {
  106.                         value : '^_^',
  107.                         writable : false,
  108.                         enumerable : true,
  109.                         configurable : true
  110.                 });
  111.                 Console.log(rng.Smile);
  112.                 try {
  113.                         //这句会报错
  114.                         rng.Smile = '-_-';
  115.                 } catch { }
  116.                 Console.log(rng.Smile);
  117.         }
  118.        
  119.         static ReplaceMember() {
  120.                 //1.JS 是基于原型链的,你可以通过 __proto__ 或者
  121.                 //  prototype 访问原型,它也是个对象
  122.                 //2.你可以为原型对象,添加成员来为属于它的所有实例
  123.                 //  添加更多属性与操作(方法)
  124.                 //3.对象被构造时固有的成员,你可以通过
  125.                 //  Object.getOwnPropertyNames() 方法来取得,且它
  126.                 //  不包含通过 __proto__ 或 prototype 定义在原型上
  127.                 //  的成员
  128.                 //4.因为实例的固有成员的优先级,要高于附加在
  129.                 //  __proto__ 或 prototype 上的同名成员,所以虽然你可
  130.                 //  以为原型对象加上同名的成员,但实例对象是无法直接访
  131.                 //  问到它们的,所以想覆写固有成员,是做不到的
  132.                 //4.1.在 __proto__ 上创建同名方法,以图覆写它:
  133.                 ActiveCell.__proto__.Delete = function() {
  134.                         Console.log('Deleting range ' + this.Address());
  135.                 }       
  136.                 let rng = new Range('B2:C3');
  137.                 for (let i = 1; i <= rng.Cells.Count; i++)
  138.                         rng.Cells.Item(i).Value2 =
  139.                                 rng.Cells.Item(i).Address();
  140.                 let sht = rng.Worksheet;
  141.                 //由以下两个输出,你会了解到实例直接访问到
  142.                 //的还是被构造时固有的 Delete() 方法
  143.                 Console.log(sht.Range('B2').Value2);
  144.                 rng.Cells.Item(1).Delete();
  145.                 Console.log(sht.Range('B2').Value2);
  146.                
  147.                 //但是你仍可以通过如下方式,访问到同名方法
  148.                 rng.__proto__.Delete.call(rng);

  149.                 //4.2.在 __proto__ 上创建同名属性,以图覆写它:
  150.                 ActiveCell.__proto__.Text = {
  151.                         get() {
  152.                                 return this.Address() + " : " + this.Value2.toString();
  153.                         },
  154.                         set(value) {
  155.                                 this.Value2 = value;
  156.                         }
  157.                 };
  158.                 let cell = sht.Range('B2');
  159.                 Console.log(cell.Text);
  160.                 try {
  161.                         //这句会报错,因为固有的 Text 属性是只读的
  162.                         cell.Text = 321;
  163.                 } catch { }
  164.                 Console.log(cell.Text);               
  165.         }
  166.        
  167.         static DeleteMember() {
  168.                 //1.删除原型对象上一个不存在的成员
  169.                 let result = 'AreYouOkey' in ActiveCell.__proto__ &&
  170.                         delete ActiveCell.__proto__.AreYouOkey;
  171.                 Console.log('删除 AreYouOkey 成功?:' + result);
  172.                 //2.删除原型上已经存在的成员
  173.                 ActiveCell.__proto__.YesItIs = 857;
  174.                 result = 'YesItIs' in ActiveCell.__proto__ &&
  175.                         delete ActiveCell.__proto__;
  176.                 Console.log('删除 YesItIs 成功?:' + result);
  177.                
  178.                 let rng = new Range('B2');
  179.                 //3.删除对象上已经存在的属性
  180.                 try {
  181.                         //会失败,因为 configurable = false
  182.                         result = 'Text' in rng &&
  183.                                 delete rng.Text;
  184.                 } catch { result = false; }
  185.                 Console.log('删除 Text 成功?:' + result);
  186.                 //4.删除对象上已经存在的方法
  187.                 try {
  188.                         //会失败,因为 configurable = false
  189.                         result = 'Address' in rng &&
  190.                                 delete rng.Address;
  191.                 } catch { result = false; }
  192.                 Console.log('删除 Address() 成功?:' + result);               
  193.                 //5.对于对象固有属性,可以通过
  194.                 //  Object.getOwnPropertyDescriptor(obj, pptName) 方法
  195.                 //  来查看成员的描述符,由 configurable 描述符来确定它是
  196.                 //  否支持 delete 操作
  197.                 //5.1.固有属性的描述符
  198.                 let textDesc = Object.getOwnPropertyDescriptor(rng, 'Text');
  199.                 Console.log(JSON.stringify(textDesc, undefined, 4));
  200.                 //5.2.固有方法的描述符
  201.                 let addressDesc = Object.getOwnPropertyDescriptor(rng, 'Address');
  202.                 Console.log(JSON.stringify(addressDesc, undefined, 4));
  203.         }
  204.        
  205.         static RunAll() {
  206.                 let padCenter = (str) => {
  207.                         let restWidth = 56 - str.length;
  208.                         let left = Math.floor(restWidth / 2);
  209.                         let right = left + restWidth % 2;
  210.                         return '-'.repeat(left) + str + '-'.repeat(right);
  211.                 }
  212.                 Console.log(padCenter(ModifyRangeTypeExample.AddMember.name));
  213.                 ModifyRangeTypeExample.AddMember();
  214.                 Console.log(padCenter(ModifyRangeTypeExample.ReplaceMember.name));
  215.                 ModifyRangeTypeExample.ReplaceMember();
  216.                 Console.log(padCenter(ModifyRangeTypeExample.DeleteMember.name));
  217.                 ModifyRangeTypeExample.DeleteMember();
  218.                 ModifyRangeTypeExample.clear();
  219.         }
  220.        
  221.         static clear() {
  222.                 for (let name in ActiveCell.__proto__)
  223.                         delete ActiveCell.__proto__[name];
  224.         }
  225. }

  226. function RunExample() {
  227.         ModifyRangeTypeExample.RunAll();
  228. }
复制代码

附注:本贴内容已整合,请版主删除本贴,我的所有回复


评分

2

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2021-8-24 14:00 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
上面的是通过修改原型的方式,影响对象,其实还有另一种方式,就是使用代理对对象进行打包,返回打包后的对象给用户使用,以此方式对对象的访问进行直接干预,看代码:
  1. //居中填充
  2. String.prototype.padCenter =
  3.   function(targetLength, padString = ' ') {

  4.   if (typeof targetLength != 'number')
  5.     throw new TypeError('Parameter "targetLength" ' +
  6.                         'must be a number object.');

  7.   if (typeof padString != 'string') {
  8.     if (padString === null)
  9.       padString = 'null';
  10.     else
  11.       padString = padString.toString();
  12.   }

  13.   let padStrWidth = padString.length;       
  14.   if (padStrWidth == 0) return this;

  15.   let restWidth = targetLength - this.length;
  16.   if (restWidth <= 0) return this;

  17.   let leftWidth = Math.trunc(restWidth / 2);
  18.   let rightWidth = leftWidth + restWidth % 2;

  19.   if (padString.length == 1) {
  20.     return padString.repeat(leftWidth) + this +
  21.       padString.repeat(rightWidth);
  22.   } else {
  23.     if (leftWidth == 0)
  24.       return this + padString[0];
  25.     else {

  26.       //leftPart
  27.       let leftRepeat = Math.trunc(leftWidth / padStrWidth);       
  28.       let leftRest = leftWidth - leftRepeat * padStrWidth;       
  29.       let leftStr = padString.repeat(leftRepeat) +
  30.           padString.substr(0, leftRest);
  31.       //rightPart
  32.       let rightRepeat = Math.trunc(rightWidth / padStrWidth);
  33.       let rightRest = rightWidth - rightRepeat * padStrWidth;
  34.       let rightStr = padString.repeat(rightRepeat) +
  35.           padString.substr(0, rightRest);

  36.       return leftStr + this + rightStr;
  37.     }
  38.   }
  39. }

  40. /*Proxy handler 可对应于
  41.   Reflect.apply()
  42.   Reflect.construct()
  43.   Reflect.defineProperty()
  44.   Reflect.deleteProperty()
  45.   Reflect.get()
  46.   Reflect.getOwnPropertyDescriptor()
  47.   Reflect.getPrototypeOf()
  48.   Reflect.has()
  49.   Reflect.isExtensible()
  50.   Reflect.ownKeys()
  51.   Reflect.preventExtensions()
  52.   Reflect.set()
  53.   Reflect.setPrototypeOf()
  54.   实现想要的代理
  55. */
  56. class ES6ProxyTest {
  57.         //既然 Reflect.set(target, key, value[, receiver]) 有
  58.         //要设置的属性的键和值,我们就可以通过 set 代理,在一个
  59.         //对象的定义的外部:
  60.         //1.通过键,拦截对某些属性的写入
  61.         //2.通过值,检验类型,拦截非法写入
  62.         //3.通过键,重定向属性的写入,就像为属性设置一些假名一样
  63.         static SetProxy() {
  64.                 let p = new Point(3, 8);
  65.                 let pp = new Proxy(p, {
  66.                         set:function(target, key, value, receiver){
  67.                                 //Reflect.set(target, key, value[, receiver])
  68.                                 //target : 用于接收属性(被代理的对象)的对象
  69.                                 //key : 要写入的属性的键(字符串或Symbol类型)
  70.                                 //value : 要写入的属性新值
  71.                                 //receiver : 如果 target 对象的 key 属性有 setter,
  72.                                 //         receiver 则为 setter 调用时的 this 值。
  73.                                 //return : 返回一个 Boolean 值,表明操作的成败
  74.                                 let success = Reflect.set(target, key, value, receiver);
  75.                                 if (success) {
  76.                                         //Console 在此不可用
  77.                                         Debug.Print('property '+ key +' on '+
  78.                                                 target + ' set to '+ value);
  79.                                 }
  80.                                 //必须返回操作成败状态,否则报错
  81.                                 return success;
  82.                         }
  83.                 });
  84.                 pp.xxx = 13;
  85.                 Console.log(p.xxx);
  86.         }
  87.        
  88.         //既然 Reflect.get(target, key[, receiver]) 提供了要读取
  89.         //的属性的键,我们就可以通过 get 代理,在对象的定义的外部:
  90.         //1.通过键,拦截对某些属性的读取
  91.         //2.通过键,伪造一些不存在的属性
  92.         //3.通过键,实现类同假名的属性
  93.         static GetProxy() {
  94.                 var obj = new Proxy({}, {
  95.                   get: function (target, key, receiver) {
  96.                           //Console 在此不可用
  97.                         Debug.Print(`getting ${key}!`);
  98.                         //Reflect.get(target, key[, receiver])
  99.                         //target : 被代理的对象
  100.                         //key : 要读取的属性的键(字符串或Symbol类型)
  101.                         //receiver : 如果 target 对象的 key 属性有 getter,
  102.                     //           receiver 则为 getter 调用时的 this 值。
  103.                     //return : 属性的值。
  104.                     return Reflect.get(target, key, receiver);
  105.                   }
  106.                 });
  107.                 obj.count = 1;
  108.                 ++obj.count;
  109.         }
  110.        
  111.         /*Reflect.apply(target, thisArg, argsList)
  112.           target : 被代理对象,请确保它是一个 Function 对象
  113.           thisArg : 函数调用时绑定的对象
  114.           argsList : 函数调用时传入的实参列表,该参数应该是一个类数组的对象。
  115.           return : 调用函数返回的结果
  116.           通过这种代理:
  117.           1.检验调用时传入的参数
  118.           2.阻止函数被调用
  119.         */
  120.         static ApplyProxy() {
  121.                 function sum (...values){
  122.                     return values.reduce((pre, cur) => pre + cur, 0);
  123.                 }

  124.                 let sumProxy = new Proxy(sum, {
  125.                     apply : function(target, thisArg, argsList){
  126.                         argsList.forEach(arg => {
  127.                             if(typeof arg !== "number")
  128.                                 throw new TypeError("所有参数必须是数字,亲!");
  129.                         });
  130.                         return Reflect.apply(target, thisArg, argsList);
  131.                     }
  132.                 });
  133.                
  134.                 try {
  135.                         let r = sumProxy(3, 5, 'hello');
  136.                         Console.log(r);
  137.                 } catch(e) {
  138.                         Console.log(e.message);
  139.                 }
  140.                 Console.log(sumProxy(3, 8, 5));
  141.         }
  142.        
  143.         /*Reflect.construct(target, argsList[, newTarget])
  144.           target : 被运行的目标构造函数
  145.           argsList : 类数组,目标构造函数调用时的参数。
  146.           newTarget : 可选,作为新创建对象的原型对象的 constructor 属性,
  147.                             参考 new.target 操作符,默认值为 target。          
  148.         */
  149.         static ConstructProxy() {
  150.                 function sum (...values){
  151.                     return values.reduce((pre, cur) => pre + cur, 0);
  152.                 }

  153.                 let sumProxy = new Proxy(sum, {
  154.                     construct:function(target, argsList){
  155.                         throw new TypeError("亲,该函数不能通过 new 调用。");
  156.                     }
  157.                 });
  158.                
  159.                 try {
  160.                         let x = new sumProxy(3, 5, 7);
  161.                 } catch(e) {
  162.                         Console.log(e.message);
  163.                 }
  164.         }
  165.        
  166.         //禁止向指定单元格区域写入数据
  167.         static ForbidSetValue() {
  168.                 let rng = new Range('B1:D3');
  169.                 rng.Value2 = 32;
  170.                
  171.                 let rngProxy = new Proxy(rng, {
  172.                         set : function(target, key, value, receiver) {
  173.                                 if (key === 'Value2') {
  174.                                         throw new Error('无法设置属性')
  175.                                 } else
  176.                                         return Reflect.set(target, key, value, receiver);
  177.                         }
  178.                 });
  179.                
  180.                 try {
  181.                         rngProxy.Value2 = 168;
  182.                 } catch(e) {
  183.                         Console.log(e.message);
  184.                 }
  185.                 Console.log(rngProxy.Text);
  186.         }
  187.        
  188.         //运行所有测试用例
  189.         static RunAll() {
  190.                 let members = Object.getOwnPropertyNames(ES6ProxyTest);
  191.                 let notCall = ['length', 'prototype', 'name', 'RunAll'];
  192.                 for (let member of members) {
  193.                         if (!notCall.includes(member)) {
  194.                                 Console.log(member.padCenter(56, '-'));
  195.                                 eval(`ES6ProxyTest.${member}()`);
  196.                         }
  197.                 }
  198.         }
  199. }
复制代码

在立即窗口中输入 ES6ProxyTest.RunAll(); ,然后回车,其输出如下:
  1. ------------------------SetProxy------------------------
  2. property xxx on (3, 8) set to 13
  3. 13
  4. ------------------------GetProxy------------------------
  5. getting count!
  6. -----------------------ApplyProxy-----------------------
  7. 所有参数必须是数字,亲!
  8. 16
  9. ---------------------ConstructProxy---------------------
  10. 亲,该函数不能通过 new 调用。
  11. ---------------------ForbidSetValue---------------------
  12. 无法设置属性
  13. 32
复制代码

评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2021-8-24 15:26 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 wrove 于 2021-8-24 16:19 编辑

通过修改原型对象,对对象成员进行汉化:
  1. class ChinesizationRangeType {
  2.         static Do() {
  3.                 let range = ActiveCell.__proto__;
  4.                
  5.                 range.取地址 = function() {
  6.                         return this.Address(...arguments);
  7.                 };
  8.                
  9.                 Object.defineProperty(range, '值', {
  10.                         get() { return this.Value2; },
  11.                         set(value) { this.Value2 = value; },
  12.                         configurable : true,
  13.                 });
  14.                
  15.                 range.取值 = function() {
  16.                         return this.Value();
  17.                 }               
  18.         }
  19.        
  20.         static Undo() {
  21.                 for (let name in ActiveCell.__proto__)
  22.                         delete ActiveCell.__proto__[name];       
  23.         }
  24.        
  25.         static Test() {
  26.                 //执行汉化
  27.                 ChinesizationRangeType.Do();
  28.                
  29.                 //测试汉化
  30.                 let rng = new Range('B2');
  31.                 Console.log(rng.取地址());
  32.                 Console.log(rng.取地址(true, true, xlR1C1));
  33.                 rng.值 = 32;
  34.                 Console.log(rng.值);
  35.                 Console.log(rng.取值());               
  36.                
  37.                 //撤销汉化
  38.                 ChinesizationRangeType.Undo();
  39.         }
  40. }
复制代码
立即窗口中输入 ChinesizationRangeType.Test(),然后回车,其输出如下:
  1. $B$2
  2. R2C2
  3. 32
  4. 32
复制代码



TA的精华主题

TA的得分主题

发表于 2024-6-7 10:16 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
很强大  但是表示看不懂怎么用的

TA的精华主题

TA的得分主题

发表于 2024-6-7 13:03 | 显示全部楼层
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-12-25 15:55 , Processed in 0.031587 second(s), 8 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表