编程技术文章分享与教程

网站首页 > 技术文章 正文

一个菜鸟的攻坚系列——__proto__和prototype

hmc789 2024-11-26 03:40:40 技术文章 2 ℃

写在前面:这是有史以来第一次写这种非表面知识的个人理解。肯定会有理解不到位甚至是错误的地方,希望大家带有批判性的阅读。有认为不合理的地方,尽管提出来~


说到原型,相信现在处于懵逼状态的人还是挺多的。

什么是原型?

什么是原型链?

__proto__是什么?

prototype又是什么?

__proto__和prototype有什么区别?

上边这些问题,在之前我也一直挺懵的,即使到现在,我也不敢说我完全弄明白了。所有接下来的东西如果大家觉得写的有什么问题,尽管提出来,大家一起进步。

这几天看了不少文章,查了资料,终于对于__proto__和prototype有了一点自己的头绪,废话少说,正文开始。

首先要知道我们为什么要搞懂 原型,它在我们平时写代码的时候有什么用。有人说原型是高阶编程必须的东西,比如说你要写框架,对JS原生的Object或者Array进行改造,那就需要在原型上进行。举个栗子,Vue里我们定义数组和JS原生形式没什么不一样,但是Vue中数组的push、pop等方法可以触发Vue的双向绑定,就是因为尤大对这些方法进行了封装。

Vue文档介绍:https://cn.vuejs.org/v2/guide/list.html#%E5%8F%98%E6%9B%B4%E6%96%B9%E6%B3%95

但是对我们来说,平时用到原型最多的地方还是通过构造函数进行实例化对象。这个过程在面向对象语言中通过class来实现。但是在es6之前,JS中没有class的概念,而且ES6的class也不过是语法糖,本质上还是通过原型来实现的。

先提出四个概念以及它们分别是什么:

  • 构造函数:用来构造对象的构造器,跟普通函数没有本质区别
  • 实例对象:就是一个对象
  • __proto__:每个对象拥有的私有属性
  • prototype:构造函数的原型对象

下面我们就分别展开来说

一、__proto__

根据MDN的描述:__proto__是每个实例对象都有的私有属性。在js中,函数也是对象,因此无论是构造函数还是普通函数也是存在__proto__属性的。

  • 实例对象的__proto__

实例对象是通过构造函数或者说构造器实例化得来的,所以可以知道实例对象的原型指向的是它对应的构造函数的原型对象(prototype)。

每个对象都有一个 constructor 属性,可以得到它的构造函数。

所以可以得到

function f(){}
let o = new f()
o.__proto__ === o.constructor.prototype    //true
  • 函数的__proto__

函数通过函数构造器实例化得来,也就是 Function,因此函数的 __proto__ 应当指向 Function的prototype


二、prototype

根据MDN的描述,prototype是构造函数的原型对象。因此可以得知,prototype是只有函数才有的属性,实例对象没有prototype

  • 构造函数的prototype

构造函数的 prototype 得到的是构造函数的原型对象,它包含构造函数定义在原型上的方法和属性,以及每个构造函数的原型对象都有的constructor属性和它的__proto__属性

既然prototype得到的一个对象,那它理论上也应该有 __proto__ 属性。经过验证(大家自行进行吧~),它确实是存在。其实也不难理解,既然prototype是原型对象,那对象的__proto__指向的就是对象构造器的prototype,即 Object.prototype。


三、属性遮蔽

在通过构造函数实例化对象后,得到的对象将会继承该构造函数拥有的属性和方法。

通过这里也就可以看到上部分所说的 实例对象的 __proto__ 属性指向构造函数的 prototype,因为 getVal 方法原本是定义在构造函数 f() 的 prototype 上的(上一个图)。

但是如果得到的实例对象定义了和构造函数原型上相同的属性 或者 构造函数同时在自身和prototype上定义了相同属性,会怎么样呢?

从图中可以看到,这种情况下,原型上的属性不能被访问到,这就是所谓的”属性遮蔽“。


四、原型链

介绍生成对象的几种方式,以及每种方式对应的原型链

  • 使用语法结构

js中的语法结构,本质上也是使用的构造器,只不是过语言自带的。因此,通过这种方式创建的对象__proto__指向的是(Object、Array等构造器).prototype

let obj = {a: 1, b:2}
//原型链:obj -> Object.prototype -> null
let arr = [1,2,3]
//原型链:arr -> Array.prototype -> Objec.prototype -> null
//因为Array构造器的原型对象(prototype,也是一个对象)是通过Object构造器生成的
//因此多了Object.prototype这一环
//Object.prototype的__proto__为null,所以原型链终止
  • 自定义构造函数

自定义构造函数,其实和第一种方式没有什么区别,生成对象的__proto__ 指向自定义函数的 prototype。

实例对象 -> 构造函数.prototype -> Object.prototype -> null

  • Object.creat
let a = {a: 1}
let b = Object.create(a)
let c = Object.create(b)
c.a  //1
//原型链:c -> b -> a -> Object.prototype -> null
  • class 关键字
class area{
    constructor(width, height){
        this.width = width;
        this.height = height
    }
    getArea(){
        return this.width*this.height
    }
}
let areaInstance = new area(10,20)
//原型链:areaInstance -> area.prototype -> Object.prototype -> null


关于class的具体内容,后边会有一篇文章单独介绍。

es6的class是什么?

class和构造函数方式有什么区别?

class如何实现继承?


最后附一张自己画的原型结构关系图:

Tags:

标签列表
最新留言