函数和对象
首先我们先来看一下JavaScript中函数和对象的关系,这对我们理解原型链有很大的帮助。
你可能也会看到“JavaScript中万物皆对象”,这显然是错误的。
实际上在JavaScript中,有许多特殊的对象子类型,可以叫做复杂基本类型。
函数就是对象的一个子类型。
函数的本质就是对象。
JavaScript: 嘿,兄弟,我是一夜情的产物,不要要求太多!
在JavaScript中,检测对象类型时,强烈建议使用Object.prototype.toString方法。typeof的一些返回值在标准文档中并未定义,因此不同的引擎实现可能不同。
回到正题,实际上,函数和对象没有本质的区别,函数是特殊的对象。
函数对象天生带有prototype属性,也就是每个函数在创建之后会天生拥有一个与之相关联的原型对象,这个原型对象中拥有一个constructor属性,该属性指向这个函数。
很多人认为新创建的函数对象身上除了prototype属性外,还有constructor这个属性,但是这里使用的constructor属性实际上是从原型链中获取的,即Function.prototype.constructor
备注:在ECMAScript标准中函数创建相关章节有这样一句话:NOTE A prototype property is automatically created for every function, to allow for the possibility that the function will be used as a constructor.
解释了给新创建函数添加prototype属性的意义在于便于该函数作为构造函数使用。
https://www.ecma-international.org/ecma-262/5.1/#sec-13.2
搞清楚了JavaScript中函数和对象的关系后,我们接下来看一看什么是原型对象。
原型对象
当构造函数被创建出来的时候,会默认关联一个Ojbect类型的新对象,这个对象就是当前构造函数的原型对象,构造函数的原型对象默认是一个空对象。
当然,构造函数创建出来的对象可以访问该构造函数原型对象的属性和方法。
通过上面的代码和结果进行分析,我们可以得出构造函数、实例、原型对象三者之间的关系。
我们可以得出以下结论
- 1.构造函数Man可以通过prototype属性访问到它的原型对象
- 2.通过构造函数Man实例化出来的d可以通过__proto__属性访问到Man的原型对象
- 3.Man的原型对象可以通过constructor(构造器)属性访问其关联的构造函数
prototype 函数对象拥有的属性,指向它的原型对象
proto 所有的对象都拥有__proto__属性,指向实例的原型
construtor 构造器,原型对象可以通过constructor来访问其所关联的构造函数。当然,每个实例对象也从原型中继承了该属性
注意:__proto__属性并不在ECMAScript标准中,只为了开发和调试而生,不具备通用性,不能出现在正式的代码中。
搞清楚了原型对象,接下来我们来看原型链。
原型链
这一夜,原型链没少折腾…
// 1.让我们先来看一看食物链(原型链)的顶端 null
console.log(Object.prototype.__proto__); //null
// 2.Function.prototype的原型对象为Object.prototype而不是它自己
console.log(Function.prototype.__proto__ == Object.prototype);//true
// 3.Function和Object的构造函数都是Function
console.log(Function.constructor == Function); //true
console.log(Object.constructor == Function); //true
// 4.Function.prototype的构造函数是Function
console.log(Function.prototype.constructor == Function); //true
// 5.m1的原型对象为Man.prototype
console.log(m1.__proto__ == Man.prototype); //true
// 6.Man.prototyepe|Woman.prototype的constructor指向Object
// Man.prototyepe|Woman.prototype的原型对象为Object.prototype
// 先删除实例成员,通过原型成员访问
delete Man.prototype.constructor;
delete Woman.prototype.constructor;
console.log(Man.prototype.constructor == Object); //true
console.log(Woman.prototype.constructor == Object); //true
console.log(Man.prototype.__proto__ == Object.prototype); //true
console.log(Woman.prototype.__proto__ == Object.prototype); //true
// 7.Man和Woman的构造函数为Function
// Man和Woman的构造函数的原型对象为空函数
console.log(Man.constructor == Function); //true
console.log(Woman.constructor == Function); //true
console.log(Man.__proto__ == Function.prototype); //true
console.log(Woman.__proto__ == Function.prototype); //true
原型链的访问规则
就近原则
对象在访问属性或方法时,先检查自己的实例,如果存在就直接使用。如果不存在那么就去原型对象上去找,存在就直接使用,如果没有就顺着原型链一直往上查找,找到即使用,找不到就重复该过程直到原型链的顶端,如果还没有找到相应的属性或方法,就返回undefined,报错。
三种检验方法
Object.getPrototypeOf方法用于获取指定实例对象的原型对象。
isPrototypeOf方法用于检查某对象是否在指定对象的原型链中。
instanceof运算符的作用跟isPrototypeOf方法类似,左操作数是待检测的实例对象,右操作数是用于检测的构造函数。如果右操作数指定构造函数的原型对象在左操作数实例对象的原型链上面,则返回结果true,否则返回结果false。
注意:不要错误的认为instanceof检查的是该实例对象是否从当前构造函数实例化创建的,其实它检查的是实例对象是否从当前指定构造函数的原型对象继承属性。
最佳的组合继承方案
- 1.使用原型链实现对原型属性和方法的继承
- 2.通过伪造(冒充)构造函数来实现对实例成员的继承,并且解决了父构造函数传参问题