原创作者: sdcyst   阅读:17028次   评论:38条   更新时间:2011-06-01    

类、构造函数、原型

先来说明一点:在上面的内容中提到,每一个函数都包含了一个prototype属性,这个属性指向了一个prototype对象(Every
function has a prototype property that refers to a predefined prototype object  --section8.6.2).注意不要
搞混了.

构造函数:
new操作符用来生成一个新的对象.new后面必须要跟上一个函数,也就是我们常说的构造函数.构造函数的工作原理又是怎样的呢?
先看一个例子:

function Person(name,sex) {
    this.name = name;
    this.sex = sex;
}
var per = new Person("sdcyst","male");
alert("name:"+per.name+"_sex:"+per.sex); //name:sdcyst_sex:male

 

下面说明一下这个工作的步骤:
开始创建了一个函数(不是方法,只是一个普通的函数),注意用到了this关键字.以前我们提到过this关键字表示调用该方法的对象,也就
是说通过对象调用"方法"的时候,this关键字会指向该对象(不使用对象直接调用该函数则this指向整个的script域,或者函数所在的域,在此
我们不做详细的讨论).当我们使用new操作符时,javascript会先创建一个空的对象,然后这个对象被new后面的方法(函数)的this关键字引用!然后在方法中
通过操作this,就给这个新创建的对象相应的赋予了属性.最后返回这个经过处理的对象.这样上面的例子就很清楚:先创建一个空对象,然后
调用Person方法对其进行赋值,最后返回该对象,我们就得到了一个per对象.

prototype(原型)--在这里会反复提到"原型对象"和"原型属性",注意区分这两个概念.
在javascript中,每个对象都有一个prototype属性,这个属性指向了一个prototype对象.
上面我们提到了用new来创建一个对象的过程,事实上在这个过程中,当创建了空对象后,new会接着操作刚生成的这个对象的prototype属性.
每个方法都有一个prototype属性(因为方法本身也是对象),new操作符生成的新对象的prototype属性值和构造方法的prototype属性值是一致的.构造方
法的prototype属性指向了一个prototype对象,这个prototype对象初始只有一个属性constructor,而这个constructor属性又指向了prototype属性所在的方
法(In the previous section, I showed that the new operator creates a new, empty object and then invokes a constructor
function as a method of that object. This is not the complete story, however. After creating the empty object,
new sets the prototype of that object. The prototype of an object is the value of the prototype property of its
constructor function. All functions have a prototype property that is automatically created and initialized when
the function is defined. The initial value of the prototype property is an object with a single property. This property
is named constructor and refers back to the constructor function with which the prototype is associated. this is why every
object has a constructor property. Any properties you add to this prototype object will appear to be properties of
objects initialized by the constructor. -----section9.2)
有点晕,看下面的图:

 

这样,当用构造函数创建一个新的对象时,它会获取构造函数的prototype属性所指向的prototype对象的所有属性.对构造函数对应的prototype对象
所做的任何操作都会反应到它所生成的对象身上,所有的这些对象共享构造函数对应的prototype对象的属性(包括方法).
看个具体的例子吧:

function Person(name,sex) {  //构造函数
    this.name = name;
    this.sex = sex;
}
Person.prototype.age = 12;   //为prototype属性对应的prototype对象的属性赋值
Person.prototype.print = function() { //添加方法
    alert(this.name+"_"+this.sex+"_"+this.age);
};

var p1 = new Person("name1","male");
var p2 = new Person("name2","male");
p1.print();  //name1_male_12
p2.print();  //name2_male_12

Person.prototype.age = 18;  //改变prototype对象的属性值,注意是操作构造函数的prototype属性
p1.print();  //name1_male_18
p2.print();  //name2_male_18

 

到目前为止,我们已经模拟出了简单的类的实现,我们有了构造函数,有了类属性,有了类方法,可以创建"实例".
在下面的文章中,我们就用"类"这个名字来代替构造方法,但是,这仅仅是模拟,并不是真正的面向对象的"类".
在下一步的介绍之前,我们先来看看改变对象的prototype属性和设置prototype属性的注意事项:
给出一种不是很恰当的解释,或许有助于我们理解:当我们new了一个对象之后,这个对象就会获得构造函数的prototype属
性(包括函数和变量),可以认为是构造函数(类)继承了它的prototype属性对应的prototype对象的函数和变量,也就是说,
prototype对象模拟了一个超类的效果.听着比较拗口,我们直接看个实例吧:

function Person(name,sex) {  //Person类的构造函数
    this.name = name;
    this.sex = sex;
}
Person.prototype.age = 12;   //为Person类的prototype属性对应的prototype对象的属性赋值,
                             //相当于为Person类的父类添加属性
Person.prototype.print = function() { //为Person类的父类添加方法
    alert(this.name+"_"+this.sex+"_"+this.age);
};

var p1 = new Person("name1","male"); //p1的age属性继承子Person类的父类(即prototype对象)
var p2 = new Person("name2","male");

p1.print();  //name1_male_12
p2.print();  //name2_male_12

p1.age = 34; //改变p1实例的age属性
p1.print();  //name1_male_34
p2.print();  //name2_male_12

Person.prototype.age = 22;  //改变Person类的超类的age属性
p1.print();  //name1_male_34(p1的age属性并没有随着prototype属性的改变而改变)
p2.print();  //name2_male_22(p2的age属性发生了改变)

p1.print = function() {  //改变p1对象的print方法
    alert("i am p1");
}

p1.print();  //i am p1(p1的方法发生了改变)
p2.print();  //name2_male_22(p2的方法并没有改变)

Person.prototype.print = function() { //改变Person超类的print方法
    alert("new print method!");
}

p1.print();  //i am p1(p1的print方法仍旧是自己的方法)
p2.print();  //new print method!(p2的print方法随着超类方法的改变而改变)

 

看过一篇文章介绍说javascript中对象的prototype属性相当于java中的static变量,可以被这个类下的所有对象
共用.而上面的例子似乎表明实际情况并不是这样:
在JS中,当我们用new操作符创建了一个类的实例对象后,它的方法和属性确实继承了类的prototype属性,类的prototype属性
中定义的方法和属性,确实可以被这些实例对象直接引用.但是,当我们对这些实例对象的属性和方法重新赋值或定义后,那么
实例对象的属性或方法就不再指向类的prototype属性中定义的属性和方法,此时,即使再对类的prototype属性中相应的方法或
属性做修改,也不会反应在实例对象身上.这就解释了上面的例子:
一开始,用new操作符生成了两个对象p1,p2,他们的age属性和print方法都来自(继承于)Person类的prototype属性.然后,我们
修改了p1的age属性,后面对Person类的prototype属性中的age重新赋值(Person.prototype.age = 22),p1的age属性并不会
随之改变,但是p2的age属性却随之发生了变化,因为p2的age属性还是引自Person类的prototype属性.同样的情况在后面的
print方法中也体现了出来.

通过上面的介绍,我们知道prototype属性在javascript中模拟了父类(超类)的角色,在js中体现面向对象的思想,prototype属性
是非常关键的.

评论 共 38 条 请登录后发表评论
38 楼 zhangjq5 2011-10-27 14:39
9楼说得对,由构造函数生成的对象中没有prototype属性,而是走了构造函数的prototype属性……换个角度,由构造函数生成的对象属个例,个例在继承上来说是没什么意义的,所以关键点还是在于构造函数,构造函数在此充当了模板的作用,而构造函数的prototype属性则充当了扩充模板的角色……
37 楼 lishl 2011-10-27 11:36
呕心之作,非常感谢。
36 楼 tan4836128 2011-08-25 17:24
写得好,好文章,看这回复数量可谓大面积扫盲了,顶楼主,期待更精彩的文章!
35 楼 dir_murong 2011-02-22 17:17
原来后续的都在这。。 
34 楼 liangxianfu5811 2011-01-06 16:10
winter8 写道
moliu 写道
constructor
myali88 写道
我是这样理解的,JS中所谓实例继承了类的属性,实际上是对属性的调用是沿着prototype的链来查找的,先找实例本身,再找其构造函数的prototype,。。。直到Object.prototype 上。所以,p1,p2对age的访问,在p1没有重新对age赋值时,都是从其prototype上获得的,当执行了p1.age = 3333;则再调用p1.age 时其实就是从p1实例本身上找到了相应的age而不是到其prototype链上,因此,其对p2也不会有影响。

----原型链,值的读取顺序,应当是这样的,此为 正解,我以为。

某也是这么认为!
正解!
ayong2010 写道
winter8 写道
moliu 写道
constructor
myali88 写道
我是这样理解的,JS中所谓实例继承了类的属性,实际上是对属性的调用是沿着prototype的链来查找的,先找实例本身,再找其构造函数的prototype,。。。直到Object.prototype 上。所以,p1,p2对age的访问,在p1没有重新对age赋值时,都是从其prototype上获得的,当执行了p1.age = 3333;则再调用p1.age 时其实就是从p1实例本身上找到了相应的age而不是到其prototype链上,因此,其对p2也不会有影响。

----原型链,值的读取顺序,应当是这样的,此为 正解,我以为。

某也是这么认为!

正解!


你这里的原型链和作用域链是否同一个意思,请作者和读者们回答,谢谢
33 楼 liangxianfu5811 2011-01-06 16:07
31楼,这句话我也很不理解,按照我的理解new操作符生成的新对象不会拥有prototype属性,很赞同30楼的说法,在32楼中例子中,类test2给构造出的对象赋了age属性,但test2的prototype属性所指的prototype对象中又有age属性,这是t应该优先使用其自身的age,而不会使用test2.prototype.age属性,望读者们给与指正和回复
32 楼 liangxianfu5811 2011-01-06 15:54
"在javascript中,每个对象都有一个prototype属性"有点不太恰当,我觉得对象和函数还是有本质的区别的:对象没有prototype属性,而函数拥有prototype属性;以下是验证代码:
var test2 = function(name, age){
this.name = name || "aa";
this.age = age || 21;
}
test2.prototype = {
age: 2,
show: function(){
alert(this.name + "\t" + this.age);
}
}
var t = new test2();
t.prototype值为undefined;
test2.prototype是一个Object;
typeof(test2.prototype.constructor)是一个function
另外:函数、类、构造函数、方法是否是同一个意思,请读者们给与回复,我觉得是一个意思,没什么区别;
在以上代码中,给test2的prototype属性增加了一个属性和一个方法, 按照作者所画的示意图,test2.prototype.constructor.prototype应该是一个非空对象,这里是一个空对象,不知道为什么,请读者们给与回答,谢谢!
31 楼 rambolxd 2010-12-10 19:18
new操作符生成的新对象的prototype属性值和构造方法的prototype属性值是一致的.这句话是不是有问题?是不是应该把第一个prototype去掉?
30 楼 ayong2010 2010-12-02 17:30
winter8 写道
moliu 写道
constructor
myali88 写道
我是这样理解的,JS中所谓实例继承了类的属性,实际上是对属性的调用是沿着prototype的链来查找的,先找实例本身,再找其构造函数的prototype,。。。直到Object.prototype 上。所以,p1,p2对age的访问,在p1没有重新对age赋值时,都是从其prototype上获得的,当执行了p1.age = 3333;则再调用p1.age 时其实就是从p1实例本身上找到了相应的age而不是到其prototype链上,因此,其对p2也不会有影响。

----原型链,值的读取顺序,应当是这样的,此为 正解,我以为。

某也是这么认为!

正解!
29 楼 yutianc 2010-09-30 11:15
nalan 写道
watershi tter 写道
例子中
如果 
alert(p1.prototype)
结果是 undefined  说明 对象创建的时候并没有初始化它的prototype
只是 function 对象创建的时候才会初始化prototype 

那么 这句话 “在javascript中,每个对象都有一个prototype属性,这个属性指向了一个prototype对象.
上面我们提到了用new来创建一个对象的过程,事实上在这个过程中,当创建了空对象后,new会接着操作刚生成的这个对象的prototype属性.”
是不是有问题呢~

事实上 p1 ,p2 之所有有age,是通过
p1.constructor.prototype.age  找到的~

所以创建对象的时候没有初始化prototype,而是初始化了constructor,指向构造函数

这个应该是正解!


这个才是正解
28 楼 dingyaodanv1 2010-03-04 08:54
myali88 写道
我是这样理解的,JS中所谓实例继承了类的属性,实际上是对属性的调用是沿着prototype的链来查找的,先找实例本身,再找其构造函数的prototype,。。。直到Object.prototype 上。所以,p1,p2对age的访问,在p1没有重新对age赋值时,都是从其prototype上获得的,当执行了p1.age = 3333;则再调用p1.age 时其实就是从p1实例本身上找到了相应的age而不是到其prototype链上,因此,其对p2也不会有影响。

这和楼主是一个意思啊,只不过楼主怕有的同学看不懂又引用java(面向对象)里面的思维模式来阐述一下其原理。这样有助于搞面向对象的同学们理解的更彻底。很好 是我看过介绍prototype最好理解的一篇了。
27 楼 bjsuo 2009-12-17 14:45
winter8 写道
moliu 写道
constructor
myali88 写道
我是这样理解的,JS中所谓实例继承了类的属性,实际上是对属性的调用是沿着prototype的链来查找的,先找实例本身,再找其构造函数的prototype,。。。直到Object.prototype 上。所以,p1,p2对age的访问,在p1没有重新对age赋值时,都是从其prototype上获得的,当执行了p1.age = 3333;则再调用p1.age 时其实就是从p1实例本身上找到了相应的age而不是到其prototype链上,因此,其对p2也不会有影响。

----原型链,值的读取顺序,应当是这样的,此为 正解,我以为。

某也是这么认为!

正解
26 楼 kinglot 2009-12-04 11:03
我再加一段代码,希望可以帮助大家理解prototype
Object.prototype.ohNo = function(){alert('Ok,You have too much')};
var obj='';
obj.ohNo();
这样所有的类型的实例,无论是String,Array....都具备了ohNo()这个方法,希望可以帮助大家理解.
25 楼 yjydmlh 2009-11-23 16:49
如果定义一个Person,一个Student,如果让Student继承自Person,那么是不是可以这样写呢:Student.prototype=Person?
24 楼 winter8 2009-10-03 15:36
moliu 写道
constructor
myali88 写道
我是这样理解的,JS中所谓实例继承了类的属性,实际上是对属性的调用是沿着prototype的链来查找的,先找实例本身,再找其构造函数的prototype,。。。直到Object.prototype 上。所以,p1,p2对age的访问,在p1没有重新对age赋值时,都是从其prototype上获得的,当执行了p1.age = 3333;则再调用p1.age 时其实就是从p1实例本身上找到了相应的age而不是到其prototype链上,因此,其对p2也不会有影响。

----原型链,值的读取顺序,应当是这样的,此为 正解,我以为。

某也是这么认为!
23 楼 moliu 2009-09-18 22:18
constructor
myali88 写道
我是这样理解的,JS中所谓实例继承了类的属性,实际上是对属性的调用是沿着prototype的链来查找的,先找实例本身,再找其构造函数的prototype,。。。直到Object.prototype 上。所以,p1,p2对age的访问,在p1没有重新对age赋值时,都是从其prototype上获得的,当执行了p1.age = 3333;则再调用p1.age 时其实就是从p1实例本身上找到了相应的age而不是到其prototype链上,因此,其对p2也不会有影响。

----原型链,值的读取顺序,应当是这样的,此为 正解,我以为。
22 楼 moliu 2009-09-18 22:03
prototype对象
moliu 写道
所以创建对象的时候没有初始化prototype,而是初始化了constructor,指向构造函数

迷糊
21 楼 moliu 2009-09-16 15:55
所以创建对象的时候没有初始化prototype,而是初始化了constructor,指向构造函数
20 楼 sdcyst 2009-08-10 18:17
nalan 写道
new Person(name,sex)的过程实际上是:
1、生成一个空对象{}
2、执行构造函数Person(实际上就是普通函数),将函数中的this用刚生成的空对象代替,相当于给刚生成的空对象添加属性及值。
3、初始化空对象(实际上现在已经有了属性,是构造函数赋予的)的constructor属性,让他的值为构造函数(函数也是对象)。
4、整个过程没有初始化空对象的prototype属性,实际上空对象的prototype是没有定义的。

那是不是用new出来的每个js对象都没有prototype属性?
19 楼 魏祖清 2009-07-23 17:13
我认为不是很好 很杂 看看这个《悟透JavaScript》会更好,地址为http://hi.baidu.com/hui7405846/blog/item/84d63dfcc2c0de1e09244dd9.html
18 楼 nalan 2009-07-08 15:15
new Person(name,sex)的过程实际上是:
1、生成一个空对象{}
2、执行构造函数Person(实际上就是普通函数),将函数中的this用刚生成的空对象代替,相当于给刚生成的空对象添加属性及值。
3、初始化空对象(实际上现在已经有了属性,是构造函数赋予的)的constructor属性,让他的值为构造函数(函数也是对象)。
4、整个过程没有初始化空对象的prototype属性,实际上空对象的prototype是没有定义的。
17 楼 nalan 2009-07-08 15:02
watershi tter 写道
例子中
如果 
alert(p1.prototype)
结果是 undefined  说明 对象创建的时候并没有初始化它的prototype
只是 function 对象创建的时候才会初始化prototype 

那么 这句话 “在javascript中,每个对象都有一个prototype属性,这个属性指向了一个prototype对象.
上面我们提到了用new来创建一个对象的过程,事实上在这个过程中,当创建了空对象后,new会接着操作刚生成的这个对象的prototype属性.”
是不是有问题呢~

事实上 p1 ,p2 之所有有age,是通过
p1.constructor.prototype.age  找到的~

所以创建对象的时候没有初始化prototype,而是初始化了constructor,指向构造函数

这个应该是正解!
16 楼 eatsun1983 2009-06-17 20:38
太好了,把自己的理解给大家讲的透彻,跪谢
15 楼 myali88 2009-06-10 16:07
我是这样理解的,JS中所谓实例继承了类的属性,实际上是对属性的调用是沿着prototype的链来查找的,先找实例本身,再找其构造函数的prototype,。。。直到Object.prototype 上。所以,p1,p2对age的访问,在p1没有重新对age赋值时,都是从其prototype上获得的,当执行了p1.age = 3333;则再调用p1.age 时其实就是从p1实例本身上找到了相应的age而不是到其prototype链上,因此,其对p2也不会有影响。
14 楼 pj8324 2009-06-04 15:53
用new操作符生成了两个对象p1,p2,他们的age属性和print方法都来自(继承于)Person类的prototype属性.然后,我们
修改了p1的age属性,后面对Person类的prototype属性中的age重新赋值(Person.prototype.age = 22),p1的age属性并不会随之改变
-----------相当于子类对父类的方法进行了重写
但是p2的age属性却随之发生了变化
-----------子类依然继承了父类的方法
13 楼 whicky 2009-05-20 17:14
辅助的扩充类,这样理解对不对?
12 楼 akira1986 2009-05-15 16:51
大概上明白了!很好的一篇文章!
11 楼 wdw8217 2009-04-27 08:46
  很值得看的文章 ~!~!
sdcyst 应该花了很多心思总结的
每天早上看一章收获很大
10 楼 vavi 2009-04-14 23:18
例子中
如果 
alert(p1.prototype)
结果是 undefined  说明 对象创建的时候并没有初始化它的prototype


在ie和firefox中都是一样undefined。。
9 楼 watershitter 2009-03-08 21:40
例子中
如果 
alert(p1.prototype)
结果是 undefined  说明 对象创建的时候并没有初始化它的prototype
只是 function 对象创建的时候才会初始化prototype 

那么 这句话 “在javascript中,每个对象都有一个prototype属性,这个属性指向了一个prototype对象.
上面我们提到了用new来创建一个对象的过程,事实上在这个过程中,当创建了空对象后,new会接着操作刚生成的这个对象的prototype属性.”
是不是有问题呢~

事实上 p1 ,p2 之所有有age,是通过
p1.constructor.prototype.age  找到的~

所以创建对象的时候没有初始化prototype,而是初始化了constructor,指向构造函数

发表评论

您还没有登录,请您登录后再发表评论

文章信息

Global site tag (gtag.js) - Google Analytics