前言
原型与原型链是JavaScript中的基础知识点,同时也是面试的高频考点,今天带大家来熟知它。 在讲原型前,我们先来看看构造函数。
构造函数创建对象
js
function Person(name) {
this.name = name
}
const p1 = new Person()
const p2 = new Person()
p1.name = 'Dog'
p2.name = 'Cat'
console.log(p1.name) // Dog
console.log(p2.name) // Cat
由上边代码可知,通过构造函数为实例对象定义属性虽然方便,但有一个缺点。同一个构造函数的多个实例之间无法共享属性,从而造成系统资源的浪费。
上面代码中p1
和p2
是同一个构造函数的不同实例
,它们都具有name
属性,由于name属性是生成在每个实例对象上边的,因而两个实例就生成了两次
。也就是说,每创建一个实例就会新增一个name属性
,这即没有必要的,又浪费了系统资源,对于每个实例都需要name属性,其次完全可以共享
的。
这个问题的解决方法就是JavaScript的原型对象(prototype)
。
我们先看以下两点:
- 每个构造函数都有一个prototype属性
- prototype是函数才有的属性
我们先来验证上边第一点,每个构造函数身上都有一个prototype属性
,我们打印下Person构造函数上的prototype属性:
js
function Person(name) {
this.name = name
}
console.log(Person.prototype) // Person {}
打印发现果真如此, 接着我们看下边例子:
js
Person.prototype.name = 'Bill'
const p1 = new Person()
const p2 = new Person()
console.log(p1.name) // Bill
console.log(p2.name) // Bill
我们首先往Person构造函数上的prototype属性添加了一个name属性,然后使用Person构造了两个实例,发现两个实例的name
属性都是Bill
,说明这两个实例共享了同一个原型对象
,即共享了Person.prototype
,因而它们共享了同一个原型对象上的name属性
。
那么这两个实例为什么可以直接访问到Person.prototype
上的属性呢?
其实,函数的prototype
会有一个指向。 函数的prototype属性指向一个对象
,这个对象正是调用该构造函数而创建的实例的原型
,也就是上边p1
与p2
实例对象的原型。
也就是说:构造函数(Person)有一个prototype属性指向其实例对象(p1、p2)的原型
那么实例对象(p1、p2)的原型
是什么?
对于实例上的原型,它用__proto__
来表示,即p1.__proto__
,p2.__proto__
,我们打印下看看:
js
console.log(p1.__proto__) // Person {}
console.log(p2.__proto__) // Person {}
好了,我们现在可以认识一下什么是原型了。
原型
:每一个JavaScript对象(null除外)
在创建的时候就会与之关联另一个对象(即原型)
,这个对象就是我们所说的原型
,每个对象都会从原型"继承"属性
。
这里强调一下,JavaScript中的函数本质也是一个对象
,所以函数也有原型。
__proto__
:每一个JavaScript对象(null除外)
都具有的一个属性,叫__proto__
,这个属性会指向其构造函数的原型。既然是指向那么它们一定相等。
js
console.log(p1.__proto__ === Person.prototype) // true
那么Person.prototype
同样也有__proto__
这个属性:
js
console.log(Person.prototype.__proto__ === Object.prototype) // true
// ES5的方法,获取对象的原型
console.log(Object.getPrototypeOf(p1) === Person.prototype) // true
既然实例对象
和构造函数
都可以指向原型
,那么原型是否有属性
可以指向构造函数
或者实例
呢?
指向实例倒是没有,因为一个构造函数
可能生成多个实例
,但是原型对象指向构造函数是有的,这个属性就是constructor
。
constructor
每一个原型都有一个constructor
属性指向关联的构造函数
既然每个原型都有一个constructor属性,那么我们可以通过p1实例对象
上的原型__proto__
上的constructor属性
来验证其是由哪个构造函数产生的
。
作用:可以得知某个实例对象到底是哪一个构造函数产生的
js
console.log(Person.prototype.constructor === Person) // true
console.log(p1.__proto__.constructor === Person); // true
通过打印可知道,Person的原型对象指向构造函数Person,即验证了原型对象的constructor属性指向关联的构造函数
上边主要对prototype
和__proto__
进行了介绍。
我们接着来看看实例和原型之间关系:
实例与原型
当读取不到实例上的属性时,就会查找与对象关联的原型中的属性,如果还查不到,就去原型的原型上查找,一直找到最顶层为止。
js
function Person() {
}
Person.prototype.name = '张三'
const p3 = new Person()
p3.name = '李四'
console.log(p3.name) // 李四
delete.p3.name
console.log(p3.name) // 张三
在上边例子中,我们先是往实例对象上添加了name
属性,当我们打印时,结果自然为张三,接着我们删除了p3实例对象上的name属性,再次打印时,由于p3实例对象上已没有name属性,所以会从实例对象的原型对象,也就是p3.__proto__
上查找,我们知道实例的原型对象
指向其构造函数的原型对象
,所以会找到Person.prototype
上的name
属性,最后打印结果为张三。
幸运的是,我们上边Person构造函数上刚好有name属性。 如果Person构造函数上没有name属性呢?Person构造函数原型的原型又是什么呢?
原型的原型
在前边例子已经讲了,原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:
js
const obj = new Object()
obj.name = 'Bill'
console.log(obj.name) // Bill
其实原型对象
就是通过Object构造函数生成的
,结合之前所讲,实例的__proto__指向构造函数的prototype
。
原型链
那Object.prototype
的原型
呢? 我们打印下:
js
console.log(Object.prototype.__proto__) // null
null
表示没有对象,即该处不应该有值
。 所以Object.prototype.__proto__
等于null
跟Object.prototype
没有原型,其实表达的是一个意思。 所以查找属性时查找到Object.prototype
就可以停止查找了。
由此我们可以得出:由相互关联的原型
组成的链状结构
就是原型链
。
大家结合上边例子来看下边的图,由相互关联的原型
组成的链状结构
就是原型链
,多看几遍你肯定能搞懂👍

扩展
1.constructor
js
function Person() {
}
const p1 = new Person()
console.log(p1.constructor === Person) // true
我们知道构造函数的原型对象上有一个constructor属性指向构造函数,当读取p1.constructor时,其实p1上并没有constructor属性,当不能读取到constructor属性时,就会从p1的原型也就是Person.prototype中读取,正好原型上有该属性,所以:
js
p1.constructor === Person.prototype.constructor // true
2.__proto__
绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于Person.prototype中,实际上,它是来自于Object.prototype
,与其说它是一个属性,不如说是一个getter/setter
,当使用obj.__proto__
时,可以理解成返回了Object.getPrototypeOf(obj)
。
3.真的是继承吗?
前面讲到"每一个对象都会从原型上'继承'属性"
,实际上,继承是一个非常具有迷惑性的说法。 继承意味着复制
的操作,然而JavaScript默认并不会复制对象的属性,相反,JavaScript只是在两个对象之间创建一个关联
,这样,一个对象可以通过委托访问
另一个对象上的属性和函数,所以与其叫它继承,委托的说法反而更准确些。
看到这里的小伙伴给你们自己点个赞哈👏,现在咱动手敲起代码来,验证上边说的每一点,做到真正把它记住,多实践、多思考、多总结,画图形成你们的知识脉络。
结语
以上就是本篇的所有内容,喜欢的点点赞支持下luckyCover哈,欢迎评论区互动呀~