本文由葡萄城技术团队原创并首发
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。
与多数面向对象的开发语言有所不同,虽然JavaScript没有引入类似类的概念(ES6已经引入了class语法糖),但它仍然能够大量的使用对象,那么如何将所有对象联系起来就成了问题。于是就有了本文中我们要讲到的原型和原型链的概念。
原型和原型链作为深入学习JavaScript最重要的概念之一,如果掌握它了后,弄清楚例如:JavaScript的继承,new关键字的原来、封装及优化等概念将变得不在话下,那么下面我们开始关于原型和原型链的介绍。
什么是原型?
JS中的对象包含了一个prototype的内部属性,这个属性所对应的就是该对象的原型。
我们先看下图:a、b、c 分别为数组、对象、函数。
可以看到,三者都有一个属性:__proto__
这个 __proto__ 称作 隐式原型。
除此之外,c还有一个属性:prototype
这个prototype 称作 显式原型。
小结一下:
- 所有引用类型(函数,数组,对象)都拥有__proto__属性(隐式原型)
- 所有函数除了有_proto_属性之外还拥有prototype属性(显式原型)
- 原型对象:每创建一个函数,该函数会自动带有一个prototype属性,该属性是一个指针,指向了一个对象,我们称之为原型对象。
函数除了有_proto_属性之外还拥有prototype属性,我们借助构造函数来寻找二者之间的关系。如下图:
总结如下(结合上图更容易理解):
1. 实例对象a只有__proto__(隐式原型),构造函数既有 __proto__(隐式原型)也有prototype(显式原型)
2. __proto__ 和 prototype 都是一个对象,既然是对象,就表示他们也有一个 __proto__
a.__proto__.__proto__ A.prototype.__proto__
3.实例对象a的隐式原型指向它构造函数的显式原型,指向的意思是恒等于
a.__proto__ === A.prototype
4. 当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的__proto__属性中调用查找,也就是它构造函数的prototype中调用查找。
什么是原型链?
先看下图,提出一个问题:
1. 在 Object的显式原型添加属性b,为什么 示例对象p 能使用此属性呢? p.b = b
2. 为什么 p.a 为undefined
如下图所示
1. 实例对象p的隐式原型(__proto__)是一个对象,有两个属性值:constructor 和 __proto__
2. p.__proto__.constructor 返回的结果为构造函数Person
3. p.__proto__.__proto__ .constructor 返回的结果为Object()函数
结合上面所讲的显式原型与隐式原型之间的关系,等同如下:
1. p.__proto__.__proto__ = Object.prototype
所以p.b打印结果为b,p没有b属性,会一直通过__proto__向上查找,最后当查找到Object.prototype时找到,最后打印出b,向上查找过程中,得到的是Object.prototype,而不是Function.prototype,找不到a属性,所以结果为undefined,这就是 原型链,通过__proto__向上进行查找,最终到null结束。
总结:
1. 查找属性,如果本身没有,则会去__proto__中查找,也就是构造函数的显式原型中查找,如果构造函数的显式原型中也没有该属性,因为构造函数的显式原型也是对象,也有__proto__,那么会去它的显式原型中查找,一直到null,如果没有则返回undefined
2. p.__proto__.constructor == function Person(){}
3. p.___proto__.__proto__== Object.prototype
4. p.___proto__.__proto__.__proto__== Object.prototype.__proto__ == null
5. 通过__proto__形成原型链而非protrotype
什么是原型继承?
Person.prototype 只是一个指针,指向的是原型对象,但是这个原型对象并不特别,它也只是一个普通对象。假设说,这时候,我们让 Person.prototype 不再指向最初的原型对象,而是另一个类 (Animal)的实例,情况会怎样呢?
执行该代码 Person.prototype = new Animal() 后,Person的prototype指针指向发生了变化,指向了一个 Animal 实例。
当 p 去访问 address 属性时,js会先在 p 的实例属性中查找,发现找不到后,就会去 Person 的原型对象上 查找。因为Person的原型对象已经被我们换成一个animal实例,所以就会先找animal实例的属性,当发现还是没有 address属性,就会去Animal的原型对象上查找,最终找到。
这就说明,我们可以通过原型链的方式,实现 Person 继承 Animal 的所有属性和方法。
结语
看到这,相信大家对原型和原型链的概念应该已经有了一定了解了,如果仍然不太理解,也不用气馁,因为闭包及原型链是JavaScript最难理解的几部分。相信之后在不断的开发实践中会使你理解的更为透彻,多学习多思考才能更快掌握。如果大家有任何反馈和问题,也欢迎通过评论区告诉我,谢谢。