本文正在参加「金石计划」
前言: 本文适合阅读人群——了解原型链基本概念
学习参考文章:
用自己的方式(图)理解constructor、prototype、__proto__和原型链
帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)
理清原型对象、 实例对象、构造函数 - 掘金 (juejin.cn)
理解 es6 class 中 constructor 方法 和 super 的作用 - 掘金 (juejin.cn)
prototype
prototype是构造函数指向其原型对象的属性
我们可以看到下列Function构造函数中有属性prototype,也就是说所有函数对象都有这个属性
接下来的部分通过一段代码示例讲解
function Person(name,age) {
this.name = name
this.age = age
}
Person.prototype.logBaseMsg = function() {
console.log('姓名:' + this.name + ',年龄:' + this.age);
}
let p = new Person('ZhangSan',18)
console.log(p)
如上代码,构造函数Person有两个属性,name和age,在其原型上添加打印基本信息的方法。
首先为什么是在原型上添加方法?
当我们构造一个对象时,如果方法作为属性写在构造函数中,我们每new一个对象,相当于创建了一个方法,有多个实例对象,虽然方法一样,但是却存储在不同的内存空间里,如果写在原型中,对象调用方法时都指向同一个方法,避免了浪费内存。
上列代码控制台输出为
可以看到输出为一个对象,该对象分为两部分
- 对象属性:
age,name [[Prototype]]:对象的内置属性,隐式原型。
[[Prototype]]
[[Prototype]]是对于其他对象的引用,即该对象的原型,当读取对象某一属性时,若没有,就在原型链上查找,也就是继续读取下一级的[[Prototype]]。
等等,原型链?对象的原型?这不是__proto__?(细心的朋友发现的,输出p.__proto__与[[Prototype]]是一样的)
众所周知,对象一定有__proto__这个属性,指向原型对象,而这个属性被命名为对象的原型,而由于原型对象也是对象,所以也有__proto__这个属性,继而形成了原型链,但是我们刚刚输出的实例对象p中并没有看到__proto__,那么对象又是怎么读取到这个属性的?我们将刚刚输出的[[Prototype]]多次展开得到了下面:
我们发现,原来是下层的[[Prototype]]中有__proto__这个属性啊,根据上述[[Prototype]]的特性,__proto__能被对象p访问!而且__proto__指向了构造函数Person的原型对象。
总结一下
[[Prototype]]相当于隐式的原型,而__proto__是显式的原型,它们指向相同,__proto__可以直接通过obj.__proto__的方式访问
我们重新来解读一下输出的实例对象p
实例对象p中有2个显式属性(name,age)和一个隐式属性[[Prototype]],[[Prototype]]属性指向原型对象,我们在原型对象写入了一个logBaseMsg方法,在[[Prototype]]中可以看到
Person的原型对象也是对象,所以也有[[Prototype]],而它的构造函数是Object(普通的对象都认为是Object构造的),所以第二层[[Prototype]],指向Object.prototype,是构造函数Object的原型,该原型有属性__proto__。因为对象的原型中写有公共属性__proto__,所以所有对象都能访问到__proto__。
根据上述描述我们可以得出这样的关系
由于函数也是对象,所以也能访问到__proto__属性,我们将关系补充完整
我们继续看[[Prototype]]中还有什么,可以看到除了我们写入的方法logBaseMsg和对象内置属性[[Prototype]]外,还有一个constructor
constructor
我们知道,原型对象Person.prototype能够通过constructor访问到实例的构造函数Person,而实例对象p也能通过constructor访问到构造函数Person,但是他们访问的方式并不相同。
-
首先需要明确的一点所有对象都有
constructor,我们返回本文第三张图Object原型对象可以看到constructor,所以constructor和__proto__一样,是所有对象都有的公共属性。 -
我们使用对象访问,
p.constructor实际上是访问了[[Prototype]]中的constructor,也就是访问了原型上的属性,而原型的constructor,也就是指向了构造函数。 -
而我们使用
Person.prototype.constructor访问是直接访问
所以如果说constructor是供实例对象中指向构造函数的属性,我认为是不太严谨的,试想一下Person.prototype是不是也是一个对象?但Person.prototype.constructor却是指向Person,按照上面的说法,他不是应该指向构造函数Object吗?那是因为Person.prototype已经有constructor对象了,而我们实际要访问的是其下层[[Prototype]]上的constructor,所以p.constructor实际是p.__proto__.constructor。
constructor是在原型对象上,用于访问构造函数的属性。
学习完constructor我们再次补充关系图
最后我们再对比一下ES6中的Class,将Person改造为Class
class Person {
constructor(name,age) {
this.name = name
this.age = age
}
logBaseMsg() {
console.log('姓名:' + this.name + ',年龄:' + this.age)
}
}
let p = new Person('ZhangSan',18)
我们打印p
Class的写法比构造函数的写法更好理解,在constructor中写的就是构造函数的内容,我们实例化一个类时,要先执行constructor中的内容
大家想一下,我们在创建构造函数的实例时,是不是也需要执行构造函数中的内容呢?写在constructor外的就是类的公共方法和属性,对应prototype,也就是原型对象。
相关文章:new的实现