JS中的原型和原型链
一、为什么设计原型?
- 在 JS 中,所有的数据类型都被设计为对象(原始类型也都有着对应的包装对象)
- JS 在设计之初只是为了让浏览器可以互动,是一门简单的脚本语言,引入面向对象范式中的“类”机制有些过于正式,增大学习难度
- 但是,确实需要一种机制来将所有对象联系起来。否则所有对象的属性都是私有的,一方面写多个对象时,需要一个个手写;另一方面也会比较占用内存
- 最终,选择通过“原型”来实现对象的继承机制
二、从构造函数说起
2.1 对象的创建 — 构造函数
一般创建对象有两种方式:
- 通过字面量创建
1 | let obj = {} |
- 通过构造函数创建
1 | let obj = new Object() |
但事实上,字面量创建本身也是通过调用构造函数来实现的,它是构造函数创建的语法糖。
构造函数创建对象的步骤是这样的:
- 创建新对象
- 将 this 指向这个新对象
- 执行构造函数中的代码
- 返回 this
1 | function Foo(name) { |
2.2 构造函数的原型
- 构造函数有一个属性 prototype,属性值为一个对象
- 使用时,将实例化对象的私有属性写在构造函数里,共有属性写在构造函数的 prototype 属性对象里
1 | function People(name) { |
三、了解原型链前必须了解的 6 大规则
声明:以下使用对象来代表 对象、数组、函数(即除 null 外的所有引用类型)
每个对象都是由构造函数创建的。这一点在上面已经讲得很清楚,不再举例
所有对象都可自由扩展属性。如:
1 | let obj = {}, ary = [], fun = function(){} |
可以看出,并不是只有对象可以自由增加属性,数组和函数也可以,因为它们本质上就是对象
- 所有对象都有一个 proto 属性,属性值是一个普通对象:
- 所有函数都有一个 prototype 属性,属性值是一个普通对象:
- 所有对象的 proto 属性值指向它构造函数的 prototype 属性值:
注意:例子中用的是严格相等符,也就是说除非它们指向同一个对象才会返回 true
- 当查找一个对象的属性时,如果对象本身没有这个属性,会去它的 proto (即它构造函数的 prototype)中寻找。用上面的例子说明:
1 | function People(name) { |
四、基于原型链的继承
4.1 原型链到底是什么?
简单从字面意思上理解:原型链就是由一个个原型组成的链条。如图:
4.2 原型链如何实现继承?
- 当查找 foo 对象的属性时,先确定它本身有没有这个属性
- 若没有,则找它构造函数的原型,也就是 Foo.prototype 有没有这个属性
- 若没有,则找 Foo.prototype 的构造函数的原型,也就是 Object.prototype,确定是否有这个属性(注意 Foo.prototype 本身也是一个对象(参见上面第 4 条原则),所以它也有 proto 属性,及自己的构造对象,也就是 Object)
- 若还是没有,则返回 undefined
- 图中这条向上查找的链条,就是原型链
注意:为了避免死循环,原型链是有“尽头”的。如图所示,Object.prototype 的原型对象是 null,null 没有原型,并作为原型链的最后一个环节,即: