likes
comments
collection
share

原型链、__proto__、constructor、prototype都是些啥玩意儿?

作者站长头像
站长
· 阅读数 32

相信有很多js的朋友,在日常学习或者工作过程都有听说过constructorprototype__proto__、原型链等等的一些概念。在经过多年的一知半解之后,我决定将这些一一梳理一下,有理解不对的地方,希望各位大佬指正。

什么是原型链和__proto__ ?

要理解什么是原型链,首先要知道,在v8中,对象的继承原理以及对象的__proto__ 属性。简单地理解,继承就是一个对象可以访问另外一个对象中的属性和方法。而在js中,继承是通过原型链来实现的,而Js 的每个对象都包含了一个隐藏属性 __proto__ 。V8的属性和方法查找机制是,A对象查找不到时,去查找A对象的原型,如果A对象的原型指向B对象。则可实现A对象继承B对象的效果。可以参考下图来帮助理解(图画的有点拉垮):

原型链、__proto__、constructor、prototype都是些啥玩意儿?

可以看到使用 A.type 时,给人的感觉属性 type 都是对象A 本身的属性,但实际上这些属性都是位于原型对象上,我们把这个查找属性的路径称为原型链,它像一个链条一样,将几个原型链接了起来

什么是constructor?

要理解什么是constructor,就要知道什么是构造函数。我有看到网上有很多对构造函数的定义。有人说,构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。看到这个定义我依然感觉一脸懵。我觉得下面这个说法比较能让人理解:当任意一个普通函数用于创建一类对象时,它就被称作构造函数,或构造器。

理解了构造函数,那我们就有个疑问了,那我怎么知道这个对象是被函数new出来的呢?或者说我如何获取该对象的构造函数。js给我们提供了一个方法。那就是constructor。通过对象.constructor拿到创建该实例对象的构造函数。该方法获取构造函数存在一定的偏差,下面会讲到。

例如下面的代码中

function Person() {} 
var person1 = new Person()
console.log(person1.constructor)  //结果输出: [Function: Person]

通过对象.constructor 我们其实可以看出来,constructor其实是对象的一个属性。每个对象都有constructor属性。我们可以通过下图来理解该属性的指向。

原型链、__proto__、constructor、prototype都是些啥玩意儿?

上图可以看出,

  1. person1Person对象的实例,他们的constructor指向创建它们的构造函数,即Person函数;
  2. Person是一个函数,所以是Function的实例对象,所以Personconstructor指向Function
  3. Function函数,它是JS的内置对象,所以constructor属性指向自己。

prototype又是个啥?

在没有仔细研究之前,我一直没有搞懂prototype__proto__的区别,甚至一度以为这两个是一个概念的东西。

上文中我们提到__proto__是对象的一个隐藏属性,这个属性通常指向该对象想继承的对象。而在学习constructor的时候,我们可以知道,一个函数可以实例出成千上万的对象。当这些对象都拥有一个相同的方法时,每个方法都占用一定的内存,就会造成浪费。而prototype的出现可以解决该问题。当需要为很多实例对象添加相同的方法时,可以将该方法放到prototype中。以达到共用的效果,如下图所示:

原型链、__proto__、constructor、prototype都是些啥玩意儿?

其实,有没有发现,constructor就是一个共用的属性,所以,在js中constructor是被放在了prototype对象中。在实际开发中,person1person2对象里面是不存在constructor的。

prototype__proto__最大的区别?

讲了这么多,我们可以知道的是,prototype__proto__最大的区别就是。prototype是函数里面的一个对象,而__proto__是对象里面的一个属性。

但是,函数也是一个对象,那么它有没有__proto__属性呢?

答案是有的。

既然说,prototype是函数里面的一个对象,那它有没有__proto__属性呢?

答案是有的,基于所以__proto__属性都指向它的原型对象,我们可以看下以下代码:

function Person() {} 
console.log(Person.prototype.__proto__.constructor) // [Function: Object]

Object的__proto__属性为null。

那么Person.prototypePerson.__proto__之间是什么关系呢?

答案是没有关系。PersonFunction 构造函数的一个实例,所以 Person.__proto__ = Function.prototypePerson.prototype 是调用 Object 构造函数的一个实例,所以 Person.prototype.__proto__ = Object.prototype 因此 Person._proto_Person.prototype 没有直接关系

可参考以下代码:

function Person() {} 
var person1 = new Person()
console.log(Person.__proto__ === Function.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) //ture

从上文,我们可以知道,prototype对象里面放的是函数的共用属性和方法,那如果我有一个实例对象将其中的属性或者方法修改了怎么办,那其他的实例对象指向的也会改掉吗?

答案是不会的。在实例对象要修改共用属性时,会为该对象重新创建一个属性。例如,当我们修改person1.constructor = Function时,会给person1加上constructor属性。查看时,会优先查找person1对象中的constructor,当查找不到时,再去查找构造函数Person的属性。

基于上面的操作,我们知道constructor时可以被随时改变的,有时候,并不一定百分之百指向该实例对象的构造函数,那么我们要通过什么方法可以来始终找到该对象的构造函数呢?其实在上面讲__proto__prototype的关系中,我们可以得到答案:

  1. 实例对象.proto = 创建自己的构造函数内部的prototype(原型对象)
  2. 实例对象.proto.constructor = 创建自己的构造函数

最后,浅浅的总结一下吧

1,prototype__proto__最大的区别就是。prototype是函数里面的一个对象,而__proto__是对象里面的一个属性 2,constructor指向实例对象的构造方法,实例对象本身并没有该属性,它通常放在其构造函数的prototype对象中,但是它可以被更改,当它被更改时,将为它单独创建一个。 3,prototype是一个对象,只有函数才拥有,在函数声明的时候就创建了,主要是为了存放一些共有的属性和方法,为了节省内存空间。 4,__proto__始终指向自己的原型对象,是构成原型链的重要一环。