原型链、__proto__、constructor、prototype都是些啥玩意儿?
相信有很多js的朋友,在日常学习或者工作过程都有听说过constructor
、prototype
、__proto__
、原型链等等的一些概念。在经过多年的一知半解之后,我决定将这些一一梳理一下,有理解不对的地方,希望各位大佬指正。
什么是原型链和__proto__ ?
要理解什么是原型链,首先要知道,在v8中,对象的继承原理以及对象的__proto__
属性。简单地理解,继承就是一个对象可以访问另外一个对象中的属性和方法。而在js
中,继承是通过原型链来实现的,而Js
的每个对象都包含了一个隐藏属性 __proto__
。V8的属性和方法查找机制是,A
对象查找不到时,去查找A
对象的原型,如果A
对象的原型指向B对象。则可实现A
对象继承B
对象的效果。可以参考下图来帮助理解(图画的有点拉垮):
可以看到使用 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
属性。我们可以通过下图来理解该属性的指向。
上图可以看出,
person1
是Person
对象的实例,他们的constructor
指向创建它们的构造函数,即Person
函数;Person
是一个函数,所以是Function
的实例对象,所以Person
的constructor
指向Function
;- 而
Function
函数,它是JS的内置对象,所以constructor
属性指向自己。
prototype又是个啥?
在没有仔细研究之前,我一直没有搞懂prototype
和__proto__
的区别,甚至一度以为这两个是一个概念的东西。
上文中我们提到__proto__
是对象的一个隐藏属性,这个属性通常指向该对象想继承的对象。而在学习constructor
的时候,我们可以知道,一个函数可以实例出成千上万的对象。当这些对象都拥有一个相同的方法时,每个方法都占用一定的内存,就会造成浪费。而prototype
的出现可以解决该问题。当需要为很多实例对象添加相同的方法时,可以将该方法放到prototype
中。以达到共用的效果,如下图所示:
其实,有没有发现,constructor
就是一个共用的属性,所以,在js中constructor
是被放在了prototype
对象中。在实际开发中,person1
、person2
对象里面是不存在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.prototype
和Person.__proto__
之间是什么关系呢?
答案是没有关系。Person
是 Function
构造函数的一个实例,所以 Person.__proto__ = Function.prototype
,Person.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
的关系中,我们可以得到答案:
- 实例对象.proto = 创建自己的构造函数内部的prototype(原型对象)
- 实例对象.proto.constructor = 创建自己的构造函数
最后,浅浅的总结一下吧
1,prototype
和__proto__
最大的区别就是。prototype
是函数里面的一个对象,而__proto__
是对象里面的一个属性
2,constructor
指向实例对象的构造方法,实例对象本身并没有该属性,它通常放在其构造函数的prototype
对象中,但是它可以被更改,当它被更改时,将为它单独创建一个。
3,prototype
是一个对象,只有函数才拥有,在函数声明的时候就创建了,主要是为了存放一些共有的属性和方法,为了节省内存空间。
4,__proto__
始终指向自己的原型对象,是构成原型链的重要一环。
转载自:https://juejin.cn/post/7160225485719339021