前言

JavaScript常被描述为一种「基于原型的语言」——每个对象都拥有一个「原型对象」,对象以其原型为模板、从原型继承属性和放法。原型对象也可能拥有原型,并从中继承属性和方法,一层一层以此类推。这种关系常被称为「原型链」,它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

准确的说,这些属性和方法定义在Object构造函数prototype属性上,而非对象实例本身。

四句话道破原型与原型链:

  • 每个函数(类)天生自带一个属性prototype,属性值是一个对象,里面存储了当前类供实例使用的属性和方法 「(显示原型)」
  • 在浏览器默认给原型开辟的堆内存中有一个constructor属性:存储的是当前类本身(️注意:自己开辟的堆内存中默认没有constructor属性,需要自己手动添加)「(构造函数)」
  • 每个对象都有一个__proto__属性,这个属性指向当前实例所属类的原型(不确定所属类,都指向Object.prototype「(隐式原型)」
  • 当你试图获取一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型__proto__(也就是它的构造函数的显示原型prototype)中查找。「(原型链)」

构造函数,原型与实例的关系:

每个构造函数(constructor)都有一个原型对象(prototype)原型对象(prototype)都包含一个指向构造函数(constructor)的指针,而实例(instance)都包含一个指向原型对象(__proto__)的内部指针

prototype(显式原型)

每个函数都有一个prototype属性

// 构造函数(类)
function Person(name){
    this.name = name
}
// new了一个实例 (对象)
var person = new Person('南玖')
console.log(person) //Person { name: '南玖' }
console.log(Person.prototype)  //构造函数(类)的原型 ----->对象
Person.prototype.age = 18  // 构造函数原型
console.log(person.age)  // 18

上面我们把这个函数Person的原型打印出来了,它指向的是一个对象,并且这个对象正是调用该构造函数而创建的实例的原型

上面这张图表示的是构造函数与实例原型之间的关系,所以我们知道了构造函数的prototype属性指向的是一个对象。

那实例与实例原型之间的关系又是怎样的呢?这里就要提到__proto__属性了

__proto__(隐式原型)

从上面四句话中我们可以知道这是每一个Javascript对象(除null)都具有的一个属性,这个属性会指向该对象的原型(也就是实例原型)

因为在JavaScript中没有类的概念,为了实现类似继承的方式,通过__proto__将对象和原型联系起来组成原型链,的以让对象访问到不属于自己的属性。

那么我们就能够证明实例与实例原型之间的关系

console.log(person.__proto__)  //实例(对象)的原型--->对象

console.log(person.__proto__ === Person.prototype)  //实例的原型与构造函数的原型相等

从上图我们可以看出实例对象与构造函数都可以指向原型,那么原型能不能指向构造函数或者是实例呢?

constructor(构造函数)

原型是没有属性指向实例的,因为一个构造函数可以创建多个实例对象;

从前面的四句话中我们知道「在浏览器默认给原型开辟的堆内存中有一个constructor属性」,所以原型也是可以指向构造函数的,这个属性就是「constructor」

于是我们可以证明一下观点:

console.log(Person.prototype.constructor) //实例的显式原型的构造函数ƒ Person(name){this.name = name}
console.log(person.__proto__.constructor)  //实例的隐式原型的构造函数 ƒ Person(name){this.name = name}
console.log(person.__proto__.constructor === Person.prototype.constructor)//true 实例原型的构造函数与类的构造函数相等
console.log(Person === Person.prototype.constructor)  //true

实例对象的__proto__是如何产生的?

我们知道当我们使用new 操作符时,生成的实例对象就拥有了__proto__属性

function Foo() {}
// 这个函数时Function的实例对象
// function是一个语法糖
// 内部其实调用了new Function()

所以可以说,在new的过程中,新对象被添加了__proto__属性并且链接到了构造函数的原型上。

new的原理

说简单点可以分为以下四步:

  • 新建一个空对象
  • 链接原型
  • 绑定this,执行构造函数
  • 返回新对象
function myNew() {
// 1.新建一个空对象
let obj = {}
// 2.获得构造函数
let con = arguments.__proto__.constructor
// 3.链接原型
obj.__proto__ = con.prototype
// 4.绑定this,执行构造函数
let res = con.apply(obj, arguments)
// 5.返回新对象
return typeof res === 'object' ? res : obj
}

原型链

说完了原型,我们再来看看什么是原型链?先来看一张图:

这张图中,由__proto__串起来的链式关系,我们就称它为原型链

原型链的作用

原型链决定了JavaScript中继承的实现方式,当我们访问一个属性时,它的查找机制如下:

  • 访问对象实例属性,有的话直接返回,没有则通过__proto__去它的原型对象上查找
  • 原型对象上能找到的话则返回,找不到继续通过原型对象的__proto__查找
  • 一直往下找,直到找到Object.prototype,如果能找到则返回,找不到就返回undefined,不会再往下找了,因为Object.prototype.__proto__是null,说明了Object是所有对象的原型链顶层了。

从图中我们可以发现,所有对象都可以通过原型链最终找到 Object.prototype ,虽然 Object.prototype 也是一个对象,但是这个对象却不是 Object 创造的,而是引擎自己创建了 Object.prototype 。所以可以这样说,所有实例都是对象,但是对象不一定都是实例。

构造函数的__proto__是什么呢?

由上面的原型链的解释,我们应该能够理解构造函数的__proto__的,在JavaScript中所有东西都是对象,那么构造函数肯定也是对象,是对象就有__proto__

function Person(){}
console.log(Person.__proto__)
console.log(Function.prototype)
console.log(Person.__proto__===Function.prototype) // true

「这也说明了所有函数都是Function的实例」

那这么理解的话,Function.__proto__岂不是等于Function.prototype。。。。我们不妨来打印一下看看

Function.__proto__ === Function.prototype // true

打印出来确实是这样的。难道 Function.prototype 也是通过 new Function() 产生的吗?

答案是否定的,这个函数也是引擎自己创建的。首先引擎创建了 Object.prototype ,然后创建了 Function.prototype ,并且通过 __proto__ 将两者联系了起来。这里也很好的解释了上面的一个问题,为什么 let fun = Function.prototype.bind() 没有 prototype 属性。因为 Function.prototype 是引擎创建出来的对象,引擎认为不需要给这个对象添加 prototype 属性。

总结

  • Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它
  • Function.prototypeObject.prototype 是两个特殊的对象,他们由引擎来创建
  • 除了以上两个特殊对象,其他对象都是通过构造器 new 出来的
  • 函数的 prototype 是一个对象,也就是原型
  • 对象的 __proto__ 指向原型, __proto__ 将对象和原型连接起来组成了原型链

JavaScript之原型与原型链的更多相关文章

  1. JavaScript之继承(原型链)

    JavaScript之继承(原型链) 我们知道继承是oo语言中不可缺少的一部分,对于JavaScript也是如此.一般的继承有两种方式:其一,接口继承,只继承方法的签名:其二,实现继承,继承实际的方法 ...

  2. 【javascript基础】4、原型与原型链

    前言 荒废了好几天,在宿舍闷了几天了,一直想着回家放松,什么也没搞,论文就让老师催吧.不过,闲的没事干的感觉真是不好,还是看看书,写写博客吧,今天和大家说说函数的原型. 原型是什么 第一次看到这个的时 ...

  3. 【JavaScript】深入理解JavaScript之强大的原型和原型链

    由于JavaScript是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链. AD: hasOwnProperty函数: hasOw ...

  4. 《JavaScript 闯关记》之原型及原型链

    原型链是一种机制,指的是 JavaScript 每个对象都有一个内置的 __proto__ 属性指向创建它的构造函数的 prototype(原型)属性.原型链的作用是为了实现对象的继承,要理解原型链, ...

  5. Javascript 原型和原型链

    先来了解一下Javascript中的原型:”原型也是一个对象,原型可以用来实现继承...“ 对于 原型,构造函数,以及实例之间的关系:“每个(构造)函数都有一个原型属性,原型对象都包含一个指向构造函数 ...

  6. javascript 原型 和 原型链

    最近几天,好些新同事来问原型,原型链啥的.本身作为菜鸟的我好像也没有好好整理过这个,这里写写自己的理解. 原型 大家都知道,JavaScript 不包含传统的类继承模型,而是使用 prototype ...

  7. JavaScript深入之从原型到原型链(本文转载)

    JavaScript深入之从原型到原型链(本文转载) https://github.com/mqyqingfeng/Blog.原文地址 构造函数创建对象 我们先使用构造函数创建一个对象: functi ...

  8. javascript 之原型、原型链-14

    原型 原型是一个对象,每个函数对象(在javascript 之对象中说过函数也是对象 )都有一个属性(prototype)指向这个对象--原型对象,这个对象的作用是让所有对象实例共享原型对象中的属性. ...

  9. JavaScript原型与原型链

    一.数据类型 JavaScript的数据类型可以分为基本数据类型和引用数据类型. 基本数据类型(6种) String Number Boolean null undefined Symbol(ES6) ...

  10. JavaScript prototype原型和原型链详解

    用过JavaScript的同学们肯定都对prototype如雷贯耳,但是这究竟是个什么东西却让初学者莫衷一是,只知道函数都会有一个prototype属性,可以为其添加函数供实例访问,其它的就不清楚了, ...

随机推荐

  1. 20210826 Lighthouse,Miner,Lyk Love painting,Revive

    考场 T1 这不裸的容斥 T2 这不裸的欧拉路,先从奇数度点开始走,走不了就传送 T3 什么玩意,暴力都不会 T4 点分树??? 仔细想了一波,发现 T1 T2 都好做,T3 二分答案后可以暴力贪心, ...

  2. windows系统框架

  3. SpringMVC-初见

    目录 什么是SpringMVC? DispatcherServlet 第一个MVC程序 配置版 Maven可能存在资源过滤的问题 注解版 RestFul和控制器 实现Controller接口 使用注解 ...

  4. 网络层协议、ARP攻击

    一.IP数据包格式 二.ICMP协议介绍 PING命令 三.ARP协议介绍 四.ARP攻击原理 一.IP数据包格式 网络层的功能 定义了基于IP协议的逻辑地址 连接不同的媒介类型 选择数据通过网络的最 ...

  5. Docker部署启动错误,需要手动进入Docker的容器里,启动程序,排查错误

    #docker-compose build --no-cache //重新创建容器,不管有没有 #docker-compose up #docker-compose up -d //后台启动并运行容器 ...

  6. 测试开发【提测平台】分享10-Element UI抽屉和表单校验&增改接口合并实现应用管理

    微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. 开篇说个小讨论,一个群里聊天聊到关于更新篇章的长度,是小篇幅多次,还是每次按照一个小完整的功能,我个人的是按照后种来的,主要的思考就是希望 ...

  7. centos7.6,nginx1.18,php-7.4.6,mysql-5.7.30 安装

    #1.下载,来自各官网 nginx-1.18.0.tar.gz php-7.4.6.tar.gz mysql-5.7.30-linux-glibc2.12-x86_64.tar.gz #下载到本地再传 ...

  8. PC+PLC通过Modbus协议构建工控系统

    一. 概述 工业设备采用HMI+PLC控制是比较常见的方案,随着工业自动化的要求越来越高,现在很多设备都要求接入企业MES系统,MES系统一般为WEB系统,接口形式大部分为HTTP协议,这种传统方案和 ...

  9. python学习笔记(十二)-网络编程

    本文结束使用 Requests 发送网络请求.requests是一个很实用的Python HTTP客户端库,编写爬虫和测试服务器响应数据时经常会用到.可以说,Requests 完全满足如今网络的需求. ...

  10. genymotion从本地拖拽apk到模拟器失败,报错“An error occured while deploying the file……”-解决方案

    前两篇已经讲过genymotion的安装了,但genymotion构建的安卓模拟器的界面比较简洁,什么软件都没.那么我们进行测试之前,先将需要测试的apk安装到模拟器中,一般来说,直接将apk文件从本 ...