JavaScript原型链详解
JavaScript原型链核心是构造函数、实例与原型对象的关系。实例的`__proto__`指向其构造函数的`prototype`,构造函数的`__proto__`指向`Function.prototype`,最终`Object.prototype.__proto__`为`null`,形成完整链条。这一链条使得对象能够继承原型上的属性和方法,是JavaScr
要理解Ja vaScript的原型链,其实核心就在于理清几个关键概念之间的关系。我们先从一段常见的构造函数代码说起,一步步拆解。
function Person(name,walk){
this.name = name;
this.walk = walk;
this.getName = function(){
console.log(this.name);
}
}
Person.eat = function(){ console.log('eat'); }
Person.prototype.getWalk = function(){
console.log(this.walk);
}
const person = new Person('张三', '顺拐');
constructor
首先来看constructor。当你访问一个实例的constructor属性时,你会发现实例本身并没有这个属性,所以它会沿着原型链往上找。顺着链条往上一级,就是它构造函数的prototype对象,而这个对象身上恰好有constructor。关键来了:这个constructor指向的是构造函数本身。
换句话说,person自己没有constructor,它继承的是Person.prototype上的,而Person.prototype.constructor === Person,所以person.constructor === Person。
但别忘了,构造函数本身也是一个对象。作为对象,它的行为和普通实例是一样的——它也会继承它自己构造函数的prototype上的constructor。所有函数(作为对象时)的构造函数都是Function。所以,Person.constructor === Function.prototype.constructor === Function。
prototype 与 __proto__
函数身上有一个特殊的属性叫prototype(注意:只有函数才有,普通对象没有,箭头函数也没有)。这个prototype定义了由该构造函数创建出来的对象的公共祖先。换句话说,通过这个构造函数new出来的所有实例,都能继承这个prototype上的属性和方法。在上面代码里,person就能继承Person.prototype上的getWalk。
实例的原型(也就是__proto__)指向它构造函数的原型对象(prototype)。所以,person.__proto__ === Person.prototype。
接下来是一个有趣的推论:所有的函数(包括箭头函数,它只是没有prototype,无法用来构造实例)的构造函数都是Function。也就是说,所有函数都是Function的实例。这一点连Object也不例外——Object本身也是一个构造函数。所以,任何函数的__proto__都指向Function.prototype。包括Object和Function自己:Object.__proto__ === Function.prototype,Function.__proto__ === Function.prototype(因为Function是由Function.prototype构造的)。
那么普通对象呢?普通对象是Object的实例,它们的构造函数是Object,所以普通对象的__proto__指向Object.prototype。这里需要注意:构造函数是函数,但构造函数的prototype本身是一个非函数的普通对象。因此,Person.prototype和Function.prototype的__proto__都指向Object.prototype。
理论上Object.prototype也是一个对象,它的__proto__似乎应该指向Object.prototype自己,但为了避免循环引用,Ja vaScript规定Object.prototype.__proto__ === null。
把这些链条连起来,你就得到了完整的原型链。举个例子:
person的原型链:person → Person.prototype → Object.prototype → nullPerson的原型链:Person → Function.prototype → Object.prototype → nullFunction的原型链:Function → Function.prototype → Object.prototype → null
这里还有个小细节。如果构造函数里通过this.属性定义了属性,那这个属性并不属于构造函数本身——它只是一条赋值语句,this指向调用方。所以new出来的实例会有这个属性,但构造函数没有。比如:
function Person(){
this.name = 15;
}
const p = new Person();
console.log(p.name); // 15
console.log(Person.name); // undefined
Person.prototype.name = 20;
console.log(p.name); // 15,因为原型链从下往上找,实例自己的name优先
对于固定值的属性,其实没必要在构造函数里用this.xxx定义,因为每个实例都会单独创建一份,浪费内存。更好的做法是直接挂到prototype上:Person.prototype.name = 15,然后构造函数里就别再定义this.name了。
如果直接修改Person.prototype的引用,比如写成Person.prototype = {},有两种情况:如果实例是在修改之前创建的,那实例的__proto__仍然指向旧的prototype对象,不会自动连接到新的上;如果实例是在修改之后创建的,则会连接到新的prototype。另外,Person能否连接到新的prototype,取决于新的引用是否把constructor指回Person。记住,Person.prototype只是一个普通对象,它和Person的关联是通过它上面的constructor属性完成的。如果新对象没有定义constructor,或者定义成了别的东西,那连接就断了。
最后,通过实例是无法修改构造函数原型上的属性的。比如你写person.name = '李四',这只是在person实例上添加了一个name属性,并不会改变Person.prototype.name。你写delete person.name,也只能删除实例自己的name属性,哪怕实例上本来就没有这个属性,delete也会返回true——但这只是表示删除操作“成功”了,并不代表原型上的属性被删除了。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















