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 的原型和原型链的更多相关文章

  1. JS原型,Prototype,原型

    对于javascript这样一种前端语言,个人觉得,要真正的理解其oop, 就必须要彻底搞清楚javascript的对象,原型链,作用域,闭包,以及this所引用的对象等概念.这些对弄明白了,应该就可 ...

  2. 一张图带你搞懂Javascript原型链关系

    在某天,我听了一个老师的公开课,一张图搞懂了原型链. 老师花两天时间理解.整理的,他讲了两个小时我们当时就听懂了. 今天我把他整理出来,分享给大家.也让我自己巩固加深一下. 就是这张图: 为了更好的图 ...

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

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

  4. javascript学习-对象与原型

    javascript学习-对象与原型 Javascript语言是符合面向对象思想的.一般来说,面向对象思想需要满足以下三个基本要求: 封装,Javascript的对象可以自由的扩充成员变量和方法,自然 ...

  5. (转)【javascript基础】原型与原型链

    原文地址:http://www.cnblogs.com/allenxing/p/3527654.html 前言 原型是什么 理解原型对象 原型对象 isPrototypeOf hasOwnProper ...

  6. 如何理解JavaScript的原型和原型链

    在现在的业务开发中,应该很少人在写原生JavaScript了,大家都一股脑地扑在各个框架上.本来,这些框架对于业务和开发者来说是一种福音,减少了各种各样的开发痛点,但是带来的负面问题就是对于开发者来说 ...

  7. 十分钟读懂JavaScript原型和原型链

    原型(prototype)这个词来自拉丁文的词proto,意谓“最初的”,意义是形式或模型.在JavaScript中,原型的探索也有很多有趣的地方,接下来跟随我的脚步去看看吧. 原型对象释义 每一个构 ...

  8. JavaScript学习总结(五)原型和原型链详解

    转自:http://segmentfault.com/a/1190000000662547 私有变量和函数 在函数内部定义的变量和函数,如果不对外提供接口,外部是无法访问到的,也就是该函数的私有的变量 ...

  9. JavaScript中的原型、原型链、原型模式

    今天,咱来聊聊JavaScript中的原型跟原型链 原型跟原型模式 这一块的知识,主要是设计模式方面的. 首先,我们知道JavaScript是面向对象的.既然是面向对象,那它自然也有相应的类跟对象等概 ...

随机推荐

  1. 防止SQL注入总结

    1.预编译(占位符)可以很大程度上防止SQL注入 预编译的原理是数据库厂商提供的JAR包中,对参数进行了转义 2.mybatis中,能用# 的地方,不用$,因为#是预编译占位符形式,可以防止SQL注入 ...

  2. OO第四单元

    OO第四单元总结 第四单元架构设计 第一次作业 uml类图 这次作业我采取的基本思路就是根据指令来建造一个简易的类图,用于查询,其中umlclass中包含了umlAttraibute,umlOpera ...

  3. Pandas核心用法

    目录 Numpy和Pandas Numpy科学计算 Pandas数据分析 安装jupyter notebook Numpy语法 创建和基本使用 切片索引 布尔索引 对位运算 矩阵的乘除 其他方法 Pa ...

  4. 零基础学习C语言字符串操作总结大全

    本篇文章是对C语言字符串操作进行了详细的总结分析,需要的朋友参考下 1)字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, ...

  5. 攻防世界 杂项 6.pure_color

    图片隐写 工具 使用StegSolve一把梭 另一种解法 右击图片编辑,画图工具打开,属性设置黑白.

  6. 助你上手Vue3全家桶之Vue3教程

    目录 前言 1,setup 1.1,返回值 1.2,注意点 1.3,语法 1.4,setup的参数 2,ref 创建响应式数据 3,reactive 创建响应式数据 4,computed 计算属性 5 ...

  7. error: ‘int64_t’ does not name a type

    我在CodeBlock中编译工程没有出现问题,但是放到ubuntu上用自己写的Makefile make的时候报错 error: 'int64_t' does not name a type # 2 ...

  8. candy leetcode C++

    There are N children standing in a line. Each child is assigned a rating value. You are giving candi ...

  9. axios & fetch 异步请求

    // 一.创建实例 const request = axios.create({ baseURL: "http://kg.zhaodashen.cn/v2", headers: { ...

  10. CANN5.0黑科技解密 | 别眨眼!缩小隧道,让你的AI模型“身轻如燕”!

    摘要:CANN作为释放昇腾硬件算力的关键平台,通过深耕先进的模型压缩技术,聚力打造AMCT模型压缩工具,在保证模型精度前提下,不遗余力地降低模型的存储空间和计算量. 随着深度学习的发展,推理模型巨大的 ...