如何在自定义Object.prototype.xxx函数中获取对象本身?

Har*_* Yu 3 javascript monkeypatching object

Object.prototype.getB = function() {

  // how to get the current value a
  return a.b;

};
const a = {b: 'c'};
a.getB();
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我想为所有对象值创建一个函数。我需要获取这个函数中的对象值然后做一些事情。

Seb*_*mon 5

猴子补丁

\n

你想做的就是猴子修补\xe2\x80\x8a\xe2\x80\x94\xe2\x80\x8ayou 变异内置原型。\n有很多错误的方法可以做到这一点,应该完全避免,因为它会对 Web 兼容性产生负面影响。\n不过,我将演示如何以最匹配现有原型功能的方式实现这一点。

\n

在您的情况下,函数体应该返回this.b。\n在作为方法调用的函数中,您可以使用关键字获取对象本身this。\n请参阅“this”关键字如何工作?(第 4 节:\xe2\x80\x9c输入函数代码\xe2\x80\x9d,小节\xe2\x80\x9c函数属性\xe2\x80\x9d)了解更多详细信息。

\n

您已正确地将方法添加到Object.prototype。\n有关更多详细信息,请参阅继承和原型链。

\n

工具

\n

在推理猴子补丁时涉及到许多工具:

\n
    \n
  1. 检查自有财产是否存在
  2. \n
  3. 使用属性描述符
  4. \n
  5. 使用正确的函数类型
  6. \n
  7. 获取器和设置器
  8. \n
\n

让\xe2\x80\x99s 假设你\xe2\x80\x99 正在尝试theMethodTheClass.

\n

1. 检查自有财产是否存在

\n

根据您的用例,您可能需要检查您想要引入的方法是否已经存在。\n您可以使用Object.hasOwn;来执行此操作。这是一个相当新的方法,但在较旧的环境中,它可以简单地替换为Object.prototype.hasOwnProperty.call。\n或者, hasOwnProperty 正常使用,但请注意,如果您或其他人对该 hasOwnProperty 方法本身进行了猴子修补,这可能会导致不正确的结果。

\n

请注意,它in不会专门检查自己的属性,但也会检查继承的属性,当您要在对象上创建自己的属性时,这(必然)不是您想要的。 \n另外,请注意,这if(TheClass.prototype.theMethod)不是属性存在检查;它\xe2\x80\x99是真实性检查。

\n
代码示例
\n
if(Object.hasOwn(TheClass.prototype, "theMethod")){\n  // Define the method.\n}\n
Run Code Online (Sandbox Code Playgroud)\n
if(Object.prototype.hasOwnProperty.call(TheClass.prototype, "theMethod")){\n  // Define the method.\n}\n
Run Code Online (Sandbox Code Playgroud)\n
if(TheClass.prototype.hasOwnProperty("theMethod")){\n  // Define the method.\n}\n
Run Code Online (Sandbox Code Playgroud)\n

2. 使用属性描述符

\n

您可以根据需要选择属性描述符,但现有方法是可写、可配置且不可枚举的(最后一个方法是使用 时的默认值defineProperty)。\ndefineProperties可用于一次性定义多个属性。

\n

当简单地使用 分配属性时=,该属性变得可写、可配置和可枚举

\n
代码示例
\n
// Define the method:\nObject.defineProperty(TheClass.prototype, "theMethod", {\n  writable: true,\n  configurable: true,\n  value: function(){}\n});\n
Run Code Online (Sandbox Code Playgroud)\n
// Define the method:\nObject.defineProperties(TheClass.prototype, {\n  theMethod: {\n    writable: true,\n    configurable: true,\n    value: function(){}\n  }\n});\n
Run Code Online (Sandbox Code Playgroud)\n

3. 使用正确的函数类型

\n

JavaScript 有四种主要的函数,它们有不同的用例。\n\xe2\x80\x9c 是可调用的\xe2\x80\x9d 表示可以不 调用它,new而 \xe2\x80\x9c 是可构造的\xe2\x80\x9d 表示可以用 new调用它。

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
功能种类例子可调用可施工this绑定
箭头功能() => {}是的
方法({ method(){} }).method是的是的
班级(class{})是的是的
function功能(function(){})是的是的是的
\n
\n

我们\xe2\x80\x99正在寻找的是一个可以被调用(没有new)并且有自己的this绑定的方法。\n我们可以使用functions,但是,看看现有的方法,不仅很new "HELLO".charAt();奇怪,而且\xe2\ x80\x99 无法工作!\n因此该方法也不应该是可构造的。\n因此,我们\xe2\x80\x99 正在寻找正确的方法。

\n

请注意,这显然取决于您的用例。\n例如,如果您想要一个可构造的函数,请务必使用class代替。

\n
代码示例
\n

我们回到之前的代码示例,而不是function(){}使用方法定义。

\n
// Define the method:\nObject.defineProperty(TheClass.prototype, "theMethod", {\n  writable: true,\n  configurable: true,\n  value: {\n    theMethod(){\n      // Do the thing.\n    }\n  }.theMethod\n});\n
Run Code Online (Sandbox Code Playgroud)\n

为什么要为 2. 和 3. 烦恼呢?

\n

可枚举性和可构造性考虑因素的目标是创建与现有内置方法具有相同 \xe2\x80\x9clook 和 Feel\xe2\x80\x9d 的东西。\n可以证明这些方法与简单实现之间的差异使用这个片段:

\n
class TheClass{}\n\nTheClass.prototype.theMethod = function(){};\n
Run Code Online (Sandbox Code Playgroud)\n

让\xe2\x80\x99s 将此与不同的内置方法进行比较,例如String.prototype.charAt

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n
代码片段幼稚的例子内置示例
thePrototypeTheClass.prototypeString.prototype
theMethodTheClass.prototype.theMethodString.prototype.charAt
for(const p in thePrototype){ console.log(p); }"theMethod"将在某个时刻被记录。"charAt"永远不会被记录。
new theMethod创建 的实例theMethodTypeError:theMethod不是构造函数。
\n
\n

使用第 2 和第 3 小节中的工具可以创建行为更像内置方法的方法。

\n

4. getter 和 setter

\n

另一种方法是使用 getter。考虑一下:

\n
const arr = [\n    "a",\n    "b",\n    "c",\n  ];\n\nconsole.log(arr.indexOfB); // 1\n
Run Code Online (Sandbox Code Playgroud)\n

indexOfB原型上的 getter会是什么样Array子?\n我们可以\xe2\x80\x99t 使用上述方法并替换valueget,否则我们\xe2\x80\x99 将得到:

\n
\n

TypeError:当指定 getter 或 setter 时,属性描述符不得指定值或可写

\n
\n

该属性writable需要从描述符中完全删除value。\n现在可以替换为get

\n
Object.defineProperty(Array.prototype, "indexOfB", {\n  configurable: true,\n  get: {\n    indexOfB(){\n      return this.indexOf("b");\n    }\n  }.indexOfB\n});\n
Run Code Online (Sandbox Code Playgroud)\n

set还可以通过向描述符添加属性来指定 setter :

\n
Object.defineProperty(Array.prototype, "indexOfB", {\n  configurable: true,\n  get: {\n    indexOfB(){\n      return this.indexOf("b");\n    }\n  }.indexOfB,\n  set: {\n    indexOfB(newValue){\n      // `newValue` is the assigned value.\n      // Use `this` for the current Array instance.\n      // No `return` necessary.\n    }\n  }.indexOfB\n});\n
Run Code Online (Sandbox Code Playgroud)\n

网络兼容性影响

\n

人们想要扩展内置原型有几个原因:

\n
    \n
  • 您自己有一个绝妙的主意,可以将新功能添加到您\xe2\x80\x99正在扩展的任何类的所有实例中,或者
  • \n
  • 您想要将现有的指定功能向后移植到旧版浏览器。
  • \n
\n

如果扩展目标对象,有两个选项需要考虑:

\n
    \n
  • 如果该属性不存在\xe2\x80\x99t,则提供它,否则,保留现有属性,或者
  • \n
  • 始终用您自己的实现替换该属性,无论它是否存在。
  • \n
\n

所有方法大多都有缺点:

\n

如果您或其他人发明了自己的功能,但出现了具有相同名称的标准方法,那么这些功能几乎肯定是不兼容的。

\n

如果您或其他人尝试实现标准功能,以便将其反向移植到不支持它的浏览器,但不要阅读规范和 xe2x80x9cguessxe2 x80\x9d 是如何工作的,那么 polyfilled 功能几乎肯定与标准实现不兼容。\n即使严格遵循规范,谁来确保跟上规范的更改?\n谁来负责可能出现的错误实施?

\n

如果您或其他人选择在覆盖该功能之前检查该功能是否存在,则有可能一旦使用支持该功能的浏览器的人访问您的页面,一切都会突然中断,因为实现结果不兼容。

\n

如果您或其他人无论如何都选择覆盖该功能,那么至少实现是一致的,但迁移到标准功能可能会很困难。\n如果您编写的库在其他软件中大量使用,那么迁移成本变得如此之大以至于标准本身必须改变;这就是为什么Array.prototype.contains必须重命名为Array.prototype.includes[Reddit] [ESDiscuss] [Bugzilla] [MooTools],并且Array.prototype.flatten无法使用,必须命名Array.prototype.flat[Pull request 1] [Pull request 2] [Bugzilla]。\n换句话说,这就是我们不能\xe2\x80\x99拥有好东西的原因

\n

此外,您的库可能无法与其他库互操作。

\n

备择方案

\n

最简单的替代方法是定义您自己的普通函数:

\n
const theMethod = (object) => object.theProperty; // Or whatever.\nconst theProperty = theMethod(theObject);\n
Run Code Online (Sandbox Code Playgroud)\n

您还可以考虑Proxy。\n这样您就可以动态查询属性并对其进行响应。\n让\xe2\x80\x99s 说您有一个具有属性的对象,a并且您希望通过以下z方式实现方法:getAgetZ

\n
const theProxiedObject = new Proxy(theObject, {\n    get(target, property, receiver){\n      const letter = property.match(/^get(?<letter>[A-Z])$/)?.groups?.letter.toLowerCase();\n      \n      if(letter){\n        return () => target[letter];\n      }\n      \n      return Reflect.get(target, property, receiver);\n    }\n  });\n\nconsole.assert(theProxiedObject.getB() === theProxiedObject.b);\n
Run Code Online (Sandbox Code Playgroud)\n

您还可以使用另一个类扩展您的 object\xe2\x80\x99s 原型,并使用它:

\n
class GetterOfB extends Object{\n  b;\n  constructor(init){\n    super();\n    Object.assign(this, init);\n  }\n  getB(){\n    return this.b;\n  }\n}\n\nconst theObject = new GetterOfB({\n    b: "c"\n  });\n\nconst theB = theObject.getB();\n
Run Code Online (Sandbox Code Playgroud)\n

您需要记住的只是不要修改您自己定义的\xe2\x80\x99t 内容。

\n