深入理解JavaScript原型与原型链
本文深入解析JavaScript中的原型概念,澄清prototype属性与内部[[Prototype]]的区别。通过探讨原型链、构造函数行为及instanceof机制,揭示对象继承的本质。文章结合代码示例,说明如何正确利用原型实现属性共享与扩展,帮助开发者掌握JS核心面向对象编程技巧。
JavaScript原型概念解析
JavaScript中的原型机制常令开发者困惑,即便是资深专家甚至语言作者也常给出简略解释。这种困惑往往源于早期对原型、new关键字、constructor以及prototype属性的刻板印象。实际上,原型概念本身相当直观。要真正掌握它,我们需要暂时放下那些容易误导的既有认知,回归概念本源重新审视。
原型的基本定义与对象关系
什么是原型?
原型本质上是一个用于继承属性的对象。
是否任何对象都能充当原型?
是的,任何对象都可以作为原型。
哪些对象拥有原型?
每个对象都默认拥有一个原型。由于原型本身也是对象,因此每个原型自身也存在一个原型(唯一的例外是位于原型链顶端的那个默认对象原型,其他原型则位于链条后续位置)。
退一步讲,究竟什么是对象?
在JavaScript中,对象是存储键值对的任意无序集合。只要不是原始类型(undefined、null、boolean、number或string),其余皆为对象。
你可以认为每个对象都有prototype属性,但当你尝试访问({}).prototype时,结果却是undefined。这确实令人困惑。
访问原型的标准与非标准方法
请忘记你对prototype属性的既有理解,这往往是误解的根源。对象真正的原型存储在其内部[[Prototype]]属性中。ECMA 5标准引入了Object.getPrototypeOf(object)作为标准访问方式,该实现已被Firefox、Safari、Chrome及IE9支持。此外,除IE外,所有浏览器均支持非标准的__proto__访问方式。否则,我们只能将对象的构造方法视为其prototype属性。
var a = {};
//Opera 或 IE<=8下失败
Object.getPrototypeOf(a); //[object Object]
//IE下失败
a.__proto__; //[object Object]
//所有浏览器
//(but only if constructor.prototype has not been replaced and fails with Object.create)
a.constructor.prototype; //[object Object]
很好,false是原始类型,为什么访问false.__proto__会返回一个值呢?
当访问原始类型的原型时,JavaScript会将其强制转换为对应的包装对象。
//(works in IE<=8 too, due to double-negative)
false.__proto__ === Boolean(false).__proto__; //true
利用原型实现继承与构造函数作用
我想使用原型实现继承,具体该怎么做?
为单个实例添加原型属性通常意义不大,除非是为了高效地向实例本身添加属性。假设我们已有一个对象并需共享现有功能(例如Array),我们可以采取如下方式操作。
//fails in IE<=8
var a = {};
a.__proto_ = Array.prototype;
a.length; //0
然而,原型的真正威力体现在多个实例共享同一原型上。原型对象的属性只需定义一次,即可被所有引用该原型的实例继承。这种机制对提升性能和程序可维护性效果显著。那么,构造函数是否因此产生?确实如此,构造函数提供了一种跨浏览器兼容的便捷机制,用于在实例创建时分配公用原型。
prototype属性与构造函数的关系
在给出具体示例前,我需要明确constructor.prototype属性的作用是什么?
首先,JavaScript不区分构造函数与其他方法,因此每个方法都拥有prototype属性。相反,非方法类型的对象则不具备此属性。
//永远不是构造函数的方法,无论如何都是有prototype属性的
Math.max.prototype; //[object Object]
//构造函数也有prototype属性
var A = function(name) {
this.name = name;
}
A.prototype; //[object Object]
//Math不是一个方法,所以没有prototype属性
Math.prototype; //null
现在可以定义:方法的prototype属性,是指当该方法被用作构造函数来创建实例时,赋给该实例的原型对象。
至关重要的一点是,必须理解方法的prototype属性与实际的原型([[Prototype]])之间并无直接关联。
//(在IE中会失败)
var A = function(name) {
this.name = name;
}
A.prototype == A.__proto__; //false
A.__proto__ == Function.prototype; //true - A的prototype是它的构造函数的prototype属性
能否提供一个具体示例?
以下代码你可能已见过或用过无数次,但此处再次展示,或许能带来新的见解。
//构造器. <em>this</em> 作为新对象返回并且它内部的[[prototype]]属性将被设置为构造器默认的prototype属性
var Circle = function(radius) {
this.radius = radius;
//next line is implicit, added for illustration only
//this.__proto__ = Circle.prototype;
}
//扩充 Circle默认的prototype对象的属性因此扩充了每个由它新建实例的prototype对象的属性
Circle.prototype.area = function() {
return Math.PI*this.radius*this.radius;
}
//创建Circle的两个示例,每个都会使用相同的prototype属性
var a = new Circle(3), b = new Circle(4);
a.area().toFixed(2); //28.27
b.area().toFixed(2); //50.27
这很棒。如果我修改了constructor的prototype属性,即使是已存在的实例对象也能立即访问新的原型版本吗?
嗯……并不完全如此。如果修改的是现存原型对象的属性,那么确实如此,因为对象创建时a.__proto__引用了由A.prototype定义的对象。
var A = function(name) {
this.name = name;
}
var a = new A('alpha');
a.name; //'alpha'
A.prototype.x = 23;
a.x; //23
但是,如果我将prototype属性替换为一个新对象,a.__proto__仍然指向原始对象。
var A = function(name) {
this.name = name;
}
var a = new A('alpha');
a.name; //'alpha'
A.prototype = {x:23};
a.x; //null
默认原型与instanceof机制
默认的原型是什么样子的?
它是一个拥有constructor属性的对象。
var A = function() {};
A.prototype.constructor == A; //true
var a = new A();
a.constructor == A; //true (a 的constructor属性继承自它的原型)
instanceof与prototype有何关系? 如果A的prototype属性出现在a的原型链中,则表达式a instanceof A将返回true。这意味着我们可以通过操纵原型链使instanceof失效。
var A = function() {}
var a = new A();
a.__proto__ == A.prototype; //true - so instanceof A will return true
a instanceof A; //true;
//mess around with a's prototype
a.__proto__ = Function.prototype;
//a's prototype no longer in same prototype chain as A's prototype property
a instanceof A; //false
扩展内置构造器的原型
我还能利用原型做些什么?
记得我曾提到每个构造器都拥有prototype属性,利用该属性可将原型赋值给所有由构造器生成的实例?这同样适用于本地构造器,例如Function和String。通过扩展(而非替换)该属性,我们可以更新指定类型所有对象的prototype。
String.prototype.times = function(count) {
return count < 1 ? '' : new Array(count + 1).join(this);
}
"hello!".times(3); //"hello!hello!hello!";
"please...".times(6); //"please...please...please...please...please...please..."
原型链机制与属性查找规则
请多讲讲继承与原型的工作机制,原型链究竟是什么?
由于每个对象和每个原型(本身)都拥有一个原型,我们可以想象对象依次连接形成一条原型链。原型链的终端始终是默认对象(object)的原型。
a.__proto__ = b;
b.__proto__ = c;
c.__proto__ = {}; //默认对象
{}.__proto__.__proto__; //null
原型继承机制是内在且隐式实现的。当对象a试图访问属性foo时,JavaScript会遍历a的原型链(首先从a自身开始),检查原型链每个环节中是否存在foo属性。如果找到foo属性则返回其值,否则返回undefined。
直接赋值会发生什么?
当直接为对象属性赋值时,原型继承机制将不再起作用。执行a.foo='bar'会直接为a的foo属性赋值。若需为原型对象的属性赋值,你必须直接定位并修改原型对象上的该属性。 关于JavaScript原型的讲解至此结束。我认为对原型概念的理解已较为准确,但我的观点并非最终定论。欢迎指出我的错误或提出不同见解。
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
为什么JS中typeof []返回object:数组类型检测误区解析
2026-11-04 19:50:47
