前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >彻底搞清楚 JavaScript 的原型和原型链 Object.prototypeObject.__proto __小结共同点特例

彻底搞清楚 JavaScript 的原型和原型链 Object.prototypeObject.__proto __小结共同点特例

作者头像
用户1174620
发布2021-12-02 20:38:57
7600
发布2021-12-02 20:38:57
举报

JavaScript真的挺无语的,怪不得看了那么多的介绍文章还是一头雾水,直到自己终于弄懂了一点点之后才深有体会: 先从整体说起吧,发现没有基础做依据,那都是空中楼阁; 先从基础开始介绍吧,又发现基础是个蛇头咬蛇尾的圆环,无从下手,应该先整体介绍。 于是介绍本身就成了一个死循环。。。

还是尝试着从基础开始。。。(多图预警)

主要内容:

  • 对象的继承树。
  • 函数的继承树。
  • 函数 VS 对象
  • prototype VS _ _ proto__
  • 继承 VS 组合
  • 自己定义函数(class),以及实现继承

寻找原型链的“源头”

网上有一个梗:万物基于MIUI。虽然是一句调侃,但是也表达源头的重要性。

看过一些高手写的关系图,应该是非常专业,但也正是因为太专业了,所以才导致新手看的是一头雾水。 那么对于先手来说,有没有简单一点的方式呢?我们可以借鉴一下面向对象的思路。

提到面向对象,大家都会想到基类(超类、父类)、子类、继承、多态等。为啥容易记住呢?因为继承关系非常简单,从基类开始,一层一层继承下去,结构非常清晰明了。

我觉得应该借鉴一下这种表达方式,也许这种方式并不契合JavaScript,但是我觉得应该比较方便初学者入门。

经常听说,JavaScript 的世界是基于 Object 的,这句话对但是又不对,为啥这么说呢?我们来看看 Object 的结构:(使用 console.dir() 可以看到细节 )

代码语言:javascript
复制
console.dir(Object)
Object的结构组成
Object的结构组成

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

Object 其实是函数
Object 其实是函数

这到底是怎么回事呢?后面细说,先把找到源头才好理解。

这个 Object 并不是源头,因为还有 prototype 和 __ proto__, 我们先看看 Object.prototype 的结构:

Object.prototype

代码语言:javascript
复制
console.dir(Object.prototype)
Object原型的结构
Object原型的结构

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

代码语言:javascript
复制
console.dir(Object.prototype.prototype)
console.dir(Object.prototype.__proto__)
Object 验证prototype
Object 验证prototype
Object 验证 proto
Object 验证 proto

Object.__proto __

103-object2.png
103-object2.png

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

103-object2验证.png
103-object2验证.png

这是咋回事?后面再解释。

小结

是不是有点晕,让我们来梳理一下思路:

Object的三个重要属性
Object的三个重要属性

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

Object的两个重要属性
Object的两个重要属性
  • 思路一: Object有两个属性,一个是对象原型,一个是函数原型。
  • 思路二: Object有两个指针,一个指向对象原型,一个指向函数原型。

我觉得思路二更适合一些,这个是理解 JavaScript 的原型链的第一个门槛,如果绕不清楚的话……没关系,往下看就好,我也是把下面都写出来,然后回头才整理出来这个图的。。。(这个也是给继承和组合做个铺垫)

构建一颗大树 —— 对象的继承关系

找到源头之后,我们就可以构建一颗大树了。

构建原则:xxx.prototype._ _ proto__ === Object.prototype 即:Object.prototype 看做父类,然后把其“子类”画出来。

对象的树
对象的树

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

  • Array
190-Array原型.png
190-Array原型.png
  • String:
String原型
String原型

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

  • Number
Number原型
Number原型
  • BigInt
BigInt原型
BigInt原型
  • Boolean
Boolean原型
Boolean原型
  • Symbol
Symbol原型
Symbol原型
  • Date
Date原型
Date原型
  • RegExp (正则表达式)
RegExp原型
RegExp原型
  • Math
Math
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
200-Function原型.png
200-Function原型.png
  • String
String()
String()
  • Number
Number()
Number()
  • Boolean
Boolean()
Boolean()
  • BigInt
BigInt()
BigInt()
  • Symbol
Symbol()
Symbol()
  • Date
Date()
Date()
  • RegExp
RegExp()
RegExp()
  • Array
Array()
Array()

对象 VS 函数

对象和函数的树都画完了,然后我们来分析一下对象和函数的区别。

  • 对象:是一个容器,可以存放各种类型的实例(数据),包括函数。
  • 构造函数:依据原型创建原型的实例。(个人理解可能有误)
  • 一般函数:就是我们“随手”写的函数,执行某些代码,返回结果(也可以不返回)。

从 JavaScript 的语法角度来看,Object、Function、String、Date、Number等都是function,而Object.prototype、String.prototype、Date.prototype、Number.prototype等才是对象。

这和我们经常用到表述方式有点不一样,也正是这个原因,导致理解和表达的时候非常混乱。

我们还是来验证一下:

  • 函数
函数的验证.png
函数的验证.png
  • 对象
对象的验证.png
对象的验证.png

这里有一个特例,Function.prototype 是一个函数,而且是所有函数的源头。

所以说,从 JavaScript 的语法角度来看,函数就是函数,对象就是对象,不存在Object既是对象又是函数的情况。

那么到底是什么关系呢?我们定义一套函数来具体分析一下。

实战:用ES6的class定义一套对象/函数

ES6提供了class,但是这个并不是类,而是 Function 的语法糖。 目的是简化ES5里面,为了实现继承而采用的各种“神操作”。

用class来定义,结构和关系会非常清晰,再也不会看着头疼了,建议新手可以跳过ES5的实现方式,直接用ES6的方式。

我们先定义一个Base,然后定义一个Person继承Base,再定义一个Man继承Person。 也就是说,可以深层继承。

代码语言:javascript
复制
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)
  }
}

我们打印来看看结构:

实例的结构.png
实例的结构.png
  • 构造函数 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

object 的“特殊”的结构

看上面的图我们会发现,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__ ,指向函数原型。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-12-02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 主要内容:
  • 寻找原型链的“源头”
    • Object.prototype
      • Object.__proto __
        • 小结
        • 构建一颗大树 —— 对象的继承关系
          • 共同点
            • 特例
            • 再构建一颗大树 —— 函数的继承关系
            • 对象 VS 函数
            • 实战:用ES6的class定义一套对象/函数
            • prototype VS _ _ proto _ _
            • 组合 VS 继承
            • object 的“特殊”的结构
            • 总结
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档