阅读 1643

函数对象、对象、原型

序、范例代码

以下为范例代码,本文中讲解多次用到这段代码,可以在阅读本文前提前运行,边看文边敲码验证。

function Person(){}; //构造函数
var person = new Person(); //实例对象复制代码

一、对象

每个对象都是基于一个引用类型创建的,这个引用类型可以是原生类型(Object、Array、Function等),也可以是用户自定义的类型。

对象总的来说大致可以分成两种:

1、函数对象(Function Object)

通过new Function()生成函数并为其指定函数名,通过函数名来进行调用,其实就是我们通常所说的函数。

2、其他类型的对象(Object)

直接通过new操作符生成(除了Function类型)的对象,如new Object(); 。

对象通过new操作符创建的过程:

(1)创建一个新对象;

(2)将构造函数的作用域赋给新对象(this就会指向这个新对象);

(3)执行构造函数中的代码(为对象添加属性);

(4)返回新对象。

二、原型指针(__proto__)

当构造函数创建一个新实例(又称实例对象,下同)后,该实例下都会存在一个指针__proto__(内部属性),指向创建这个实例的构造函数(constructor指向值)下的原型对象(prototype)。只要是个对象,都会有__proto__。

根据范例代码来看,person是属于构造函数Person的一个实例,person.__proto__ === Person.prototype为true。要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

这里出现了另外两个名词:构造函数和原型对象,请看下面内容。

三、构造函数(constructor指向值)

构造函数的解释是这样的,创建对象时初始化对象。任何函数,只要通过new操作符来调用,那它就可以作为构造函数。像Object和Array等这样执行环境自带的称为原生构造函数,它们会在运行时自动出现在执行环境中。

观察范例代码,person是一个对象,而他的构造函数就是Person,Person.prototype.constructor === Person为true。

四、原型对象(prototype)

原型对象比较特殊(划重点),只有函数对象会拥有,是用来解决构造函数在创建实例的时候,防止重复执行所导致的性能的降低(这里主要指占用内存),为复用带来方便。

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性是一个指针,指向函数的原型对象,这个原型对象的用途是包含可以由特定类型的所有实例共享的属性和方法,如果按照字面意思来理解,那么prototype也是通过调用构造函数而创建的那个对象实例的原型对象(__proto__指向值)。

在默认情况下,所有原型对象(prototype)都会自动获得一个constructor属性,这个属性包含一个指针,指向prototype属性所在函数,通常会是构造函数。

五、原型搜索机制

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。

以上就是原型搜索机制的大致介绍。原型链继承(此篇不讲,有兴趣自行谷歌百度)则是在这个基础上进行扩展,然后实现继承,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

六、原型关系图


如上图,我们从最简单的关系入手。以范例代码为例,创建Person函数,然后进行实例化生成实例对象person。

我们来看看实例对象person和函数Person各自的原型关系:

1、person(实例对象)

person会有一个__proto__指针,指向构造函数的原型对象Person.prototype。Person.prototype会有两个属性constructor和__proto__,constructor指向构造函数Person,__proto__指向原生类型Object的原型对象Object.prototype。Object.prototype有两个属性constructor和__proto__,constructor指向它的构造函数Object,而比较特殊的是__proto__的值为null(原型链的终点),用下列代码可验证。

person.__proto__ === Person.prototype; //true
person.__proto__.__proto__ === Person.prototype.__proto__; //true
person.__proto__.__proto__ === Object.prototype; //true
Person.prototype.__proto__ === Object.prototype; //true
Object.prototype.__proto__ === null; //true复制代码

2、Person(构造函数)

Person函数也会有一个__proto__指针,不同的是多了一个prototype对象属性。prototype的构成上面已经介绍过就不重复了,__proto__指向原生类型Function的原型对象Function.prototype。Function.prototype有两个属性constructor和__proto__,constructor指向构造函数Function,__proto__指向Object的原型对象Object.prototype,介绍同上。

Person.__proto__ === Function.prototype; //true
Function.prototype.__proto__ === Object.prototype; //true
Person.prototype.__proto__ === Object.prototype; //true
Object.prototype.__proto__ === null; //true复制代码

3、原生类型函数

Function函数和Object函数除了prototype之外都存在一个__proto__指针,都是指向Function的原型对象Function.prototype,介绍同上。

Object.__proto__ === Function.prototype; //true
Function.__proto__ === Function.prototype; //true复制代码

4、总结

构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型的内部指针。所有构造函数的__proto__指针指向都是Function.prototype;所有对象的__proto__指针最终指向都是指向Object.prototype,而Object.prototype的__proto__属性值则为null。

七、写在最后

最近在网上看见一个类似关于Function和Object谁是爸爸(由谁而来)的问题,记得最开始学js时就把“js中万物皆对象”这句话给记牢了,想着应该是对象最原始,但是凡事都要有个理(拿出证据来),所以就深究了一下以上函数和对象的一些关系,至于谁是爸爸,明确不出来,但是有一点可以肯定的是,无论是谁,按关系图最后的走向都是null(貌似已经出现结果。。。)。

八、参考文献

最详尽的 JS 原型与原型链终极详解

彻底理解JavaScript原型链(一)—__proto__的默认指向

高能!typeof Function.prototype 引发的先有 Function 还是先有 Object 的探讨

《javascript高级程序设计》

附录一:较为完整的原型链关系图

image.png

附录二:浏览器在初始化JS 环境时都发生了些什么(猜的)

1.用 C/C++ 构造内部数据结构创建一个 OP 即(Object.prototype)以及初始化其内部属性但不包括行为。

2.用 C/C++ 构造内部数据结构创建一个 FP 即(Function.prototype)以及初始化其内部属性但不包括行为。

3.将 FP 的[[Prototype]]指向 OP。

4.用 C/C++ 构造内部数据结构创建各种内置引用类型。

5.将各内置引用类型的[[Prototype]]指向 FP。

6.将 Function 的 prototype 指向 FP。

7.将 Object 的 prototype 指向 OP。

8.用 Function 实例化出 OP,FP,以及 Object 的行为并挂载。

9.用 Object 实例化出除 Object 以及 Function 的其他内置引用类型的 prototype 属性对象。

10.用 Function 实例化出除Object 以及 Function 的其他内置引用类型的 prototype 属性对象的行为并挂载。

11.实例化内置对象 Math 以及 Grobal

至此,所有 内置类型构建完成。

原创不容易,转载请声明出处,谢谢!


文章分类
前端
文章标签