JavaScript核心属性详解:this、prototype与constructor的机制与陷阱

作者:小括号时间:2026-10-27 10:39:08浏览:636

本文深入解析JavaScript中this、prototype和constructor三大核心属性的工作机制。详细阐述this在不同作用域下的动态绑定规则,以及利用apply和call改变上下文的方法。重点剖析prototype作为对象在构造函数中的角色,揭示直接覆盖prototype导致constructor指向Object的常见陷阱,并提供通过Object.assign或手动赋值修复constructor的标准方案,帮助开发者规避原型链扩展时的潜在错误。

this

this关键字的动态绑定机制

this关键字用于引用当前执行上下文中的对象。当在全局作用域中使用时,this指向window对象(在浏览器环境中);而在函数内部,this的具体指向取决于函数被调用时的上下文环境。开发者可以通过全局方法apply和call来显式指定函数执行时this的绑定对象。


以下示例展示了在全局作用域中使用this的情况:


代码如下:

 
<script type=>
console.log( === window);
console.log(window.alert === .alert);
console.log(.parseInt(, 10));
</script>

函数内部的this是在运行时动态确定的,而非定义时确定。具体指向取决于调用方式,例如:


代码如下:

 
foo() {
console.log(.fruit);
} 
fruit = ;
foo();
pack = {
fruit: ,
foo: foo
}; 
pack.foo();

全局函数apply和call可用于改变函数内部的this指向,示例如下:


代码如下:

 
foo() {
console.log.fruit);
} 
fruit = ;
pack = {
fruit:
}; 
foo.apply(window);
foo.apply(pack);

注:apply和call两个函数的作用相同,唯一的区别是两个函数的参数定义不同。

由于JavaScript中函数本身也是对象,因此可以观察到以下有趣的行为:


代码如下:

 
foo() {
( === window) {
console.log();
} 
} 
foo.boo = () {
( === foo) {
console.log();
} ( === window) {
console.log();
} 
}; 
foo();
foo.boo();
foo.boo.apply(window);

prototype

prototype原型对象与构造函数

prototype本质上是一个JavaScript对象。每个函数都拥有一个默认的prototype属性。


当函数用于创建自定义对象时,它被称为构造函数。例如:


代码如下:

 
Person(name) {
.name = name;
} 
Person.prototype = {
getName: () {
.name;
} 
} 
zhang = Person();
console.log(zhang.getName());

类比JavaScript内置数据类型(如String、Number、Array、Object、Date等),可以推断这些类型在内部也是通过构造函数实现的,例如:


Array() {


}

arr1 = Array(1, 56, 34, 12);


arr2 = [1, 56, 34, 12];


数组操作的许多内置方法(如concat、join、push)均定义在prototype属性中。


实际上,JavaScript所有内置数据类型都具有只读的prototype属性(这是合理的,因为修改这些类型的prototype会导致预定义方法丢失)。不过,我们可以向其中添加自定义扩展方法。


Array.prototype.min = () {


min = [0];


( i = 1; i < .length; i++) {


([i] < min) {


min = [i];


}
}

min;


};

console.log([1, 56, 34, 12].min());


注意:这里存在一个陷阱。向Array原型添加扩展方法后,在使用for-in循环遍历数组时,该扩展方法也会被枚举出来。


以下代码说明了这一现象(假设已向Array原型扩展min方法):


arr = [1, 56, 34, 12];


total = 0;


( i arr) {


total += parseInt(arr[i], 10);


}

console.log(total);


解决方法很简单:


arr = [1, 56, 34, 12];


total = 0;


( i arr) {


(arr.hasOwnProperty(i)) {


total += parseInt(arr[i], 10);


}
}

console.log(total);


constructor

constructor构造函数属性的陷阱与修复

constructor始终指向创建当前对象的构造函数。例如:


代码如下:

 
arr = [1, 56, 34, 12];
console.log(arr.constructor === Array);
Foo = () { };
console.log(Foo.constructor === Function);
obj = Foo();
console.log(obj.constructor === Foo);
console.log(obj.constructor.constructor === Function);

然而,当constructor遇到prototype时,会出现一些有趣的行为。


每个函数都有一个默认的prototype属性,该属性的constructor默认指向该函数本身。如下例所示:


代码如下:

 
Person(name) {
.name = name;
}; 
Person.prototype.getName = () {
.name;
}; 
p = Person();
console.log(p.constructor === Person);
console.log(Person.prototype.constructor === Person);
console.log(p.constructor.prototype.constructor === Person);

但是,当我们重新定义函数的prototype时(注意:这里是覆盖而非修改),constructor的行为会变得异常,如下示例:


代码如下:

 
Person(name) {
.name = name;
}; 
Person.prototype = {
getName: () {
.name;
} 
}; 
p = Person();
console.log(p.constructor === Person);
console.log(Person.prototype.constructor === Person);
console.log(p.constructor.prototype.constructor === Person);

这是为什么呢?


原因在于,覆盖Person.prototype等价于执行以下代码:


代码如下:

 
Person.prototype = Object({
getName: () {
.name;
} 
}); 

由于constructor始终指向创建自身的构造函数,此时Person.prototype.constructor === Object,即:


代码如下:

 
Person(name) {
.name = name;
}; 
Person.prototype = {
getName: () {
.name;
} 
}; 
p = Person();
console.log(p.constructor === Object);
console.log(Person.prototype.constructor === Object);
console.log(p.constructor.prototype.constructor === Object);

如何修正这个问题?方法很简单,重新设置Person.prototype.constructor即可:


代码如下:

 
Person(name) {
.name = name;
}; 
Person.prototype = Object({
getName: () {
.name;
} 
}); 
Person.prototype.constructor = Person;
p = Person();
console.log(p.constructor === Person);
console.log(Person.prototype.constructor === Person);
console.log(p.constructor.prototype.constructor === Person);
作者最新文章