猴子修补
你想做的事叫做猴子修补 — 你改变了一个内置的原型。有许多错误的方法可以做到这一点,应该完全避免,因为它会对Web兼容性产生负面影响。然而,我将演示如何以最接近现有原型特性的方式实现这一点。
在您的示例中,函数体应该返回这个。b条
.在作为方法调用的函数中,可以使用这
关键字。请参见“this”关键字是如何工作的?(第4节:“输入功能代码”,小节“函数属性”)了解更多详细信息。
您已将方法正确添加到对象.原型
.请参见继承和原型链了解更多详细信息。
工具
在推理monkey-patching时,涉及到许多工具:
- 检查自有财产存在
- 使用属性描述符
- 使用正确的函数类型
- 吸气器和排气器
假设您正在尝试实现方法
在The类
.
1.检查自有财产是否存在
根据您的用例,您可能需要检查您想要引入的方法是否已经存在。你可以用对象.hasOwn
; 这是一种非常新的方法,但在较旧的环境中,它可以简单地由对象.原型.hasOwnProperty.call
.或者,使用拥有自己的财产
正常情况下,但要注意,如果你或其他人模仿了拥有自己的财产
方法它本身,这可能会导致错误的结果。
请注意在里面
不专门检查自己的属性,但也检查继承的属性,当您要创建拥有属性。另外,请注意if(TheClass.prototype.theMethod)
不是属性存在检查;它是一个真实性检查。
代码示例
if(Object.hasOwn(TheClass.prototype,“theMethod”)){//定义方法。}
if(Object.prototype.hasOwnProperty.call(TheClass.protoype,“theMethod”)){//定义方法。}
if(TheClass.prototype.hasOwnProperty(“theMethod”)){//定义方法。}
2.使用属性描述符
您可以随意选择属性描述符,但现有的方法是可写、可配置和不可枚举(最后一个是使用时的默认值定义属性
).定义属性
可用于一次性定义多个属性。
当使用=
,属性变为可写、可配置,并且可枚举的.
代码示例
//定义方法:Object.defineProperty(TheClass.prototype,“theMethod”{可写:true,可配置:true,值:函数(){}});
//定义方法:Object.defineProperties(类原型{方法:{可写:true,可配置:true,值:函数(){}}});
3.使用正确的函数种类
JavaScript有四种主要功能,它们具有不同的用例。“可调用”意味着可以调用没有 新的
“可构造”意味着可以调用具有 新的
.
函数种类 |
例子 |
可调用 |
是可构造的 |
有这 结合 |
箭头函数 |
() => {} |
是的 |
不 |
不 |
方法 |
({method(){}}).方法 |
是的 |
不 |
是的 |
等级 |
(类{}) |
不 |
是的 |
是的 |
功能 功能 |
(函数(){}) |
是的 |
是的 |
是的 |
我们正在寻找一种可以调用的方法(无需新的
)并且有自己的这
结合。我们可以使用功能
s、 但是,看看现有的方法,不仅是新的“HELLO”.charAt();
奇怪,它也不起作用!因此,该方法也不应是可构建的。因此,适当方法就是我们要找的。
注意,这显然取决于您的用例。例如,如果您想要一个可构造的函数,请使用班
而不是。
代码示例
我们回到前面的代码示例,而不是函数(){}
使用方法定义。
//定义方法:Object.defineProperty(TheClass.prototype,“theMethod”{可写:true,可配置:true,值:{方法(){//做这件事。}}.the方法});
为什么要麻烦2。和3。?
可枚举性和可构造性考虑的目标是创建与现有内置方法具有相同“外观”的东西。这些实现与原始实现之间的区别可以通过以下代码片段来演示:
类TheClass{}Class.prototype.theMethod=函数(){};
让我们将此与不同的,内置的方法,如字符串.原型.charAt
:
代码段 |
天真的例子 |
内置示例 |
原型 |
是Class.原型 |
是字符串.原型 |
方法 |
是类.原型.方法 |
是字符串.原型.charAt |
for(原型中的常量p){console.log(p);} |
“方法” 将在某个时间点记录。 |
“charAt” 将永远不会被记录。 |
新建方法 |
创建的实例方法 . |
类型错误 :方法 不是构造函数。 |
使用第2小节和第3小节中的工具,可以创建行为更像内置方法的方法。
4.吸气器和排气器
另一种方法是使用getter。考虑一下:
常量arr=[“a”,“b”,“c”,];console.log(arr.indexOfB);//1
如何B索引
上的getter阵列
原型看起来像吗?我们不能使用上述方法替换价值
通过得到
,否则我们将得到:
类型错误
:属性描述符不能指定值,也不能在指定getter或setter时可写
财产可写的
需要完全移除从描述符。现在价值
可以替换为得到
:
Object.defineProperty(数组原型,“indexOfB”{可配置:true,获取:{索引OfB(){return this.indexOf(“b”);}}.B索引});
还可以通过添加设置
属性设置为描述符:
Object.defineProperty(数组原型,“indexOfB”{可配置:true,获取:{索引OfB(){return this.indexOf(“b”);}}.indexOfB,设置:{indexOfB(新值){//`newValue`是赋值。//对当前Array实例使用“this”。//不需要“return”。}}.B索引});
Web兼容性影响
任何人都希望扩展内置原型的原因如下:
- 您自己有一个好主意,可以将一个新特性添加到您正在扩展的任何类的所有实例中,或者
- 您希望将现有的指定功能反向移植到较旧的浏览器。
如果扩展目标对象,有两个选项需要考虑:
- 如果不存在该属性,请提供该属性,否则,保留现有属性,或
- 始终用您自己的实现替换该属性,无论它是否存在。
所有方法大多都有缺点:
如果您或其他人发明了他们自己的功能,但是出现了一个同名的标准方法,那么这些功能几乎肯定是不兼容的。
如果您或其他人试图实现一个标准功能,以便将其反向移植到不支持它的浏览器,但不阅读规范并“猜测”其工作方式,那么polyfilled功能几乎肯定与标准实现不兼容。即使严格遵守规范,谁又能确保跟上规范的变化?谁将负责实施过程中可能出现的错误?
如果您或其他人选择在覆盖该功能之前检查该功能是否存在,那么一旦有人使用支持该功能的浏览器访问您的页面,突然一切都会中断,因为实现结果是不兼容的。
如果您或其他人选择无论如何覆盖该功能,那么至少实现是一致的,但是迁移到标准功能可能会很困难。如果您编写了一个使用的库很多在其他软件中,迁移成本变得如此巨大,以至于标准本身必须改变; 这就是为什么数组原型包含
必须重命名为数组.原型.包括
[编辑] [ES讨论] [Bugzilla] [MooTools]、和数组.原型.扁平
无法使用,必须命名阵列.原型.扁平
相反[拉请求1] [拉请求2] [Bugzilla].换句话说,这就是我们不能拥有美好事物的原因.
此外,您的库可能无法与其他库进行互操作。
选择
最简单的替代方法是定义自己的普通函数:
const theMethod=(object)=>object.theProperty;//或者别的什么。const theProperty=方法(对象);
你也可以考虑代理.这样,您可以动态查询属性并对其作出响应。假设您有一个具有属性的对象一
通过z(z)
你想实现方法获取A
通过获取Z
:
const theProxiedObject=新代理(对象{get(目标、属性、接收方){const letter=属性匹配(/^get(?<letter>[A-Z])$/)?。组?。letter.toLowerCase();if(字母){return()=>目标[字母];}return Reflect.get(目标、属性、接收方);}});console.assert(theProxiedObject.getB()===theProxidedObject.b);
您还可以使用另一个类扩展对象的原型,并使用以下方法:
类GetterOfB扩展对象{b;构造函数(init){super();Object.assign(this,init);}获取B(){返回此.b;}}const theObject=新GetterOfB({b: “c”});const theB=对象.getB();
你要记住的是不要修改你自己没有定义的东西。