原型以及原型链的理解

176 阅读5分钟

参考文章原型和原型链--图解 - 掘金 (juejin.cn) 基于这篇文章,我加了一些自己的理解,文中的图解都是参考这篇文章。

一 前言

了解原型可以通过了解原型是什么,可以做什么来理解原型。

原型是什么:

  • 原型 是 JavaScript 中用于实现继承的核心机制之一。
  • 每个函数都具有一个 prototype(显示原型) 属性,它指向的对象就是原型对象
  • 每个对象都有一个内部属性叫做 [[Prototype]](隐式原型也就是__proto__),它指向实例对象构造函数的prototype

原型的作用:

  • 共享属性和方法:- 通过将属性和方法放在原型对象上,多个对象实例可以共享这些属性和方法,从而节省内存资源。
  • 实现继承:原型继承使得一个对象可以继承另一个对象的属性和方法,这种继承机制是动态的,意味着你可以随时修改原型对象来影响所有继承自该原型的对象实例。
  • 每个构造函数都有一个 prototype 属性,它是一个对象,包含可以被所有由该构造函数创建的对象所共享的属性和方法

二. 前置知识,构造函数是什么

在javascript中,构造函数本质上就是一个普通的 JavaScript 函数,但它的主要目的是用于创建新的对象实例。一个普通函数通过new构建了一个实例,那这个普通函数就是构造函数。

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


// 创建一个 Person 实例,这时候Person()就是构造函数
const john = new Person('John Doe', 25);

三.原型对象

在 JavaScript 中,每个函数都具有一个 prototype 属性,即使是普通的函数也不例外。这是因为 JavaScript 中的函数实际上也是对象。这意味着每个函数都可以作为构造函数使用,并且可以拥有一个 prototype 属性来定义其构造的对象实例所共有的属性和方法。

//普通函数Person
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 为 Person 的 prototype 添加一个 sayHello 方法
Person.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

// 创建一个 Person 实例
const john = new Person('John Doe', 25);

// 调用 sayHello 方法
john.sayHello(); // 输出: Hello, my name is John Doe and I am 25 years old.
console.log(john.constructor)//输出Person函数
console.log(Person.prototype.constructor)//输出Person函数

普通函数Person拥有prototype原型对象对象。同时原型对象有个constructor属性,这个属性就是原型对象的构造函数,再这里也就是Person函数。

看图 image.png

3.1.显示原型

显示原型就是prototype。

3.2.隐式原型(proto)或者([[Prototype]])

  • 概念:
  • 每个 JavaScript 对象都有一个内部的 [[Prototype]](也就是__proto__) 属性,它指向该对象的原型对象。
  • 当使用构造函数创建新对象时,新对象的 [[Prototype]] 会指向构造函数的 prototype 属性。
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 创建一个 Person 实例
const john = new Person('John Doe', 25);

// 访问 john 的 [[Prototype]]
console.log(john.__proto__); // 输出: Person.prototype
console.log(john.__proto__ === Person.prototype); // 输出: true

// 访问 Person.prototype 的 [[Prototype]]
console.log(Person.prototype.__proto__); // 输出: Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype); // 输出: true

图解:

image.png

四.原型链

  • 原型链的形成
  • 每个对象的 [[Prototype]] 属性指向它的原型对象。如果一个对象的原型对象也有一个 [[Prototype]] 属性,那么就形成了一个链式结构,称为原型链。最终,原型链会到达 Object.prototype,这是所有对象的最终原型对象,它的 [[Prototype]] 指向 null

图:

image.png

扩展object.prototype和object的介绍

object.prototype是什么

  • Object.prototype 是所有 JavaScript 对象的原型对象.
  • 它是所有对象的最终原型对象,意味着所有对象的原型链最终都会指向 Object.prototype
  • Object.prototype包含了一些通用的方法,如 toStringvalueOfhasOwnProperty 等,这些方法可以被所有对象实例使用

Object()

  • 在 JavaScript 中,Object() 是一个全局函数,用于创建一个新的对象。
const emptyObj = new Object(); // 创建一个空对象
const initObj = new Object({ name: 'John', age: 25 }); // 创建一个带有初始属性的对象

同时,在javascript中,函数也是对象,js中函数都是通过Funcation() 创建的实例。上面提到的Person()和Object()都是函数,所以都是Funcation() 的实例。那这样Person的__protp__就会指向构造函数Funcation的原型Funcation.prototype。Object()也是如此。然后Funcation.prototype也是对象,又因为 Object.prototype 是所有 JavaScript 对象的原型对象,所以Funcation.prototype的__proto__会指向Object.prototype

console.log(Person.__proto__===Function.prototype,'Person')//输出true

梳理上面的信息得到最终图

image.png

总结:

1.函数都有个prototype对象,用来放公共属性和方法。

2.实例对象的__proto__(或者[[prototype]])用来指向构造函数的prototype对象。

3.Object.prototype是原型链的顶点,是所有的函数及对象原型链最顶端,再往上是null。Object.prototype有很多内置函数,比如:当我们自己写的函数没有定义toString(),valueOf(),hasOwnProperty(key)等。

4.当我们自己写的函数没有定义toString()等方法,就顺着原型链找到Object.prototype.toString(),这就是原型链的作用。

关联问题:既然toString()、valueOf()是在原型对象上,为什么跟对象不同的变量能够使用toString()等方法?

举例:

let num=100 //这是一个变量不是对象
console.log(typeof num.toString())//输出string

如上,num是我们定义的一个变量,不是对象,却可以使用toString()方法。

首先我们要知道原始类型的变量(如数字、字符串、布尔值)本身不是对象,因此它们没有__proto__属性,所以原始类型的变量不能直接访问对象的方法。之所以能够调用.toString()方法是因为JavaScript 的自动装箱机制

自动装箱机制

javaScript 的自动装箱机制是一种自动将原始类型转换为对象类型的过程。这种机制使得原始类型的变量可以像对象一样使用方法。

自动装箱的触发条件:

  • 当你尝试在一个原始类型的变量上调用一个方法时,JavaScript 会自动将该原始类型的值装箱成对应的对象类型。

装箱过程:

  • JavaScript 会在后台创建一个临时的对象,并将原始类型的值赋给这个对象。
  • 然后,这个临时对象的方法会被调用。
  • 调用完成后,这个临时对象会被销毁,释放内存。

答案:当你调用 num.toString() 时,实际上是调用了 Object.prototype.toString 方法。