JavaScript———原型和原型链

JavaScript 中没有类的概念的,主要通过原型链来实现继承。通常情况下,继承意味着复制操作,然而 JavaScript默认并不会复制对象的属性,相反,JavaScript只是在两个对象之间创建一个关联(原型对象指针),这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

原型

当我们 new 了一个新的对象实例,明明什么都没有做,就直接可以访问 toString 、valueOf 等原生方法。那么这些方法是从哪里来的呢?答案就是原型。

在这里插入图片描述

在控制台打印一个空对象时,我们可以看到,有很多方法,已经“初始化”挂载在内置的 proto 对象上了。这个内置的 proto 是一个指向原型对象的指针,它会在创建一个新的引用类型对象时(显示或者隐式)自动创建,并挂载到新实例上。当我们尝试访问实例对象上的某一属性 / 方法时,如果实例对象上有该属性 / 方法时,就返回实例属性 / 方法,如果没有,就去 proto 指向的原型对象上查找对应的属性 / 方法。这就是为什么我们尝试访问空对象的 toString 和 valueOf 等方法依旧能访问到的原因,JavaScript 正是以这种方式为基础来实现继承的。

构造函数

如果说实例的 proto 只是一个指向原型对象的指针,那就说明在此之前原型对象就已经创建了,那么原型对象是什么时候被创建的呢?这就要引入构造函数的概念。

其实构造函数也就只是一个普通的函数而已,如果这个函数可以使用 new 关键字来创建它的实例对象,那么我们就把这种函数称为 构造函数。


// 普通函数
function person () {}

// 构造函数,函数首字母通常大写
function Person () {}
const person = new Person();

原型对象正是在构造函数被声明时一同创建的。构造函数被申明时,原型对象也一同完成创建,然后挂载到构造函数的 prototype 属性上:
在这里插入图片描述

原型对象被创建时,会自动生成一个 constructor 属性,指向创建它的构造函数。这样它俩的关系就被紧密地关联起来了。

细心的话,你可能会发现,原型对象也有自己的 proto ,这也不奇怪,毕竟万物皆对象嘛。原型对象的 proto 指向的是 Object.prototype。那么 Object.prototype.proto 存不存在呢?其实是不存在的,打印的话会发现是 null 。这也证明了 Object 是 JavaScript 中数据类型的起源。

分析到这里,我们大概了解原型及构造函数的大概关系了,我们可以用一张图来表示这个关系:
在这里插入图片描述

原型链

说完了原型,就可以来说说原型链了,如果理解了原型机制,原型链就很好解释了。其实上面一张图上,那条被 proto 链接起来的链式关系,就称为原型链。

原型链的作用:原型链如此的重要的原因就在于它决定了 JavaScript 中继承的实现方式。当我们访问一个属性时,查找机制如下:

  • 访问对象实例属性,有则返回,没有就通过 proto 去它的原型对象查找。
  • 原型对象找到即返回,找不到,继续通过原型对象的 proto 查找。
  • 一层一层一直找到 Object.prototype ,如果找到目标属性即返回,找不到就返回 undefined,不会再往下找,因为在往下找 proto 就 是 null 了。

通过上面的解释,对于构造函数生成的实例,我们应该能了解它的原型对象了。JavaScript 中万物皆对象,那么构造函数肯定也是个对象,是对象就有 proto ,那么构造函数的 proto 是什么?

我们可以打印出来看一下:
在这里插入图片描述
现在才想起来所有的函数可以使用 new Function() 的方式创建,那么这个答案也就很自然了,有点意思,再来试试别的构造函数。
在这里插入图片描述
这也证明了,所有函数都是 Function 的实例。等一下,好像有哪里不对,那么 Function.proto 岂不是。。。
在这里插入图片描述
按照上面的逻辑,这样说的话,Function 岂不是自己生成了自己?其实,我们大可不必这样理解,因为作为一个 JS 内置对象,Function 对象在你脚本文件都还没生成的时候就已经存在了,哪里能自己调用自己,这个东西就类似于玄学中的“道”和“乾坤”,你能说明它们是谁生成的吗,天地同寿日月同庚不生不灭。。。算了,在往下扯就要写成修仙了=。=

至于为什么 Function.proto 等于 Function.prototype 有这么几种说法:

  • 为了保持与其他函数保持一致
  • 为了说明一种关系,比如证明所有的函数都是 Function 的实例。
  • 函数都是可以调用 call bind 这些内置 API 的,这么写可以很好的保证函数实例能够使用这些 API。

注意点

关于原型、原型链和构造函数有几点需要注意:

  • proto 是非标准属性,如果要访问一个对象的原型,建议使用 ES6 新增的 Reflect.getPrototypeOf 或者 Object.getPrototypeOf() 方法,而不是直接 obj.proto,因为非标准属性意味着未来可能直接会修改或者移除该属性。同理,当改变一个对象的原型时,最好也使用 ES6 提供的 Reflect.setPrototypeOf 或 Object.setPrototypeOf。
let target = {};
let newProto = {};
Reflect.getPrototypeOf(target) === newProto; // false
Reflect.setPrototypeOf(target, newProto);
Reflect.getPrototypeOf(target) === newProto; // true
  • 函数都会有 prototype ,除了 Function.prototype.bind() 之外。
  • 对象都会有 proto ,除了 Object.prototype 之外(其实它也是有的,之不过是 null)。
  • 所有函数都由 Function 创建而来,也就是说他们的 proto 都等于 Function.prototype。
  • Function.prototype 等于 Function.proto
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值