这一章我们将会重点介绍JavaScript中几个重要的属性(this、constructor、prototype), 这些属性对于我们理解如何实现JavaScript中的类和继承起着至关重要的作用。
this
this表示当前对象,如果在全局作用范围内使用this,则指代当前页面对象window; 如果在函数中使用this,则this指代什么是根据运行时此函数在什么对象上被调用。 我们还可以使用apply和call两个全局方法来改变函数中this的具体指向。
先看一个在全局作用范围内使用this的例子:
|
console.log(this===window); // true
console.log(window.alert===this.alert); // true
console.log(this.parseInt("021",10)); // 10
|
函数中的this是在运行时决定的,而不是函数定义时,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 定义一个全局函数
functionfoo(){
console.log(this.fruit);
}
// 定义一个全局变量,等价于window.fruit = "apple";
varfruit="apple";
// 此时函数foo中this指向window对象
// 这种调用方式和window.foo();是完全等价的
foo(); // "apple"
// 自定义一个对象,并将此对象的属性foo指向全局函数foo
varpack={
fruit:"orange",
foo:foo
};
// 此时函数foo中this指向window.pack对象
pack.foo();// "orange"
|
全局函数apply和call可以用来改变函数中this的指向,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 定义一个全局函数
functionfoo(){
console.log(this.fruit);
}
// 定义一个全局变量
varfruit="apple";
// 自定义一个对象
varpack={
fruit:"orange"
};
// 等价于window.foo();
foo.apply(window); // "apple"
// 此时foo中的this === pack
foo.apply(pack); // "orange"
|
注:apply和call两个函数的作用相同,唯一的区别是两个函数的参数定义不同。
因为在JavaScript中函数也是对象,所以我们可以看到如下有趣的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 定义一个全局函数
functionfoo(){
if(this===window){
console.log("this is window.");
}
}
// 函数foo也是对象,所以可以定义foo的属性boo为一个函数
foo.boo=function(){
if(this===foo){
console.log("this is foo.");
}elseif(this===window){
console.log("this is window.");
}
};
// 等价于window.foo();
foo(); // this is window.
// 可以看到函数中this的指向调用函数的对象
foo.boo(); // this is foo.
// 使用apply改变函数中this的指向
foo.boo.apply(window); // this is window.
|
prototype
我们已经在第一章中使用prototype模拟类和继承的实现。 prototype本质上还是一个JavaScript对象。
并且每个函数都有一个默认的prototype属性。
如果这个函数被用在创建自定义对象的场景中,我们称这个函数为构造函数。 比如下面一个简单的场景:
1
2
3
4
5
6
7
8
9
10
11
12
|
// 构造函数
functionPerson(name){
this.name=name;
}
// 定义Person的原型,原型中的属性可以被自定义对象引用
Person.prototype={
getName:function(){
returnthis.name;
}
}
varzhang=newPerson("ZhangSan");
console.log(zhang.getName()); // "ZhangSan"
|
作为类比,我们考虑下JavaScript中的数据类型 - 字符串(String)、数字(Number)、数组(Array)、对象(Object)、日期(Date)等。
我们有理由相信,在JavaScript内部这些类型都是作为构造函数来实现的,比如:
|
// 定义数组的构造函数,作为JavaScript的一种预定义类型
functionArray(){
// ...
}
// 初始化数组的实例
vararr1=newArray(1,56,34,12);
// 但是,我们更倾向于如下的语法定义:
vararr2=[1,56,34,12];
|
同时对数组操作的很多方法(比如concat、join、push)应该也是在prototype属性中定义的。
实际上,JavaScript所有的固有数据类型都具有只读的prototype属性
(这是可以理解的:因为如果修改了这些类型的prototype属性,则哪些预定义的方法就消失了),
但是我们可以向其中添加自己的扩展方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 向JavaScript固有类型Array扩展一个获取最小值的方法
Array.prototype.min=function(){
varmin=this[0];
for(vari=1;i<this.length;i++){
if(this[i]<min){
min=this[i];
}
}
returnmin;
};
// 在任意Array的实例上调用min方法
console.log([1,56,34,12].min()); // 1
|
注意:这里有一个陷阱,向Array的原型中添加扩展方法后,当使用for-in循环数组时,这个扩展方法也会被循环出来。
下面的代码说明这一点(假设已经向Array的原型中扩展了min方法):
|
vararr=[1,56,34,12];
vartotal=0;
for(variinarr){
total+=parseInt(arr[i],10);
}
console.log(total); // NaN
|
解决方法也很简单:
|
vararr=[1,56,34,12];
vartotal=0;
for(variinarr){
if(arr.hasOwnProperty(i)){
total+=parseInt(arr[i],10);
}
}
console.log(total); // 103
|
constructor
constructor始终指向创建当前对象的构造函数。比如下面例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
// 等价于 var foo = new Array(1, 56, 34, 12);
vararr=[1,56,34,12];
console.log(arr.constructor===Array);// true
// 等价于 var foo = new Function();
varFoo=function(){};
console.log(Foo.constructor===Function);// true
// 由构造函数实例化一个obj对象
varobj=newFoo();
console.log(obj.constructor===Foo);// true
// 将上面两段代码合起来,就得到下面的结论
console.log(obj.constructor.constructor===Function);// true
|
但是当constructor遇到prototype时,有趣的事情就发生了。
我们知道每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。如下例所示:
1
2
3
4
5
6
7
8
9
10
11
12
|
functionPerson(name){
this.name=name;
};
Person.prototype.getName=function(){
returnthis.name;
};
varp=newPerson("ZhangSan");
console.log(p.constructor===Person); // true
console.log(Person.prototype.constructor===Person);// true
// 将上两行代码合并就得到如下结果
console.log(p.constructor.prototype.constructor===Person);// true
|
当时当我们重新定义函数的prototype时(注意:和上例的区别,这里不是修改而是覆盖),
constructor的行为就有点奇怪了,如下示例:
1
2
3
4
5
6
7
8
9
10
11
12
|
functionPerson(name){
this.name=name;
};
Person.prototype={
getName:function(){
returnthis.name;
}
};
varp=newPerson("ZhangSan");
console.log(p.constructor===Person); // false
console.log(Person.prototype.constructor===Person);// false
console.log(p.constructor.prototype.constructor===Person);// false
|
为什么呢?
原来是因为覆盖Person.prototype时,等价于进行如下代码操作:
|
Person.prototype=newObject({
getName:function(){
returnthis.name;
}
});
|
而constructor始终指向创建自身的构造函数,所以此时Person.prototype.constructor === Object,即是:
1
2
3
4
5
6
7
8
9
10
11
12
|
functionPerson(name){
this.name=name;
};
Person.prototype={
getName:function(){
returnthis.name;
}
};
varp=newPerson("ZhangSan");
console.log(p.constructor===Object); // true
console.log(Person.prototype.constructor===Object);// true
console.log(p.constructor.prototype.constructor===Object);// true
|
怎么修正这种问题呢?方法也很简单,重新覆盖Person.prototype.constructor即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
functionPerson(name){
this.name=name;
};
Person.prototype=newObject({
getName:function(){
returnthis.name;
}
});
Person.prototype.constructor=Person;
varp=newPerson("ZhangSan");
console.log(p.constructor===Person); // true
console.log(Person.prototype.constructor===Person);// true
console.log(p.constructor.prototype.constructor===Person);// true
|
via yxpjx的专栏
相关推荐
### JavaScript中的`new`操作与`constructor`属性详解 #### 一、`new`操作符的理解 在JavaScript中,`new`操作符被用于创建一个由构造函数定义的新实例对象。当使用`new`关键字调用一个构造函数时,会执行以下步骤...
原型对象存放于 ConstructorFunction.prototype 属性中。 例如,我们重写之前例子,使用构造函数创建对象“b”和“c”,那么对象”a”则扮演了“Foo.prototype”这个角色: // 构造函数 function Foo(y) { // 构造...
在JavaScript中,`prototype`是实现面向对象编程的关键特性,它允许我们为类(或称为构造函数)添加方法。在本文中,我们将深入探讨如何利用`prototype`为类添加方法,以及这种方法背后的原理。 首先,我们需要理解...
### JavaScript原型与原型链详解 JavaScript作为一门基于原型的语言,其原型和原型链的概念是理解和掌握JS继承机制的关键。接下来将详细阐述这些概念。 #### 普通对象与函数对象 在JavaScript中,一切皆为对象,...
在JavaScript这种基于原型的面向对象语言中,类(Class)的概念并非原始特性,而是通过构造函数(Constructor)和原型链(Prototype Chain)来模拟。虽然ES6引入了Class语法,但它本质上仍然是对ES5中基于函数的构造...
在JavaScript中,原型(Prototype)是实现继承的核心机制。理解__proto__和prototype这两个概念对于深入掌握JavaScript面向对象编程至关重要。 首先我们来看__proto__属性。每个JavaScript对象都拥有一个__proto__...
JavaScript中的构造器(Constructor)模式是一种创建对象的模式,它在JavaScript这种不支持传统类机制的语言中发挥着关键作用。构造器模式的核心在于通过`new`关键字实例化一个对象,通常用于初始化对象的属性和方法...
**JavaScript中的Prototype详解** 在JavaScript中,Prototype是一个非常重要的概念,它是面向对象编程的基础。本文将深入探讨Prototype的原理、用途及其在JavaScript中的实现方式,同时结合提供的`prototype.js`...
总结一下,JavaScript中使用构造函数和原型链实现继承的过程中,需要特别注意constructor属性的指向问题、避免在构造函数中初始化数据以及如何在实例化对象时自动调用初始化方法。通过上述的解决方案,我们可以在...
JavaScript中的继承是面向对象编程的重要概念,允许子类继承父类的属性和方法。本文将深入探讨JavaScript继承的实现方式,以及其中的问题和解决方案。 首先,我们来看混合方式的实现,这种方式结合了原型链和对象...
总结一下,JavaScript的继承主要依赖于`prototype`、`constructor`和`this`这三个关键概念。`prototype`允许对象之间共享属性和方法,`constructor`标识对象的构造函数,而`this`则是动态绑定的上下文,用于确定当前...
这个过程在JavaScript中通常通过修改子类的prototype属性实现。 以下是一个简单的示例,展示了如何使用原型链来实现继承: ```javascript function SuperType() { this.property = true; } SuperType.prototype....
### 详解JS中的原型、原型对象与原型链 #### 前言 JavaScript是一种基于原型的语言,这使得它与其他面向对象编程语言有所不同。在JavaScript中,每个对象都有一个原型对象,而原型对象又可能有自己的原型对象,从而...
JavaScript中的继承是面向对象编程的重要概念,允许子类继承父类的属性和方法。本文将深入探讨JavaScript继承的实现,并分析其潜在的问题和解决方案。 首先,我们来看混合方式的实现,这是一种常见的继承策略,结合...
JavaScript中的`prototype`对象是每个函数(包括构造函数)的一个属性,它包含可以被实例共享的方法。可以通过`__proto__`或`Object.getPrototypeOf()`访问对象的原型,`__proto__.constructor`返回的是创建该对象的...
本文将深入探讨 JavaScript 中创建对象及对象继承的各种有效策略,特别是如何利用构造函数模式、原型模式以及寄生组合式继承等技术来构建灵活、高效的应用程序。 #### 二、JavaScript 对象与原型 在 JavaScript 中...
在JavaScript的ES6标准中,引入了`Class`语法,这是一种更加面向对象的代码书写方式。尽管`Class`看似一种全新的结构,但它本质上仍然是基于原型(prototype)的,可以说是一种语法糖,使得对象的创建和操作更加直观...
JavaScript中的继承是面向对象编程的重要概念,它允许一个对象(子对象)获取另一个对象(父对象)的属性和方法,从而实现代码复用和多态性。JavaScript支持多种继承实现方式,包括以下四种: 1. **构造函数继承**...