天天看点

JavaScript【对象原型】

原型是JavaScript对象相互继承功能的机制。在本文中,我们将解释什么是原型,原型链如何工作,以及如何设置对象的原型。

先决条件: 了解 JavaScript 函数,熟悉 JavaScript 基础知识(请参阅第一步​和构建块​)和 OOJS 基础知识(请参阅对象简介)。
目的: 了解 JavaScript 对象原型、原型链的工作原理以及如何设置对象的原型。

原型链

在浏览器的控制台中,尝试创建一个对象文本:

const myObject = {
  city: 'Madrid',
  greet() {
    console.log(`Greetings from ${this.city}`);
  }
}

myObject.greet(); // Greetings from Madrid      

复制到剪贴板

这是一个具有一个数据属性 和一个方法的对象。如果在控制台中键入对象的名称,后跟句点,例如 ,则控制台将弹出此对象可用的所有属性的列表。你会看到以及和,还有很多其他属性!​

​city​

​​

​greet()​

​​

​myObject.​

​​

​city​

​​

​greet​

__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
city
constructor
greet
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
toValueOf      

尝试访问其中之一:

myObject.toString(); // "[object Object]"      

复制到剪贴板

它有效(即使它不明显是什么)。​

​toString()​

这些额外的属性是什么,它们来自哪里?

JavaScript中的每个对象都有一个内置属性,称为其原型。原型本身就是一个对象,因此原型将有自己的原型,从而形成所谓的原型链。当我们到达一个拥有自己的原型的原型时,链条就结束了。​

​null​

注意:指向其原型的对象的属性不称为 。它的名称不是标准的,但实际上所有浏览器都使用​​​__proto__​​​。访问对象原型的标准方法是 ​​​Object.getPrototypeOf()​​​ 方法。​

​prototype​

当您尝试访问对象的属性时:如果在对象本身中找不到该属性,则会在原型中搜索该属性。如果仍然找不到该属性,则搜索原型的原型,依此类推,直到找到该属性或到达链的末端,在这种情况下返回。​

​undefined​

因此,当我们调用 ,浏览器时:​

​myObject.toString()​

  • 寻找​

    ​toString​

    ​​

    ​myObject​

  • 在那里找不到它,所以在原型对象中查找​

    ​myObject​

    ​​

    ​toString​

  • 在那里找到它,并调用它。

原型是什么?要找出答案,我们可以使用以下函数:​

​myObject​

​​

​Object.getPrototypeOf()​

Object.getPrototypeOf(myObject); // Object {...}      

复制到剪贴板

这是一个名为 的对象,它是所有对象默认具有的最基本的原型。的原型是 ,所以它位于原型链的末尾:​

​Object.prototype​

​​

​Object.prototype​

​​

​null​

对象的原型并不总是 。试试这个:​

​Object.prototype​

const myDate = new Date();
let object = myDate;

do {
  object = Object.getPrototypeOf(object);
  console.log(object);
} while (object);

// Date.prototype
// Object {...}
// null      

复制到剪贴板

此代码创建一个对象,然后沿原型链向上移动,记录原型。它向我们表明,的原型是一个对象,而它的原型是 。​

​Date​

​​

​myDate​

​​

​Date.prototype​

​​

​Object.prototype​

实际上,当您调用熟悉的方法(如 )时,您正在调用在 上定义的方法。​

​myDate2.getMonth()​

​​

​Date.prototype​

重影属性

如果在对象的原型中定义了具有相同名称的属性,则在对象中定义了属性,会发生什么情况?我看看:

const myDate = new Date(1995, 11, 17);

console.log(myDate.getYear()); // 95

myDate.getYear = function() {
  console.log('something else!')
};

myDate.getYear(); // 'something else!'      

复制到剪贴板

鉴于原型链的描述,这应该是可预测的。当我们调用浏览器时,首先查找具有该名称的属性,并且仅在未定义原型时才检查原型。因此,当我们添加到 时,将调用 中的版本。​

​getYear()​

​​

​myDate​

​​

​myDate​

​​

​getYear()​

​​

​myDate​

​​

​myDate​

这称为“阴影”属性。

设置原型

在 JavaScript 中设置对象原型的方法有很多种,在这里我们将介绍两种:和构造函数。​

​Object.create()​

使用 Object.create

该方法将创建一个新对象,并允许您指定将用作新对象原型的对象。​

​Object.create()​

下面是一个示例:

const personPrototype = {
  greet() {
    console.log('hello!');
  }
}

const carl = Object.create(personPrototype);
carl.greet();  // hello!      

复制到剪贴板

在这里,我们创建一个对象,它有一个方法。然后,我们使用创建一个新对象作为其原型。现在我们可以调用新对象,原型提供其实现。​

​personPrototype​

​​

​greet()​

​​

​Object.create()​

​​

​personPrototype​

​​

​greet()​

使用构造函数

在 JavaScript 中,所有函数都有一个名为 .将函数作为构造函数调用时,此属性设置为新构造对象的原型(按照约定,在名为 的属性中)。​

​prototype​

​​

​__proto__​

因此,如果我们设置构造函数,我们可以确保使用该构造函数创建的所有对象都给定该原型:​

​prototype​

const personPrototype = {
  greet() {
    console.log(`hello, my name is ${this.name}!`);
  }
}

function Person(name) {
  this.name = name;
}

Person.prototype = personPrototype;
Person.prototype.constructor = Person;      

复制到剪贴板

在这里,我们创建:

  • 一个对象,它有一个方法​

    ​personPrototype​

    ​​

    ​greet()​

  • 一个构造函数,用于初始化要创建的人的姓名。​

    ​Person()​

然后,我们将函数的属性设置为指向 。​

​Person​

​​

​prototype​

​​

​personPrototype​

最后一行 () 将原型的属性设置为用于创建对象的函数。这是必需的,因为在设置属性后,该属性指向 的构造函数,而不是(因为被构造为对象文本)。​

​Person.prototype.constructor = Person;​

​​

​constructor​

​​

​Person​

​​

​Person.prototype = personPrototype;​

​​

​personPrototype​

​​

​Object​

​​

​Person​

​​

​personPrototype​

在此代码之后,使用 创建的对象将作为其原型。​

​Person()​

​​

​personPrototype​

const reuben = new Person('Reuben');
reuben.greet(); // hello, my name is Reuben!      

复制到剪贴板

这也解释了为什么我们之前说过的原型叫做:它是构造函数的属性。​

​myDate​

​​

​Date.prototype​

​​

​prototype​

​​

​Date​

自己的房产

我们使用上面的构造函数创建的对象有两个属性:​

​Person​

  • 一个属性,它在构造函数中设置,因此它直接出现在对象上​

    ​name​

    ​​

    ​Person​

  • 在原型中设置的方法。​

    ​greet()​

通常可以看到这种模式,其中方法在原型上定义,但数据属性在构造函数中定义。这是因为对于我们创建的每个对象,方法通常是相同的,而我们通常希望每个对象都有自己的数据属性值(就像这里每个人都有不同的名称一样)。

直接在对象中定义的属性(如此处所示)称为自己的属性,您可以使用静态 ​​​Object.hasOwn()​​​ 方法检查属性是否为自己的属性:​

​name​

const irma = new Person('Irma');

console.log(Object.hasOwn(irma, 'name')); // true
console.log(Object.hasOwn(irma, 'greet')); // false      

复制到剪贴板

注意:您也可以在此处使用非静态 ​​​Object.hasOwnProperty()​​​ 方法,但我们建议您尽可能使用。​

​Object.hasOwn()​

原型和继承

原型是JavaScript的一个强大且非常灵活的功能,可以重用代码和组合对象。

特别是它们支持一个版本的继承。继承是面向对象编程语言的一个功能,它允许程序员表达这样一种想法,即系统中的某些对象是其他对象的更专业版本。

例如,如果我们对一所学校进行建模,我们可能有教授和学生:他们都是人,所以有一些共同的特征(例如,他们都有名字),但每个特征都可能增加额外的特征(例如,教授有一门他们教的科目),或者可能以不同的方式实现相同的特征。在OOP系统中,我们可以说教授和学生都从人那里继承。

总结

继续阅读