likes
comments
collection
share

深入理解对象的原型与原型链

作者站长头像
站长
· 阅读数 23

深入理解对象的原型与原型链

一、原型

JavaScript原型是每个对象都具有的属性,它指向一个对象,我们通常称之为原型对象。

当我们访问一个对象的属性或方法时,JavaScript 引擎首先会在该对象自身查找,如果找到则直接使用。如果找不到,则会去该对象的原型对象中查找。

下面一个简单的例子来说明原型的概念:

深入理解对象的原型与原型链

// 创建一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 在构造函数的原型对象上定义方法
Person.prototype.Hello = function() {
  console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
}

// 创建一个对象
var person1 = new Person("Alice", 25);

// 调用对象的方法
person1.sayHello(); // 输出:Hello, my name is Alice and I am 25 years old.

// person1 对象没有自己的 sayHello 方法,但它可以通过原型链找到 Person 构造函数的原型对象,并调用原型对象上的方法。

在上面的例子中,我们定义了一个构造函数 Person,它有两个属性 nameage,并在构造函数的原型对象上定义了一个方法 sayHello。然后我们通过 new 关键字创建了一个对象 person1,然后就可以通过 person1.sayHello() 来调用原型对象上的方法。

这里需要注意的是,所有通过 new 关键字创建的对象都会共享同一个原型对象,即它们的原型指向的是同一个对象。

这就是 JavaScript 原型的基本概念和用法,它是实现继承和共享属性和方法的重要机制。

二、原型链

JavaScript中的原型链是基于对象的继承机制,它通过每个对象都具有一个指向其原型对象的内部链接来实现。

当我们访问一个对象的属性或方法时,如果该对象本身该属性或方法,JavaScript引擎会沿着对象的型链一直向上找,直到找到或者到达原型链的末端。如果最终还没有到,则返回undefined

深入理解对象的原型与原型链

下是一个简单的例子来说明型链的概念:

// 定义一个构造函数
function Animal(name) {
    this.name = name;
}

// 在构造函数的原型对象上定义方法
Animal.prototype.eat = function(food){
    console.log(this.name + " is eating " + food);
};

// 定一个子构造函数
function Cat(name {
    this.name = name;
}

// 创建一个父对象实例
var animal = new Animal("Tom// 将父对象实例为子对象的原型
Cat.prototype = animal;

// 创建一个子对象实例
var cat = new Cat("Kitty");

// 调用子对象的方法
cat.eat("fish");  // 输出:Kitty is eating fish

在上面的例子中,我们定义了一个构造函数 Animal 和一个子构造函数 Cat。我们创建了一个父对象实例 animal,并在它的原型对象上定义了一个方法 。然后我们将父对象实例 animal 设置为子对象 Cat 的原型对象,这样子对象 cat 就可以通过原型链访问到父对象 animal 的属性和方法。

当我们调用 cat.eat("fish") 时,由于 cat 对象本身没有 eat 方法,JavaScript引擎会沿着原型链逐级向查找,找到父对象 animaleat 方法,并执行。

这就是 JavaScript 中原型链的基本原理:对象通过原型链实现属性和方法的承,每个对象通过内部链接指向其原型对象,形成一个链条。

需要注意的是,原型链是单向的,子对象可以访问父对象属性和方法,但父对象不能访问子对象的属性和方法。

深入理解对象的原型与原型链

为了更清晰地理解,这里将原型相关的内容画了一张图。图中从任意一个对象出发,都可以顺着原型属性 __proto__ 找到一条原型链,对象属性的查找正是遵循原型链的

从上图我们可以得知,prototype对象有个constructor对象,指向原构造函数。函数的原型对象和函数本身通过prototype属性和constructor属性形成一个循环引用

三、隐式原型和显式原型

隐式原型和显式原型是原型链中的两个重要概念。

  1. 隐式原型(__proto__):每个对象都有一个隐式原型,它指向创建该对象的构造函数的原型对象。我们可以通过obj.__proto__来访问对象的隐式原。

  2. 显式原型(prototype):每个构造函数都有一个显式原型,它是一个对象,用于存储该构造函数创建的对象共享的属性和方法。我们可以通过constructor.prototype来访问构造函数的显式原型。

区别:

  • 隐式原型是每个对象都具有的属性,它是对象与构造函数之间的连接。而显式原型是构造函数才具有的属性,它定义了构造函数的实例对象共享的属性和方法。

  • 隐式原型指向的是创建该对象的构造函数的原型对象,而显式原型指向的是构造函数的原型对象本身。

  • 通过修改显式原型可以影响构造函数创建的所有对象的属性和方法,而修改隐式原型只会影响一个对象的原型链。

下面通过代码来说明隐式原型和显式原型的概念:

// 定义构造函数
function Person(name, age) {
    this.name = name;// 定义name属性
    this.age = age; // 定义age属性
}

// 在构造函数的显式原型上定义一个方法
Person.prototype.sayHello = function() {
    console.log("Hello, my name is " +.name);
};

// 创建一个对象实例
var1 = new Person("Alice", 25);

// 访问对象的属性和方法
console.log(person1.name); // 输出:Alice
person1.sayHello(); // 输出:Hello, my name is Alice

// 访问对象的隐式原型和显式原型
console.log(person1.__proto__); // 指向构造函数的显式原型对象
console.log(Person.prototype); // 输出:构造函数的显式原型对象
console.log(person1.__proto__ === Person.prototype); // 输出:true

在上面的例子中,我们定义了一个构造函数 Person,并在它的显式原型上定义了一个方法 sayHello。然后通过 new 关键字创建了一个对象实例 person1

我们可以通过 person1.name 访问对象的属性,通过 person1.sayHello() 调用对象的方法。

由于对象 person1 是通过构造函数 Person 创建的,因此它的隐式原型指向的是 Person 构造函数的显式原型对象。我们通过 person1.__proto 或者 Person.prototype 访问到显式原型对象。

需要注意的是,__proto__ 是一个非标准的属性,尽量避免在实际开发中直接使用它,而是使用 Object.getPrototypeOf() 或者 Object.setPrototypeOf() 来操作原型。

三、构造器constructor

构造器(constructor)是 JavaScript 中一种特殊的方法,用于创建和初始化对象。它通常用于构造函数(Constructor Function)中。

构造函数是一种特殊的函数,用于创建对象。构造函数通常以大写字母开头,这是为了与普通函数做区分。构造函数除了用于创建对象外,还可以定义实例对象的属性和方法。

构造器(constructor)是构造函数中的一个特殊方法,用于实例化对象时进行初始化操作。构造器的作用是在创建新对象的同时,给这个对象设置初始的属性和方法。

深入理解对象的原型与原型链

下面是一个示例,用于演示构造函数和构造器的概念和用法:

// 定义一个构造函数
function Person(name, age) {
  // 构造器(constructor),用于初始化对象
  this.name = name;
  this.age = age;

  // 定义实例方法
  this.sayHello = function() {
    console.log("Hello, my name is " + this.name  };
}

// 创建实例对象
var person1 = new Person("Alice", 25);
var person2 = new Person("Bob", 30);

// 调用实例方法
person1.sayHello(); // 输出:Hello, my name is Alice
person2.sayHello(); // 输出:Hello, my name is Bob

在上面的示例中,我们定义了一个构造函数 Person,它接受两个参数 nameage,并在构造器中分别给实例对象设置了 nameage 属性。

通过 new 关键字,我们可以实例化 Person 构造函数,创建了两个实例对象 person1person2。每个实例对象都有自己独立的 nameage 属性。

构造函数中定义了一个实例方法 sayHello,通过调用实例对象的 sayHello 方法,实例对象可以打印出自己的名字。

需要注意的是,每个实例对象都会有一个独立的 sayHello 方法,这可能会导致内存占用较大。为了避免这个问题,我们可以使用原型方法的方式来定义方法,从而实现属性和方法的共享,提高代码内存利用率。例如:

// 定义一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 定义实例方法
Person.prototype.sayHello = function() {
  console.log("Hello, my name is " + this.name);
};

这样,每个实例对象共享一个 sayHello 方法,避免了重复创建实例方法的问题。

总结起来,构造器(constructor)是构造函数中用于初始化对象的特殊方法。它在实例化对象时被调用,通过构造器可以给对象设置初始的属性和方法。构造函数和构造器在 JavaScript 开发中非常常见,有助于实现对象的创建和初始化。

四、三个重要属性 (__proto__prototypeconstructor

在 JavaScript 中,__proto__prototypeconstructor 是三个与原型链密切相关的属性或特性。

  1. __proto__ 是一个非标准的属性,它是对象与构造函数之间的连接,它指向创建该对象的构造函数的原型对象。可以通过 obj.__proto__ 来访问对象的隐式原型。

  2. prototype 是函数对象所特有的属性,它是一个对象,用于存储该构造函数创建的对象共享的属性和方法。例如,通过 Person.prototype 可以定义 Person 构造函数创建的实例对象共享的方法。

  3. constructor 是原型对象上的一个属性,它指向创建该对象的构造函数。,Person.prototype.constructor 指向 Person 构造函数本身。

它们之间的关系如下:

  • 对象的 __proto__ 属性指向所属构造函数的 prototype 属性,即对象的隐式原型指向构造函数的显式原型。
  • 构造函数的 prototype 属性是一个普通对象,它具有 constructor 属性,指向创建该对象的构造函数自身。
  • 构造函数创建的通过隐式原型与构造函数的显式原型相连接,形成一个原型链。

举例来说明:

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

// 通过构造函数的显式原型定义方法
Person.prototype.sayHello = function() {
  console.log("Hello, my name is " + this.name);
};

var person1 = new Person("Alice");
person1.sayHello(); // 输出:Hello, my name is Alice

// 隐式原型和显式原型的关系
console.log(person1.__proto__ === Person.prototype); // 输出:true

// 构造函数和原型对象关系
console.log(person1.constructor === Person); // 输出:true
console.log(Person.prototype.constructor === Person); // 输出:true

在上面的例子中,通过构造函数 Person 和其原型对象 Person.prototype 实现了对象 person1 的属性和方法的共享。

person1.__proto__ 指向构造函数 Personprototype,因此隐式原型和显式原型是相连的。

person1.constructor 指向 Person 构造函数本身,而 Person.prototype.constructor 也指向 Person 构造函数。

这种关系形成了一个完整的原型链,实现了属性和方法的继承和共享。

总结

原型和原型链是 JavaScript 中非常重要的概念,理解它们的作用对于开发和理解 JavaScript 代码非常重要。

  1. 原型的作用:

    • 实现属性和方法的共享:通过原型,可以将对象的属性和方法存储在原型对象中,从而实现多个对象之间的属性和方法的共享,避免了在每个对象实例中复制相同的属性和方法,节省了内存空间。
    • 实现对象的继承:通过原型链,可以创建对象之间的继承关系,子对象可以继承父对象的属性和方法,并在此基础上进行扩展,实现了对象之间的继承和多态特性。
  2. 原型链的作用:

    • 属性和方法的查找:当访问对象的属性或方法时,首先在自身对象中查找,如果找不到,则沿着原型链向上查找,直到找到该属性或方法为止。这样可以实现属性和方法的继承和共享,提高代码的重用性。
    • 原型链的终点是 Object.prototype,它是所有对象的基础原型,包括 JavaScript 内置对象和自定义对象。

为什么要理解掌握原型和原型链:

  • JavaScript 是一门基于原型和原型链的面向对象的语言,理解原型和原型链是深入理解 JavaScript 语本身的必备基础。
  • 理解原型和原型链可以更好地理解和使用 JavaScript 的核心特性,如继承、原型继承、对象创建等。
  • 原型和原型链是深入学习和理解 JavaScript 高级特性的前提基础,如闭包、作用域、模块化等。
  • 在实际开发中,原型和原型链相关的知识经常用于设计和构建复杂的 JavaScript 应用、设计模式和代码优化。

总而言之,掌握原型和原型链的概念和使用方法,可以更好地理解和使用 JavaScript,提高代码的可维护性和重用性,更加灵活地进行对象创建和属性方法的管理。