【JS】从Function说原型
前言
最近小组要做学习与交流,本来准备做ES6的使用,组员说先讲下JS的原型。琢磨了下,一脸懵,平时在用这个,但是让我去讲,还真不知道怎么讲,趁机再梳理一次。
Function
平时会使用大量的Function,但是真的懂Function吗?
console.log(typeof Object);
console.log(typeof Function);
上面的输出是什么呢?机智的小伙伴已经知道是,对,是function。是不是感觉很奇怪?
JS对象
首先,要明确一点,JS中所有的函数都是Function的实例。要注意的一点是,Function自身能够生成自身,即Function的原型就是Function。
JS对象包含三种:
- 本地对象。独立于宿主环境(浏览器)的对象。包括Object、Array、Date、RegExp、Function、Error、Number、String、Boolean。
- 内置对象:包括Math、Global(window,在js中就是全局变量),使用的时候不需要new。
- 宿主对象:包括自定义对象、DOM、BOM。
一张图
在JS中Object、Function既是对象、也是函数。
prototype
prototype表明实例自身的属性。如上图中function Foo,Foo.prototype中记录中Foo自身的原型属性,其中constructor表明Foo的构造器。
_proto_
_proto_ 表明实例的原型是谁,如上图示:foo的_proto_指向Foo.prototype。
prototype与_proto_的区别
- prototype表明实例自身的属性
- _proto_表明实例的原型是谁
- 构造器具有prototype和_proto_,单纯实例只具有_proto_
原型链
原型链就是原型的集合,一级一级向上查找,直到查找到null为止。
属性查找
- 在访问对象的某个成员的时候会先在对象中找是否存在
- 如果当前对象中没有就在构造函数的原型对象中找
- 如果原型对象中没有找到就到原型对象的原型上找
- 知道Object的原型对象的原型是null为止
比如我们想要查找实例foo原型上的属性,首先先找它的原型属性foo._proto_,即Foo.prototype。接着继续向上查找Foo.prototype._proto_,即Object.prototype。继续向上Object.prototype._proto_,找到了null。查找结束。
原型继承
需要注意的是原型继承与类继承是不同的概念。下面看一个demo:
Function.prototype.hh = 123;
function Foo(){}
let foo = new Foo();
console.log(Foo.hh)
console.log(foo.hh)
输出是:
123
undefined
你思考的结果是否是和实际输出一样呢?
按照我们面向对象的思维,Foo是Function的实例,foo是Foo的实例,foo应当是应该继承Function的属性和Foo的属性的,但是很显然,并不是。这是类继承和原型继承很大的不同所在。
原因还是要从上面的那张图说起,此时foo的原型链应该是Foo.prototype + Object.prototype + null,和Function.prototype并没有什么关系。这一点是需要特别注意的。
如果能搞懂这一点,那么原型继承也就懂,demo就不写了,网上一堆。
原型链上的元素
原型链上的元素都是对象,原型链必须是有限长度,且原型链以null为终结,那么其它位置上都是些什么呢。
倒数第一位
null,无可否认。
倒数第二位
从实现上来看,倒数第二位与倒数第一位都是唯一的。因为原型链上所有的元素都是对象,所以倒数第二个元素应该是所有对象的基础对象。即Object.prototype。
倒数第三位
那么倒数第三个元素是不是固定的呢?不是。从倒数第二个元素是Object.prototype来看,通过{}字面量和new Object()创建的对象都在倒数第三这个位置。另外还有两个特例,一个是除内置函数之外的内置对象,如Math、JSON;一个是除Object之外的内置函数的prototype属性指向的对象,如Function.prototype。即Math、JSON、(Function、Array、String、Boolean、Number、Date、RegExp、Error).prototype。
倒数第四位
所有除Object之外的内置函数作为构造器调用时生成的实例对象都在倒数第四位。即Object、Function、Array、String、Boolean、Number、Date、RegExp、Error。