本文深入解析JavaScript中的原型机制,阐明Object与Function的相互继承关系及函数对象与普通对象的区别。通过代码示例详细展示prototype、constructor与__proto__的作用,并完整梳理从实例到null的原型链查找过程,帮助开发者彻底理解JS对象模型。
Object与Function的相互关系
在深入探讨JavaScript的原型机制之前,有必要先厘清Object和Function这两个核心内置对象的关系。
在JavaScript中,Object和Function均为内置构造函数。它们之间存在特殊的继承关系:Object继承自Function,而Function也继承自自身(即Function.prototype指向Function本身)。这意味着,Object和Function既具备函数的特征,也是对象实例,二者互为对方的实例。
console.log(Function instanceof Object); // true
console.log(Object instanceof Function); // true
具体而言,Object是Function的一个实例,而Function则是它自己的实例。
console.log(Function.prototype); // ƒ () { [native code] }
console.log(Object.prototype); // Object
函数对象与普通对象的区别
接下来需要区分普通对象与函数对象的概念。
虽然JavaScript中万物皆对象,但对象内部存在显著差异,主要划分为函数对象和普通对象两类。
函数对象具备创建普通对象的能力,而普通对象无法创建函数对象。在JavaScript的对象体系中,普通对象处于基础层级,不具备特殊权限。
判断一个对象是否为函数对象的标准是:凡是通过new Function构造函数创建的对象均为函数对象,其余通过Object或字面量方式创建的多为普通对象。开发者可以使用typeof运算符来区分二者。
function f1(){};
typeof f1 //"function"
var o1 = new f1();
typeof o1 //"object"
var o2 = {};
typeof o2 //"object"
需要注意的是,以下两种函数定义方式在本质上是等价的。
function f1(){}; == var f1 = new Function();
function f2(a,b){
alert(a+b);
}
等价于
var f2 = new Function(a,b,"alert(a+b)");
prototype、__proto__与constructor的关系
理解原型机制的关键在于掌握prototype、__proto__和constructor(构造函数)三者之间的关系。
以下两条核心规则对于理解原型至关重要。
第一,每个函数对象都拥有prototype属性,而普通对象通常没有该属性。
在函数的prototype属性内部,包含一个constructor属性,该属性指向函数本身。
第二,每个对象内部都隐藏着一个名为__proto__的属性(注:现代标准中建议通过Object.getPrototypeOf访问),它指向该对象所对应的构造函数的prototype。JavaScript的原型链正是基于__proto__属性构建的。
普通对象与函数对象的实例分析
通过以下代码示例,我们可以直观地观察普通对象的属性特征。
首先,普通对象o没有prototype属性。
其次,对象o是Object构造函数的实例。
第三,对象o的__proto__属性指向Object.prototype。
第四,Object.prototype.constructor属性指向Object构造函数本身。
基于上述关系,我们可以进一步延伸理解对象间的层级联系。
var o = {};
console.log(o.prototype); //undefined
console.log(o instanceof Object); //true
console.log(o.__proto__ === Object.prototype) //true
console.log(Object === Object.prototype.constructor) //true
console.log(Object.prototype.constructor) //function Object()
console.log(Object.prototype.__proto__); //null

通过以下函数对象的示例,可以进一步验证原型关系。
从示例可以看出,demo是函数对象,而f1是普通对象。
同时,f1是Demo构造函数的实例。
此外,demo的prototype属性的__proto__指向Object.prototype,而Object.prototype的__proto__指向null,标志着原型链的终点。
function Demo(){};
var f1 = new Demo();
console.log(f1.prototype); //undefined
console.log(f1 instanceof Demo); //true
console.log(f1.__proto__ === Demo.prototype); //true
console.log(Demo === Demo.prototype.constructor) ;//true
console.log(Demo.prototype.__proto__ === Object.prototype) ;//true
console.log(Object.prototype.__proto__); //null

原型链的查找机制与终点
最后,我们来详细解析原型链的工作机制。
在JavaScript中,每个对象内部都包含一个__proto__属性。当访问对象属性时,如果当前对象中不存在该属性,JavaScript引擎会沿着__proto__指向的对象继续查找,层层递进,这一过程即为JavaScript的原型链。
以f1为例,其原型链查找路径为:f1.__proto__ 指向 Demo.prototype,Demo.prototype.__proto__ 指向 Object.prototype,Object.prototype.__proto__ 指向 null。
在JavaScript中,所有事物均为对象,且均由Object衍生而来,因此所有对象的原型链终点最终都指向null。
以上关于JavaScript原型的全面讲解旨在帮助读者建立清晰的对象模型认知,供学习参考。