掌握 JS 中的对象🍨(第 2 部分)

2025-06-07

掌握 JS 中的对象🍨(第 2 部分)

对象和原型

与许多面向对象语言一样,JavaScript 支持实现继承:通过动态委托机制实现代码或数据的重用。但与许多传统语言不同,JavaScript的继承机制基于原型而非类。对于许多程序员来说,JavaScript是他们遇到的第一门没有类的面向对象语言。

在许多语言中,每个对象都是一个关联类的实例,关联类提供其所有实例之间共享的代码。相比之下, JavaScript没有内置的类概念。相反,对象继承自其他对象。每个对象都与某个其他对象关联,这些对象被称为原型。使用原型可能与使用类不同,尽管许多来自传统面向对象语言的概念仍然沿用。

prototype了解和getPrototypeOf之间的区别__proto__

原型包含三个独立但又相互关联的访问器,它们都以“原型”一词的变体命名。这种令人遗憾的重叠自然会造成不少困惑。让我们直奔主题。

  • C.prototype用于建立new C()创建的对象原型。
  • Object.getPrototypeOf(obj)是检索原型对象的标准ES5机制。obj
  • obj.__proto__是一种检索原型obj对象非标准机制

为了理解这些,请考虑 JavaScript 数据类型的典型定义。User 构造函数需要使用 new 运算符调用,并接受名称和密码字符串的哈希值,并将
它们存储在其创建的对象中。

function User(name, passwordHash) {
  this.name = name;
  this.passwordHash = passwordHash;
}

User.prototype.toString = function () {
  return "[User " + this.name + "]";
};

User.prototype.checkPassword = function (password) {
  return hash(password) === this.passwordHash;
};

let u = new User("sfalken", "0ef33ae791068ec64b502d6cb0191387");
Enter fullscreen mode Exit fullscreen mode

User函数带有一个默认的原型属性,其中包含一个初始状态几乎为空的对象User.prototype。在本例中,我们向该对象添加了两个方法:toString和。checkPassword.当我们使用 new 运算符创建 User 实例时,生成的对象 u 会自动将存储在 处的对象User.prototype
赋值为其原型对象。下图展示了这些对象的示意图。

注意连接实例对象 u 和原型对象 的箭头
User.prototype。此链接描述了继承关系。
属性查找从搜索 的 开始objectown properties例如,u.name,并u.passwordHash返回 u 的直接属性的当前值。在 u 上无法直接找到的属性将在 u 的原型中查找。u.checkPassword例如,访问 会检索存储在 中的方法User.prototype

这引出了我们列表中的下一个项目。构造函数的prototype属性用于设置新实例的原型关系,而ES5函数Object.getPrototypeOf()
用于检索现有对象的原型。例如,在上面的示例中创建对象u之后,我们可以测试:

Object.getPrototypeOf(u) === User.prototype; // true
Enter fullscreen mode Exit fullscreen mode

这说明了User构造函数实例的原型关系。

某些环境会生成一种非标准机制,通过特殊属性来获取对象的原型
对于不支持ES5的环境来说可以作为一种权宜之计。在这样的环境中,我们可以类似地测试:__proto__
Object.getPrototypeOf

u.__proto__ === User.prototype; // true
Enter fullscreen mode Exit fullscreen mode

关于原型关系的最后一点说明JavaScript程序员经常将 User 描述为一个类,即使它只包含一个函数。JavaScript 中的类本质上是一个构造函数(User)和一个原型对象的组合,原型 对象用于在类的实例之间共享方法User.prototype

这是用户“类”的概念视图。

上图提供了一种从概念上理解 User的好方法。User函数为该类提供了一个公共的构造函数User.prototype,并且是实例之间共享方法的内部实现。User 和 u 的常规使用无需直接访问原型 对象。

要记住的事情🧠

  1. C.prototype确定由 new 创建的对象的原型C()
  2. Object.getPrototypeOf(obj)是用于检索对象原型标准 ES5 函数
  3. obj.__proto__是一种检索对象原型非标准机制。
  4. 类是一种由构造函数和相关原型组成的设计模式

Object.getPrototypeOf喜欢__proto__🦄

ES5引入了Object.getPrototypeOf用于检索对象原型的标准API,但在此之前,许多JavaScript引擎早已提供了__proto__用于相同目的的特殊属性。然而,并非所有JavaScript环境都支持此扩展,即使支持,也并非完全兼容。例如,不同环境对原型为空的对象的处理方式有所不同。在某些环境中,proto继承自Object.prototype,因此原型为空的对象没有特殊的proto属性:

var empty = Object.create(null); // object with no prototype
"__proto__" in empty; // false (in some environments)
// In  others,  __proto__  is  always  handled  specially,  regardless  of  an object’s state:

var empty = Object.create(null); // object with no prototype
"__proto__" in empty; // true (in some environments
Enter fullscreen mode Exit fullscreen mode

只要Object.getPrototypeOf可用,它就是提取原型更标准、更便携的方法。此外,__proto__由于该属性会污染所有对象,因此会导致许多错误
。目前支持该扩展的 JavaScript 引擎将来可能会选择允许程序禁用它,以避免这些错误。优先使用Object.getPrototypeOf可以确保即使__proto__禁用了它,代码也能继续工作。

对于没有提供ES5 API 的JavaScript环境,可以通过以下方式轻松实现__proto__

if (typeof Object.getPrototypeOf === "undefined") {
  Object.getPrototypeOf = function (obj) {
    var t = typeof obj;
    if (!obj || (t !== "object" && t !== "function")) {
      throw new TypeError("not an object");
    }
    return obj.__proto__;
  };
}
Enter fullscreen mode Exit fullscreen mode

此实现可以安全地包含在 ES5 环境中,因为它避免在Object.getPrototypeOf已存在的情况下安装该函数。

要记住的事情🧠

  1. 优先选择符合标准的属性,Object.getPrototypeOf而不是非标准
    __proto__属性。

  2. 在支持的Object.getPrototypeOf非 ES5 环境中实现

    __proto__

切勿修改__proto__🍕

特殊的proto属性提供了一项额外的功能Object.getPrototypeOf:修改对象原型链接。虽然这种功能看似无害(毕竟,它只是另一个属性,对吧?),但它实际上会带来严重的影响,应该避免。避免修改的最明显原因是可移植性:由于并非所有平台都支持 更改对象原型的功能,
__proto__因此您根本无法编写可移植的代码来实现这一点。

避免修改的另一个原因__proto__是性能。所有现代JavaScript引擎都对获取和设置对象 属性的操作进行了深度优化,因为这些是 JavaScript 程序执行的最常见操作之一。这些优化建立在引擎对对象结构的认知之上。当你更改对象的内部结构时,例如,通过向对象或其原​​型链中的对象添加或删除属性,其中一些优化将失效。修改__proto__实际上会改变继承结构本身,这是最具破坏性的更改。与修改普通属性相比,这会使更多的优化失效。

但避免修改的最大原因__proto__是为了保持可预测的行为。对象的原型链通过确定其属性集和属性值来定义其行为。修改对象的原型链接就像给它做一次大脑移植:它会交换对象的整个继承层次结构。或许可以想象,在特殊情况下,这样的操作可能会有所帮助,但出于基本的理性考虑,继承层次结构应该保持稳定。

要使用自定义原型链接创建新对象,可以使用ES5Object.create。对于未实现 ES5 的环境,Item 33 提供了一个Object.create不依赖于 的可移植实现__proto__

要记住的事情🧠

  1. 永远不要修改对象的__proto__属性。
  2. 用于为新对象Object.create提供自定义原型。

🎉🎉🎉 感谢您阅读本文的第二部分!🎉🎉🎉

别忘了看看这个系列的第三部分!🥳让你的构造函数与 new-Agnostic 兼容

如果您想更深入地了解您最喜欢的编程语言,请查看我的个人博客,成为一名按需开发人员😉,您也可以在Twitter上找到我😃。

文章来源:https://dev.to/jrmatanda/master-objects-in-js-part-2-2fnn
PREV
如何逃离教程世界
NEXT
JS 中的主对象🍨(第一部分)