首页/文章/javascript

图解原型和原型链

2024-03-11
21698 分钟
...

什么是原型(prototype)?

我们知道原生的js中并没有类(Class)这个概念, 因此在js中创建对象主要是通过以下两种方式实现:


var xm = {name:"小明",age:18};


function Person(name,age){
    this.name = name;
    this.age = age;
}
let xm = new Person("小明",18);

其中前者适用于创建单个对象,当需要批量创建对象时,我们往往采取后者,即通过构造函数创建对象。

原型(prototype) 是函数的一个特定属性,该属性可看做一个指针,指向一个对象,该对象包含了构造函数的所有共有属性和函数。

简单来说原型相当于一个共享窗口,凡是放在原型里的属性都能共享给子代使用,但子代只有其使用权没有编辑和所有权,任何对象实例不能修改其父类的原型。

下面我们带入到一个简单的例子中具体感受一下。

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

  let xm = new Person("小明");    
  let xh = new Person("小红");     

  
  Person.prototype.greet = function(){
    console.log("hello",this.name);
  }
  
  Person.prototype.age = 18;
  xm.gender = "男";     
  
  console.log(xm);
  console.log(xh);

打印结果如下:

如图,xm实例有2个属性,xh实例只有1个属性。
其中多出的gender属性是xm特有的,只有xm实例有权访问, 这种只有实例本身可以访问的属性我们称为自有属性
相反的,属于同一个构造函数创建出来的对象属性则称为共有属性,共有属性包含原型属性和实例属性两部分。

实例属性:在构造函数内部通过this定义的属性,每个实例对象都有其独立的实例属性。 例如:this.name = name;

原型属性: 通过在构造函数的prototype上定义的属性,例如:Person.prototype.age = 18;


console.log(xm.age);  
console.log(xh.age);  


xm.greet();    
xh.greet();    

打印结果如下:

由上两次打印结果可知,添加在原型上的属性不属于任一实例,但是由该构造函数创建的实例均可以使用原型上的属性/函数

console.log("Person.prototype",Person.prototype);

打印结果如下:

如图,打印Person.prototype返回了一个对象,其内部包含了原型属性age,原型函数greet(),以及构造器创建的实例属性name,age。即该对象含了构造函数的所有共有属性和函数。

prototype、constuctor和__proto__的关系

prototype的定义已在上面有详细介绍,这里便不再赘述,主要介绍一下后两者的定义:

constructor: 在 JavaScript 中, constructor 属性返回对象的构造函数。 说白了就是告诉你某个对象是通过哪个构造函数创建出来的。

__proto__: 在 JavaScript 中,任何对象都有其隐式原型,对象的隐式原型指向创建该对象的父级的原型。

prototype和__proto__的区别:

  1. 任何对象都有__proto__,而prototype则是函数特有的属性
  2. 函数一定有原型,对象可能有原型,因为函数也是一个对象。

下面我们简单梳理一下构造函数,原型和隐式原型之间的关系:

  • 实例的__proto__指向构造函数原型,告诉实例可以访问哪些共有属性
  • 原型的constructor又指向构造函数本身,告诉实例是被哪个构造函数创建的
  • 同时构造函数的prototype指向其原型,告诉其有哪些可被子实例访问的共有属性

通过prototype、constuctor和__proto__三个指针的共同作用,实现了js中的继承机制。

原型链

了解了prototype、constuctor和__proto__的关系,我们试着来看一张解释原型链的经典老图。

线条弯弯绕绕,一眼看过去可能还是摸不着头脑,不过没关系,我们把线条拉直了并分块来分析。

前面提到js中创建对象有两种方法:

  • 通过构造函数创建
  • 创建对象字面量

1. 构造函数创建的实例的原型链构成:

  • 如红线所示,f1,f2是构造函数Foo()通过new关键字创造出的两个子实例;其隐式原型指向其构造函数Foo()的原型,原型的constructor又指向构造函数Foo()
  • Foo()自己又是一个由Function()创建出来的子实例,同理可得其prototype、constuctor的指向。但Function已是最根处的构造函数,因此其__proto__无法继续向上指向,而是指向了自己
  • 同时,不论是Function()还是Foo(),它们的prototype本质都是一个对象,因此其__proto__指向它们的构造函数Object()的原型
  • 最终到达一切原型链的尽头Object.prototype.proto,其值为null

2.通过对象字面量创建的实例的原型链构成:

  • 如红线所示,o1,o2是通过对象字面量创建的两个子实例对象,因此其隐式原型指向它们的构造函数Object()的原型,原型的constructor又指向构造函数Object(),最终到达一切原型链的尽头Object.prototype.proto,其值为null
  • Object()自己又是一个由Function()创建出来的子实例,因此其__proto__指向Function()的原型

最后我们将上面两种情况合并到一起,便得到了完整的原型链构成:

原型链的作用

原型链本质上是js中用于实现继承的一套完整的机制。通过原型链,我们可以实现继承。开发者通过修改对象的原型,为该对象添加新的属性和方法,使多个对象可以共享同一个原型对象的属性和方法,从而简化代码量和优化内存空间。

new关键字的简易实现原理

了解了原型链的构成,我们通过new关键字的简易实现原理来更具体的体会原型的应用。

new关键字创建对象时实际上干了哪些事?

  • 1.创建一个空对象 var obj={}
  • 2.改变this指向 call apply
  • 3.给该对象添加属性 this.name = name
  • 4.返回一个对象 return obj

下面是一个简易的new关键字的实现:

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

function _new(){

    
    var obj = {}
    
    var params = Array.prototype.slice.call(arguments,1)
    
    var foo = arguments[0]
    
    foo.apply(obj,params)
    
    obj.__proto__ = foo.prototype
    obj.__proto__.constructor = foo
    
    return obj
}


const xm = new Person("小明",18);
const xiaoming = _new(Person,"小明",18);

console.log("new",xm);
console.log("_new",xiaoming);

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

分享文章

相关文章

更多文章 →
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 :意味着操作 状态一旦改变就不能再变,只能从 变为 ,或从 变为 。 二...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录