彻底搞清楚 JavaScript 的原型和原型链
JavaScript真的挺无语的,怪不得看了那么多的介绍文章还是一头雾水,直到自己终于弄懂了一点点之后才深有体会:
先从整体说起吧,发现没有基础做依据,那都是空中楼阁;
先从基础开始介绍吧,又发现基础是个蛇头咬蛇尾的圆环,无从下手,应该先整体介绍。
于是介绍本身就成了一个死循环。。。
还是尝试着从基础开始。。。(多图预警)
主要内容:
- 对象的继承树。
- 函数的继承树。
- 函数 VS 对象
- prototype VS _ _ proto__
- 继承 VS 组合
- 自己定义函数(class),以及实现继承
寻找原型链的“源头”
网上有一个梗:万物基于MIUI。虽然是一句调侃,但是也表达源头的重要性。
看过一些高手写的关系图,应该是非常专业,但也正是因为太专业了,所以才导致新手看的是一头雾水。
那么对于先手来说,有没有简单一点的方式呢?我们可以借鉴一下面向对象的思路。
提到面向对象,大家都会想到基类(超类、父类)、子类、继承、多态等。为啥容易记住呢?因为继承关系非常简单,从基类开始,一层一层继承下去,结构非常清晰明了。
我觉得应该借鉴一下这种表达方式,也许这种方式并不契合JavaScript,但是我觉得应该比较方便初学者入门。
经常听说,JavaScript 的世界是基于 Object 的,这句话对但是又不对,为啥这么说呢?我们来看看 Object 的结构:(使用 console.dir() 可以看到细节 )
console.dir(Object)

首先请注意一下那个 f 的标识,这表示 Object 其实是一个函数(从 JavaScript 的语法角度来看),我们来验证一下:

这到底是怎么回事呢?后面细说,先把找到源头才好理解。
这个 Object 并不是源头,因为还有 prototype 和 __ proto__, 我们先看看 Object.prototype 的结构:
Object.prototype
console.dir(Object.prototype)

可以看到,Object.prototype 才是源头,因为 Object.prototype 没有 prototype(当然没有),_ _ proto__ 也是 null,我们来验证一下:
console.dir(Object.prototype.prototype)
console.dir(Object.prototype.__proto__)


Object.__proto __

这是啥?是不是很头晕,这个其实指向的是 Function的原型,我们来验证一下:

这是咋回事?后面再解释。
小结
是不是有点晕,让我们来梳理一下思路:

如果看上面的图有点晕的话,可以先看下面的图,灰线说的是构造函数的关系,可以先跳过。(终于画出来了那种绕圈圈的图,向着专业又迈出了一步)

思路一:
Object有两个属性,一个是对象原型,一个是函数原型。思路二:
Object有两个指针,一个指向对象原型,一个指向函数原型。
我觉得思路二更适合一些,这个是理解 JavaScript 的原型链的第一个门槛,如果绕不清楚的话……没关系,往下看就好,我也是把下面都写出来,然后回头才整理出来这个图的。。。(这个也是给继承和组合做个铺垫)
构建一颗大树 —— 对象的继承关系
找到源头之后,我们就可以构建一颗大树了。
构建原则:xxx.prototype._ _ proto__ === Object.prototype
即:Object.prototype 看做父类,然后把其“子类”画出来。

这下是不是清晰多了呢?我们来验证一下:
- Array

- String:

好长好长,差点截不下来。
- Number

- BigInt

- Boolean

- Symbol

- Date

- RegExp (正则表达式)

- Math

共同点
每种类型都有自己的成员,然后_ _ proto__ 指向 Object.prototype。
特例
这里有几个特殊情况:
Math
没有原型,或者说原型就是 Math 自己。Array
这个比较奇怪。null 和 undefined
这对兄弟先当做特殊情况来处理。Function
Function.prototype._ _ proto__ 也是指向 Object.prototype的,但是 Function.prototype 是一个 f。Object
如果说 Object.prototype 是基类的话,那么Object是啥呢?其实 Object 是函数。
是不是有点晕?从JavaScript 语法的角度来说,不仅 Object 是函数,String、Number这些都是函数。
再构建一颗大树 —— 函数的继承关系
观察上面的图(对象的树)可以发现,我写的都是xxx.prototype 的形式,那为啥不直接写xxx呢?
因为从 JavaScript 的语法的角度来看,Object、String、Number、Array、Function等都是函数,Object.prototype、String.prototype 等才是对象。
我们从函数的角度来构造另一颗大树。
依据:xxx._ _ proto__ === Function.prototype
即:把Function.prototype看做父类,把他的子类(__ proto__指向他的)都画出来。

这里加上“()”,明确一下,然后我们来看一下具体的结构:
- Function

- String

- Number

- Boolean

- BigInt

- Symbol

- Date

- RegExp

- Array

对象 VS 函数
对象和函数的树都画完了,然后我们来分析一下对象和函数的区别。
- 对象:是一个容器,可以存放各种类型的实例(数据),包括函数。
- 构造函数:依据原型创建原型的实例。(个人理解可能有误)
- 一般函数:就是我们“随手”写的函数,执行某些代码,返回结果(也可以不返回)。
从 JavaScript 的语法角度来看,Object、Function、String、Date、Number等都是function,而Object.prototype、String.prototype、Date.prototype、Number.prototype等才是对象。
这和我们经常用到表述方式有点不一样,也正是这个原因,导致理解和表达的时候非常混乱。
我们还是来验证一下:
- 函数

- 对象

这里有一个特例,Function.prototype 是一个函数,而且是所有函数的源头。
所以说,从 JavaScript 的语法角度来看,函数就是函数,对象就是对象,不存在Object既是对象又是函数的情况。
那么到底是什么关系呢?我们定义一套函数来具体分析一下。
实战:用ES6的class定义一套对象/函数
ES6提供了class,但是这个并不是类,而是 Function 的语法糖。
目的是简化ES5里面,为了实现继承而采用的各种“神操作”。
用class来定义,结构和关系会非常清晰,再也不会看着头疼了,建议新手可以跳过ES5的实现方式,直接用ES6的方式。
我们先定义一个Base,然后定义一个Person继承Base,再定义一个Man继承Person。
也就是说,可以深层继承。
class Base {
constructor (title) {
this.title = '测试一下基类:' + title
}
baseFun1(info) {
console.log('\n这是base的函数一,参数:', info, '\nthis:', this)
}
}
class Person extends Base{
constructor (title, age) {
super(title)
this.title = '人类:' + title
this.age = age
}
personFun1(info) {
console.log('\n这是base的函数一,参数:', info, '\nthis:', this)
}
}
class Man extends Person {
constructor (title, age, date) {
super(title, age)
this.title = '男人:' + title
this.birthday = date
}
manFun3 (msg) {
console.log('jim 的 this ===', this, msg)
}
}
我们打印来看看结构:

构造函数 constructor
打印结果很清晰的表达了,构造函数就是我们定义的class。属性
属性比较简单,统统都挂在 this 上面,而且是同一个级别。函数
函数就有点复杂了,首先函数是分级别的,挂在每一级的原型上面。
Base的函数(baseFun1),挂在Base的原型上面,_ _ proto__ 指向原型。
Person的函数(PersonFun1),应该挂在Person的原型上面,但是打印结果似乎是,Base好像标错了位置。原型链
Man的实例 > Man的原型 > Person的原型 > Base 的原型 > Object 的原型。
通过 _ _ proto__ 连接了起来。
Man的实例 man1,可以通过这个“链条”,找到 baseFun1,直接用即可(man1.baseFun1()),
而不需要使用_ _ proto__(man1.__ proto__.__ proto__.__ proto__.baseFun1()✘)
这个和面对对象的继承是一样的效果。
prototype VS _ _ proto _ _
看上面两个大树,既有 prototype 又有 _ _ proto _ _,好乱的样子。那么怎么办呢?我们可以找一下规律:
prototype:
prototype 是自己的原型,可以其原型可以是函数,也可以是对象。有各自的继承体系。__ proto __ :
__ proto __ 指向上一级原型,并不是自己的,只是一个指针,方便使用父级的方法和属性。
可以指向对象,也可以指向函数。

组合 VS 继承
一提到面向对象,大家一般都会想到封装、继承、和多态。但是 JavaScript 却不是这个思路。
上面那颗大树看起来是继承的关系,Object.prototype 是基类,派生出来 Object.prototype、Function.prototype、string.prototype等。
但是其实这里面隐藏了组合的方式。
我们展开Object 来看看,就会发现自己进入了一个迷宫

object 的“特殊”的结构
看上面的图我们会发现,Object 并不像我们想象的那么简单,有很多的方法,我们随便找几个点开看看:

每一个函数都有自己的原型链(__ proto__),是不是有一种进入迷宫的感觉?我当初看的到时候就被吓退了,这都是个啥?
但是我们换个思路来理解,就清晰多了,那就是:组合代替继承!
Object 其实是由若干个函数组合而成。
其实,想一想,JavaScript 没有私有成员,所以各种细节都暴露出来了,所以我们可以看到原型,看到原型链,看到构造函数。
正是因为看到了这么多的细节,而以前又没有一个比较好的封装方式,所以看起来就特别的乱,理解起来也特别头疼。
总结
按照 JavaScript 的语法来总结,否则总感觉说不清楚。
对象(xxx.prototype)
- 对象的“根”是 Object.prototype,其上一级是null。
- 对象只有_ _ proto__,指向上一级原型。
- 对象没有 prototype,因为Object.prototype、String.prototype、Number.prototype等本身就是对象。通过 _ _ proto__寻找上一级原型。
函数
- 函数的“根”是 Function.prototype,其上一级是 Object.prototype。
- 函数有prototype,(JavaScript 语法角度)Object、String、Function、Number 等都是函数,同时也是其原型的构造函数。
- 函数有_ _ proto__,指向上一级函数。
实例只有_ _ proto__ ,指向函数原型。
彻底搞清楚 JavaScript 的原型和原型链的更多相关文章
- JS原型,Prototype,原型
对于javascript这样一种前端语言,个人觉得,要真正的理解其oop, 就必须要彻底搞清楚javascript的对象,原型链,作用域,闭包,以及this所引用的对象等概念.这些对弄明白了,应该就可 ...
- 一张图带你搞懂Javascript原型链关系
在某天,我听了一个老师的公开课,一张图搞懂了原型链. 老师花两天时间理解.整理的,他讲了两个小时我们当时就听懂了. 今天我把他整理出来,分享给大家.也让我自己巩固加深一下. 就是这张图: 为了更好的图 ...
- 【javascript基础】4、原型与原型链
前言 荒废了好几天,在宿舍闷了几天了,一直想着回家放松,什么也没搞,论文就让老师催吧.不过,闲的没事干的感觉真是不好,还是看看书,写写博客吧,今天和大家说说函数的原型. 原型是什么 第一次看到这个的时 ...
- javascript学习-对象与原型
javascript学习-对象与原型 Javascript语言是符合面向对象思想的.一般来说,面向对象思想需要满足以下三个基本要求: 封装,Javascript的对象可以自由的扩充成员变量和方法,自然 ...
- (转)【javascript基础】原型与原型链
原文地址:http://www.cnblogs.com/allenxing/p/3527654.html 前言 原型是什么 理解原型对象 原型对象 isPrototypeOf hasOwnProper ...
- 如何理解JavaScript的原型和原型链
在现在的业务开发中,应该很少人在写原生JavaScript了,大家都一股脑地扑在各个框架上.本来,这些框架对于业务和开发者来说是一种福音,减少了各种各样的开发痛点,但是带来的负面问题就是对于开发者来说 ...
- 十分钟读懂JavaScript原型和原型链
原型(prototype)这个词来自拉丁文的词proto,意谓“最初的”,意义是形式或模型.在JavaScript中,原型的探索也有很多有趣的地方,接下来跟随我的脚步去看看吧. 原型对象释义 每一个构 ...
- JavaScript学习总结(五)原型和原型链详解
转自:http://segmentfault.com/a/1190000000662547 私有变量和函数 在函数内部定义的变量和函数,如果不对外提供接口,外部是无法访问到的,也就是该函数的私有的变量 ...
- JavaScript中的原型、原型链、原型模式
今天,咱来聊聊JavaScript中的原型跟原型链 原型跟原型模式 这一块的知识,主要是设计模式方面的. 首先,我们知道JavaScript是面向对象的.既然是面向对象,那它自然也有相应的类跟对象等概 ...
随机推荐
- PAT (Basic Level) Practice (中文)1017 A除以B (20分)
1017 A除以B (20分) 本题要求计算 A/B,其中 A 是不超过 1000 位的正整数,B 是 1 位正整数.你需要输出商数 Q 和余数 R,使得 A=B×Q+R 成立. 输入格式: 输入在一 ...
- dubbo注册中心占位符无法解析问题
dubbo注册中心占位符无法解析问题 1.背景 最近搞了2个老项目,想把他们融合到一起.这俩项目情况简介如下: 项目一:基于SpringMVC + dubbo,配置读取本地properties文件,少 ...
- 【数据结构与算法Python版学习笔记】图——基本概念及相关术语
概念 图Graph是比树更为一般的结构, 也是由节点和边构成 实际上树是一种具有特殊性质的图 图可以用来表示现实世界中很多有意思的事物,包括道路系统.城市之间的航班.互联网的连接,甚至是计算机专业的一 ...
- JDBC:(java database Connection) java数据库连接。
JDBC 指 Java 数据库连接,是一种标准Java应用编程接口( JAVA API),用来连接 Java 编程语言和广泛的数据库. JDBC连接步骤: 1.先导入jar包,把jar放入到工程下并 ...
- PinPoint单节点部署及客户端配置方法
在一次做项目中,需要涉及全链路压测,为了更好定位链路中某一节点可能会出现的问题,在繁忙之余,快速部署及应用了该链路工具,分享给大家~ 话不多说,开始部署~ 一.环境配置1.1 获取需要的依赖包进入ho ...
- Java:ConcurrentHashMap类小记-2(JDK7)
Java:ConcurrentHashMap类小记-2(JDK7) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentH ...
- kafka生产者和消费者api的简单使用
kafka生产者和消费者api的简单使用 一.背景 二.需要实现的功能 1.生产者实现功能 1.KafkaProducer线程安全的,可以在多线程中使用. 2.消息发送的key和value的序列化 3 ...
- 力扣 - 剑指 Offer 57. 和为s的两个数字
题目 剑指 Offer 57. 和为s的两个数字 思路1(哈希表) 这题首先想到的是使用两个for遍历,查找是哪两个相加等于target,但是时间复杂度确实\(O(N^2)\),时间复杂度太高,因此我 ...
- Kubernetes集群环境搭建全过程
资源准备以及服务器初始化 所有服务器执行一下脚本进行配置信息初始化: #!/bin/bash cd `dirname $0` # 关闭selinux setenforce 0 sed -i '/SEL ...
- 计算机中的contex理解
原文链接 https://www.xuebuyuan.com/2016635.html 1.其实简单的说就是跟当前主题有关的所有内容. 2.如说到程序的上下文,就是当前这段程序之上和之下的程序段.因 ...