简述prototype

在js中,每个构造函数都有一个原型属性 prototype,因为这个属性的值通常是一个对象,又叫原型对象!你不需要显式的去定义原型对象,因为每个构造函数都会一个原型属性,通常在这个原型对象中,会包含一个 constructor 属性指向该原型对象的构造函数:

1
2
3
4
5
6
function a() {
alert('hello');
}

var b = new a();
console.log(b.constructor);//返回函数a的字面量function a() {alert('hello')} ;

以上代码中你会发现,首先你并没有定义函数a的 prototype,以及 constructor ;但是在使用的时候构造函数a的实例对象b的 constructor 属性却正确的指向构造函数a;

我们都知道js是一种基于原型继承的语言,这里说的原型指的就是对象的原型属性!从这就能看出来,在js中,原型是用来实现继承的!那么为什么可以通过对象的原型就能实现继承了?这是因为在js中,所有使用同一个构造函数生成的实例对象,都共享该构造函数的原型对象,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//定义构造函数
function user(name, age) {
this.name = name;
this.age = age;
}

//定义构造函数user的原型方法
user.prototype.getName = function() {
alert(this.name);
}
user.prototype.getAge = function() {
alert(this.age);
}

//生成实例
var userA = new user('a', '22');
var userB = new user('b', '30');
userA.getName();//a
userA.getAge();//22
userB.getName();//b
userB.getAge();//30

由以上代码可以看出,在构造函数中我们并没有定义获取名称和获取年龄的方法getName以及getAge;它们是在原型对象中定义的;但是我们在底下代码中的user构造函数的两个实例对象userA和userB却都可以使用构造函数的原型对象中的方法!这就是上面说的:所有使用同一个构造函数生成的实例对象,都共享该构造函数的原型对象!

prototype中的constructor属性

上面简单的介绍了一下 prototype 中的 constructor 属性,该属性是原型对象中自带的属性,并且它始终是指向该原型的构造函数的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//定义构造函数
function user(name, age) {
this.name = name;
this.age = age;
}

//定义构造函数user的原型方法
user.prototype.getName = function() {
alert(this.name);
}
user.prototype.getAge = function() {
alert(this.age);
}

//生成实例
var userA = new user('a', '22');
var userB = new user('b', '30');
console.log(userA.constructor === userB.constructor)//true;

当然,我们也可以动态的设置constructor属性的值:

1
user.prototype.constructor = user;

只是在我们没有重新定义覆盖 prototype 对象的时候,并不需要这样显示的去让 constructor 属性指向构造函数!

覆盖prototype对象:

上面我们简单介绍了 prototype 对象,并且也给出来一些简单使用的例子,在上面的例子中,我们都是往构造函数的 prototype 对象上添加方法或属性的,有一个方法我们就要添加一次,这样显的不够优雅,那么有没有一种写法是可以一次定义这些方法的了?答案当然是肯定的,这就是我们下面要讲的覆盖 prototype 对象,我们先来看看怎么做,下面我们重写上面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//定义构造函数
function user(name, age) {
this.name = name;
this.age = age;
}
//定义构造函数user的原型方法
user.prototype = {
constructor : user,
getName : function() {
alert(this.name);
},
getAge : function() {
alert(this.age);
}

大家可以复制上面的代码,看看效果是不是跟之前的效果一样!相信大家看了代码之后应该也都明白了,既然 prototype 是一个对象,那么我们当然能重新定义一个对象去覆盖它了,但是这里值得注意的是,在原型对象中,默认是包含 constructor 属性指向构造函数的,在我们自己定义对象覆盖 prototype 对象时,要显式的定义 constructor 属性指向构造函数,否则,这个 prototype 对象的 constructor 属性就会是 undefined

常用用法

原型对象 prototype 在日常js开发中主要是用来实现继承的,也就是说我们可以将A对象作为B对象的原型,来实现对于A对象方法的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 构造函数A
function A() {
this.name = '';

this.getName = function(name) {
return this.name;
}
}

// 构造函数B
function B(name) {
this.name = name;

this.setName = function(name) {
this.name = name;
}
}

B.prototype = new A;
B.prototype.constructor = B;

var b = new B('xiaoming');
b.getName(); // => "xiaoming"

看上面代码,我们很容易就发现,对于构造函数B,本身是没有 getName 方法的,但是构造函数A是有 getName 方法的,在这里,我们让构造函数A的实例,成为构造函数B的 prototype 对象,这样我们就实现了对于构造函数A的继承!

原型对象弊端

原型对象虽然可以让我们实现继承,但是原型模式省略了初始化时传递参数的能力,并且由于原型对象是多个实例共享的,在其属性为非引用类型时,修改属性值可以通过在实例上添加一个同名属性进行覆盖。但是在属性值为引用类型时,只要有一个实例对该属性进行了修改,那么所有实例的这个属性都会改变!这会导致在多层继承中,出现意想不到的问题!所以在原型对象中,慎用引用类型数据

总结

说到这里,关于 prototype 对象的一些知识以及基本用法,已经跟大家交代清楚了,因为js是基于原型继承的,所以了解 prototype 对象的相关对象,对于js面向对象编程是至关重要的!关于 prototype 的部分我们就先讲到这里,等以后有时间,我们在接着讲原型链,面向对象以及js中的继承等知识!

本文允许全文转载,转载请注明来源:
平凡公子 - JavaScript之prototype对象