首页/文章/javascript

一文带你深入理解 JavaScript 中的原型与原型链

2024-03-06
19407 分钟
...

先使用构造函数创建一个对象:

function Person() {}
let person = new Person()
person.name = 'Janny'

在上述例子中,我们使用构造函数 Person​,通过 new ​创建了一个实例对象 person​。

原型 prototype​ 上的方法是给实例对象使用的。

每个函数(只有函数才会有)都有一个 prototype ​属性,其指向一个对象,这个对象就是该构造函数创建的实例对象的原型(通过 p.__proto__ ​指向,后续会详细讲解)。

function Person() {}

Person.prototype.name = 'Janny'
let person1 = new Person()
let person2 = new Person()
console.log(person1.name) 
console.log(person2.name) 


Person.prototype === person1.__proto__  
Person.prototype === person2.__proto__  

prototype ​指向的通常是一个 Object ​空对象,我们可以手动为其添加属性和方法,例如:

Person.prototype.name = 'Danny'
Person.prototype.doing = function() {}

构造函数的 prototype ​属性和实例对象的原型的关系为:

prototype

构造函数 Person

实例对象person 的原型

每个 JavaScript 对象(除了 null)都具有 __proto__ ​属性,这个属性会指向该对象的原型。

function Person() {}


let person = new Person()


person.__proto__ === Person.prototype  

关系如下:

prototype

通过 new 创建了

_proto_

构造函数 Person

实例对象person 的原型

实例对象person

实例对象的原型上有一个 constructor ​属性,它指向关联的构造函数本身。

function Person() {}

Person.prototype.constructor === Person  

let person = new Person()

person.__proto__.constructor = Person  


Object.getPrototypeOf(person) === Person.prototype  

关系如下:

prototype

constructor

通过 new 创建了

_proto_

构造函数 Person

实例对象person 的原型

实例对象person

每个构造函数都有一个 prototype​,即显式原型(属性) 每个实例对象都有一个 __proto__​,可称为隐式原型(属性) 显式原型属性与隐式原型属性都是一个地址值,二者均指向了同一个对象,就是上面说的那个实例对象的原型。

function Person() {}  
console.log(Person.prototype)  

let person = new Person()  
console.log(person.__proto__)  

console.log(Person.prototype === person.__proto__)  

原型属性都是 JS 引擎自动添加的

显式原型的创建过程:

  • 当构造函数创建的时候会自动添加一个 prototype ​属性
  • 在内存中创建一个空对象(实例对象的原型)
  • prototype ​属性指向这个(原型)空对象

隐式原型的创建过程:

  • 当通过构造函数创建实例对象的时候自动添加了一个 __proto__ ​属性
  • prototype ​中存的地址值赋值给 __proto__​,此时二者指向了同一个对象

prototype

_proto_

构造函数 Person

一个JS对象
该对象为实例的原型

实例对象person

function Person() {
  this.doing = function() {
    console.log('方法doing')
  }
}
Person.prototype.todo = function() {
  console.log('方法todo')
}

let person = new Person()
person.ownFun = function() {
  console.log('自身方法ownFun')
}

person.ownFun()  



person.doing()  
person.todo()  



person.toString()  


person.done()  

原型链访问:原型链是沿着隐式原型来查找的 原型链作用:查找对象的属性(方法)

访问一个对象属性的时候:

  1. 先在自身属性中查找,找到了就返回
  2. 如果没有找到,再沿着 __proto__ ​隐式原型链向上查找,找到了就返回
  3. 如果最终没有找到,返回 undefined

_proto_

prototype

_proto_

_proto_

实例对象person
方法ownFun

实例对象的原型
方法todo 方法doing

Object构造函数

JS中所有对象的原型
Object相关的实例方法

原型链的尽头 null

所有对象都有一个 __proto__ ​隐式原型属性,指向了构造函数 Function ​的 prototype ​显式原型属性,即==实例对象的隐式原型等于其构造函数的显式原型==。

由于 JavaScript 中一切皆对象,所以 Function ​既是构造函数又是实例对象,也就是说,Function 也是通过 new Function()​ 创建而来的。

所有构造函数(构造函数也是对象)的 __proto__ ​属性都是一样的,因为所有构造函数都是 Funciton ​通过 new 创建而来的,所以都指向了 Function ​的显式原型 prototype ​属性。

function Person() {}
function Car() {}

Person.__proto__ === Car.__proto__

Person.__proto__ === Function.prototype
Car.__proto__ === Function.prototype

因为构造函数 Object ​是 Function ​通过 new Function() ​创建而来的,所以 Object ​相当于实例对象Function ​是该实例对象的构造函数。

所以:

Object.__proto__ === Function.prototype

这和原型链的尽头是不一样的

Object.prototype.__proto__ === null

 

 

构造函数的显示原型 prototype ​指向的对象默认是空的 Object的实例对象​,但是构造函数 Object ​的显示原型 prototype ​并不满足这一点,通过下述代码对比可以发现:普通构造函数的 prototype ​属性指向的对象和 Object ​的 prototype ​属性指向的对象是不一样的。

function Person() {}

Person.prototype instanceof Object  

null instanceof Object  

Object.prototype instanceof Object  

所有的函数都是 Function ​的实例(包括 Function ​自身)

Function instanceof Function  

所有的函数都是 Function ​的实例,而 Function ​的实例的原型都是构造函数 Object ​的实例。


Object instanceof Object  

下面四条语句均是正确的,通过上面的原型链示意图就可以很轻松地理解了。

Function instanceof Function  
Function instanceof Object  
Object instanceof Object  
Object instanceof Function  

instanceof ​是如何判断的?

  • 表达式:obj instanceof Fun​ 其中 obj​ 是实例对象,Fun​ 是构造函数
  • 如果 Fun ​构造函数的显式原型对象在 obj​ 实例对象的隐式原型链上,返回 true​,否则返回 false​ 即 Fun ​只走到 Fun.prototype ​就不走了,而 obj ​要沿着隐式原型链 obj.__proto__.__proto__...... ​一直走下去,直到终点。

实例对象的隐式原型属性对象等于构造函数的显式原型属性对象。

Function ​是通过 new ​自身产生的实例。

function Person(){}
let person = new Person()


person instanceof Person  


person instanceof Object  
Object instanceof Function  



Object instanceof Object    



Function instanceof Function  



Function instanceof Object    


function Foo(){}
Object instanceof Foo     

 

 

例题一

function A(){}
A.prototype.n = 1
let b = new A()

A.prototype = {
  n:2,
  m:3
}

let c = new A()

b.n  
b.m  
c.n  
c.m  

实例一旦创建,其原型就已经确定了,后续再修改原型不会影响到已经创建的实例。

因为当创建 b 之后,b.__proto__ ​已经指向了 A.prototype ​这个实例对象,即此时这个实例对象有两个指针指向着,分别是 b.__proto__ ​和 A.prototype​,后续进行了 A.prototype={n:2,m:3} ​操作,让 A.prototype ​指向了一个新的实例对象。但是这个操作并没有修改原来的实例对象,此时原来的实例对象还有一个指针指着呢,那就是 b.__proto__​。所以实例对象 b 的原型还是原来的。 而新创建的实例对象 c ,其在创建前原型已经进行了修改,故创建 c 的时候,c.__proto__ ​指向的是修改后的原型对象。

例题二

let Foo = function(){}
Object.prototype.a = function(){
  console.log('obj_a()')
}
Function.prototype.b = function(){
  console.log('fun_b()')
}
let f = new Foo()

f.a()  
f.b()  

Foo.a()  
Foo.b()  

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-10
手把手教你实现一个EventEmitter
你是不是也遇到过这种情况:代码里各种事件监听和触发,回调函数套了一层又一层,最后自己都理不清哪个事件先触发、哪个后触发了? 别担心,今天我就带你从零开始实现一个自己的事件触发器(EventEmitter),让你彻底掌握事件驱动的精髓,代码清晰度直接提升200%! 为什么要懂EventEmitter? 先说说为什么我们要关心这个。Node.js的核心就是事件驱动,比如文件读写、网络请求,都是通过事件来处理的。不会EventEmitter,...
学习面试
javascript2025-08-09
Web Worker 多线程魔法
你是否曾遇到过网页因为复杂计算而卡顿的情况?今天,让我们一起探索Web Worker的神奇世界,看看如何用多线程技术解决性能瓶颈,并实现流畅的图片压缩功能! 为什么需要Web Worker? 想象一下,你在一个繁忙的餐厅里,只有一位服务员(主线程)负责点餐、上菜、结账所有工作。当客人突然暴增时,这位服务员就会手忙脚乱,其他客人只能干等着… 这就是JavaScript的现状—— 单线程 !所有任务都在一个线程中排队执行,一旦遇到复杂计算(...
学习面试
javascript2025-08-07
🌐AST抽象语法树
AST 是前端开发中一个非常重要的概念,它就像是代码的“编译器”,能够帮助我们理解和操作代码。本文将深入探讨 AST 的原理和应用,带你从零开始了解 AST。 一、什么是 AST? AST(抽象语法树)是一种树形结构,用于表示程序代码的语法结构。它将代码分解为一系列的节点,每个节点代表代码中的一个元素,如变量、函数、表达式等。AST 是编译器和解释器的基础,也是代码分析和转换的关键工具。 1\. AST 的结构 AST 的结构通常包括以...
学习面试
javascript2025-08-05
打字机效果是如何实现的
分析效果 使用 AI 对话的时候,文字是一个字一个字蹦出来的?它不是等所有内容生成完毕后一次性展示,而是以一种流畅、实时的“打字机”效果呈现,这种体验的背后,隐藏着一种高效的Web技术。它就是 服务器推送事件(Server Sent Events,简称 SSE) 什么是 SSE 从根本上说,SSE 是一种允许服务器 单向 、 持续 地向客户端(浏览器)推送数据的Web技术。 可以把它想象成一个 电台广播 : 服务器 是广播电台,持续不断...
学习面试
javascript2025-08-04
Promise 从原理到实现
前言 是 异步编程的核心概念之一,它为我们提供了一种更优雅的方式来处理异步操作。 本文将全面剖析 的工作原理,并逐步实现一个符合 规范的 。 正文 一、Promise 基础概念 代表一个异步操作的最终完成(或失败)及其结果值。它有 种状态: 1. pending : ,既不是成功,也不是失败状态 2. fulfilled :意味着操作 完成 3. rejected :意味着操作 状态一旦改变就不能再变,只能从 变为 ,或从 变为 。 二...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录