原型继承
定义
ES2019规范里描述的Prototype:
4.3.5 prototype /
object that provides shared properties for other objects
prototype被定义为:给其他对象提供共享属性的对象。也就是说prototype自己也是对象,只是被用以承担某个职能罢了。
_proto_
Every object has an implicit reference (called the object’s prototype)
规范中明确描述了所有对象,都有一个隐式引用,它被称之为这个对象的prototype原型。
ECMAScript规范描述prototype是一个隐式引用,但之前的一些浏览器,已经私自实现了 _proto_ 这个属性, 使得可以通过obj._proto_ 这个显式的属性访问,访问到被定义为隐式属性的 prototype。所以事实是这样的:
- 通过 Object.getPrototypeOf(obj) 间接访问指定对象的 prototype 对象。
- 通过 Object.setPrototypeOf(obj, fatherObj) 间接设置指定对象的 prototype 对象。
- 部分浏览器提前开了 _proto_ 的口子,使得可以通过 obj._proto_ 直接访问原型,通过 obj._proto_ = fatherObj 直接设置原型。
- ECMAScript 2015 规范只好向事实低头,将 _proto_ 属性纳入了规范的一部分。
两类原型继承方式
所谓的原型继承,就是指设置某个对象为另一个对象的原型(塞进该对象的隐式引用位置)。在JavaScript中,有两类原型继承的方式:显式继承和隐式继承。
显式原型继承
1 | const aa = { name: 'andy' }; |
如上,通过调用 Object.setPrototypeOf()
方法,我们将 bb
设置为 aa
的原型。
除了 Object.setPrototypeOf()
方法以外,还有一种途径,即是通过 Object.create()
方法,直接继承另一个对象。两者的差别在于:
Object.setPrototypeOf()
,给我两个对象,我把后一个对象设置为前一个对象的原型;Object.create
,给我一个对象,它将作为我创建的新对象的原型。
1 | const bb = { name: 'qiqi' }; |
Object.create
常用语获得一个没有原型的对象:
1 | const cc = Object.create(null); // cc没有原型,即没有 __proto__ 属性 |
隐式原型继承
想要得到一个包含了数据,方法以及关联原型三个组成部分的丰满对象,一个相对具体的步骤如下:
- 创建空对象
- 设置该空对象的原型为另一个对象或者
null
- 填充该对象,增加属性或方法。
假设没有隐式原型继承,创建一个普通js
对象,要像下面这样:
1 | const obj = {}; // 创建一个空对象 |
所以JavaScript
隐式原型继承的方法原理与上相似,只不过通过一个关键字new
实现了上述几个步骤:
1 | function Person() {}; |
JavaScript
的主流继承方式,选择了隐式原型继承,它提供了几个内置的constructor
函数,如Object
, Array
, Boolean
, String
, Number
等。
不管是隐式继承还是显式继承,只是外在形态,核心是具备设置对象的隐式引用的功能。他们之间具备一定乎操作性,也就是说,拥有其中一个,可以实现另一个的部分行为。
万物皆对象
在JavaScript
的世界中,万物皆对象。而对象又分为 函数对象和 普通对象,所谓的函数对象,其实就是
JavaScript
的用函数来模拟的类实现。JavaScript
中的Object
和Function
就是典型的函数对象。
1 | typeof Object; // function |
由上可以看出,所有Function
的实例都是函数对象,其他的均为普通对象,其中包括 Function
实例的实例。
Javascript
中万物皆对象,而对象皆出自构造(构造函数)。
typeof
typeof
一般被用于来判断一个变量的类型。我们可以使用 typeof
来判断number
、undefined
、symbol
、string
、function
、boolean
、object
这七种数据类型。但是遗憾的是,typeof
在判断 object
类型时候,有些许的尴尬。它并不能明确的告诉你,该 object
属于哪一种 object
。
1 | typeof null; // object |
在JavaScript
最初的实现中,JavaScript
中的值是由一个表示类型的标签和实际数值表示的。对象的类型标签是0。由于null
代表的是空指针,因此,null
的类型标签是0,typeof null
也因此返回object
。
js 在此层存储变量的时候,会在变量的机器码的低位1~3位存储其类型信息。
- 1:整数
- 110:布尔
- 100:字符串
- 010:浮点数
- 000:对象
但是,对于undefined
和 null
来说,这两个值的信息存储是有点特殊的。
null
:所有机器码均为0undefined
:用-2^30整数来表示
所以在用typeof
来判断变量类型的时候,我们需要注意,最好是用typeof
来判断基本数据类型(包括symbol
),避免对null
的判断。
instanceof
1 | obj1 instanceof obj2 |
instanceof
用来判断后一个参数的原型是否处于前一个参数的原型链上。
1 | console.log(Object instanceof Object); // true |
如上,Object
和Function
的判断都会有点问题,这里,我们可以先来模拟instanceof
的实现:
1 | function instance_of(L, R) { |
接下来,我们来解释为什么会有这两种特殊:
1 | // 为了方便表述,首先区分左侧表达式和右侧表达式 |
1 | // 为了方便表述,首先区分左侧表达式和右侧表达式 |