浅谈javascript中的prototype
2015-06-17 15:18
801 查看
在本文中,我们讲解prototype的内容主要由:什么是prototype,prototype与函数之间的关系,prototype与实例对象之间的关系,使用proto实现一个简单的继承。
这样Person.protptype就指向到了proto,如果还有其他的引用(A)也指向到了proto,那么这个引用和Person.prototype就是相等的:
prototype指向的这个对象是真实存在的,可能很多的同学对prototype属性和原型对象有些混淆,我们在这里把原型对象叫做
那么现在我们就能得到两个引用关系:
大SB = Person.prototype; // 原型对象 = 函数.prototype;
Person = 大SB.constructor(Person.prototype.constructor); // 函数 = 原型对象.constructor
从运行的代码中,可以验证我们的观点,Person.prototype的类型是
其实在实例对象中,也存在着一个属性指向到
即 John.proto 和 Person.prototype 指向的是同一个引用:
从运行的结果我们可以看到,John.name输出的是构造函数中的属性值”wenzi”,John.say()输出的是”hello world”。这是因为,当读取某个对象的属性值时,会首先在实例对象中进行搜索,若搜索到则直接进行返回;若搜索不到则去原型对象中进行寻找。因此在使用John调用say()方法时是正确的,不会报错。而且是进行了两次的搜索。
虽然
从运行
因为,对
这样就能修改prototype的say()方法了。
让我们从图里看看这些的关系,懒得画图了,从网上盗了一张:
最左边是两个实例对象,都有
而
就是对Person的prototype重写了,让prototype进行了重新的指向,本来Person.prototype指向的是
这样就能手动构造出新的原型对象了,new出的实例对象也能使用这个prototype上的属性和方法。
但是这里还存在一个问题,若之前已经有new出的实例对象,然后再修改Person.prototype,之前的实例对象是无法使用新的原型对象(
从上面的例子中可以看到,Son的实例对象继承了Father中所有的属性和方法。当然,若Son的对象实例中存在的,还依然保留。不过Son原型中的属性和方法是会被彻底覆盖的。我们来分析一下这是为什么?
Son的prototype指向的是Father的一个实例,我们把这拆成两步:
在实例
由于constructor是不可枚举的类型,在for~in循环里是输出不了constructor的,其实father是这样的:
因此Son的prototype指向的就是上面father的内容。到此,Son的prototype中的constructor的指向也发生了改变,本来Son.prototype是指向到Son的,现在指向到了Father。即完全重写了Son的prototype,重新指向了另一个引用。所以,我们就能知道为什么对象实例中的属性能够保存,而prototype中的会彻底被删除了。
如果想给Son添加原型方法或属性,那只能是在
这样就能添加上getJob方法了。
到这里,会有人问,如果
因此,如果是
本人博客:www.xiabingbao.com
1. prototype的简要介绍
在javascript中,创建的每个函数天生都自带着一个prototype属性。这里我们要强调的是:这个prototype属性是一个指针,指向一个对象,在这里,我们称指向的这个看不到但确实存在的对象为原型对象。其实可以用下面一个简单的例子来说明:var proto = { name : 'wenzi', age : 25 } function Person(){ } Person.prototype = proto;
这样Person.protptype就指向到了proto,如果还有其他的引用(A)也指向到了proto,那么这个引用和Person.prototype就是相等的:
A==Person.prototype;
prototype指向的这个对象是真实存在的,可能很多的同学对prototype属性和原型对象有些混淆,我们在这里把原型对象叫做
大SB。
大SB与普通对象的一个不同之处就是,他也有一个天生的属性:constructor,这个constructor重新指回到了函数。不过,既然
大SB也是一个对象,那么它也是继承于Object的,拥有Object所有的方法和属性,比如
toString()等。
大SB = { constructor : Person, say : function(){ return "hello world"; } }
那么现在我们就能得到两个引用关系:
大SB = Person.prototype; // 原型对象 = 函数.prototype;
Person = 大SB.constructor(Person.prototype.constructor); // 函数 = 原型对象.constructor
从运行的代码中,可以验证我们的观点,Person.prototype的类型是
object,Person.prototype.constructor重新指回到了Person。
其实在实例对象中,也存在着一个属性指向到
大SB中:
var John = new Person(); 大SB = John.__proto__;
即 John.proto 和 Person.prototype 指向的是同一个引用:
John.__proto__==Person.prototype。
2. 对象实例和prototype中的属性
在构造函数能设置属性和方法,在prototype中也能设置属性和方法,那new出的对象使用的是哪个呢?我们来看一个例子:function Person(){ this.name = "wenzi"; } Person.prototype.name = "bing"; Person.prototype.say = function(){ return "hello world"; } var John = new Person(); alert(John.name); // "wenzi" alert(John.say()); // "hello world"
从运行的结果我们可以看到,John.name输出的是构造函数中的属性值”wenzi”,John.say()输出的是”hello world”。这是因为,当读取某个对象的属性值时,会首先在实例对象中进行搜索,若搜索到则直接进行返回;若搜索不到则去原型对象中进行寻找。因此在使用John调用say()方法时是正确的,不会报错。而且是进行了两次的搜索。
虽然
say()是挂载在prototype上,John也同样能直接进行访问。但是,我们不能直接对John.say进行修改从而影响prototype的
say()方法,如下:
John.say = function(){ return "this has changed"; } John.say(); // "this has changed" var Tom = new Person(); Tom.say(); // "hello world"
从运行
John.say()的结果来看,say()方法确实发生了变化。但是,当我们再new出一个新对象Tom时,
Tom.say()返回的还是”hello world”,这是为什么呢?
因为,对
John.say()进行修改时,不是修改了prototype上的
say(),而是给John这个实例对象添加了一个
say()方法,从而屏蔽了prototype上的
say()方法,由上面的寻找顺序我们可以知道,若实例对象存在这个方法就直接返回了,不再去原型对象上寻找。而new出的新对象
Tom在调用say()方法时,Tom自己是没有say()方法的,只能去原型对象上寻找,因此返回的还是”hello world”。所以,对John.say进行修改时,不是修改了prototype上的say()方法,而是给John添加了实例方法,屏蔽掉了prototype上的方法而已。那如何才能修改prototype上的方法呢,好办,直接在prototype上修改:
Person.prototype.say = function(){ return "wenzi's blog"; }
这样就能修改prototype的say()方法了。
让我们从图里看看这些的关系,懒得画图了,从网上盗了一张:
最左边是两个实例对象,都有
__proto__属性,指向中间的原型对象;中间的原型对象,有
constructor属性,指回到函数Foo,同时因为他也是对象,也有proto属性,指向超类Object.prototype;上面的Foo()函数的prototype指向中间的原型对象,Foo()的proto指向到Function.prototype;下面的是超类Object的原型对象。
3. 重写prototype
在上面的例子中,我们都是大部分给prototype添加属性或方法,本来prototype指向的是大SB,若我们给prototype添加属性或方法时,就是给
大SB添加属性或方法:
Person.prototype.name = "wenzi";
而
大SB里其他的属性和方法是不受影响的,constructor依然指回到Person。但是,若我们这样写:
Person.prototype = { name : "wenzi", say : function(){ return "my name is name" } }
就是对Person的prototype重写了,让prototype进行了重新的指向,本来Person.prototype指向的是
大SB,可是现在却指向了
CTM,而
CTM里也没有constructor属性指回到Person,若是想重新指回到Person,还得手动添加一个constructor属性:
Person.prototype = { constructor : Person, // 重新指回到Person name : "wenzi", say : function(){ return "my name is name" } }
这样就能手动构造出新的原型对象了,new出的实例对象也能使用这个prototype上的属性和方法。
但是这里还存在一个问题,若之前已经有new出的实例对象,然后再修改Person.prototype,之前的实例对象是无法使用新的原型对象(
CTM)上的属性和方法的,因为之前的实例对象的
__proto__指向的依然是
大SB。因此,请慎重重写prototype
4. 原型继承
说到prototype就不得说prototype继承,我们通过给prototype上添加属性和方法,就能使该构造函数所有的实例对象拥有属性和方法。我们先来看下面的代码:function Father(){ this.name = "father"; this.age = 43; } Father.prototype.job = "Doctor"; Father.prototype.getName = function(){ return this.name; } function Son(){ this.name = "son"; } Son.prototype = new Father(); // Son的prototype指向Father的实例对象 var John = new Son(); for(var k in John){ console.log(k+' : '+john[k]); } /* 输出结果: name : son age : 43 job : Doctor getName : function (){ return this.name; } */
从上面的例子中可以看到,Son的实例对象继承了Father中所有的属性和方法。当然,若Son的对象实例中存在的,还依然保留。不过Son原型中的属性和方法是会被彻底覆盖的。我们来分析一下这是为什么?
Son的prototype指向的是Father的一个实例,我们把这拆成两步:
var father = new Father(); Son.prototype = father;
在实例
father中既有name,age属性,也有Father.prototype中的属性和方法,我们对father循环看一下:
for(var k in father){ console.log(k, father[k]); } /* name father age 43 job Doctor getName Father.getName() */
由于constructor是不可枚举的类型,在for~in循环里是输出不了constructor的,其实father是这样的:
father = { constructor : Father, name : father age : 43 job : Doctor getName : Father.getName() }
因此Son的prototype指向的就是上面father的内容。到此,Son的prototype中的constructor的指向也发生了改变,本来Son.prototype是指向到Son的,现在指向到了Father。即完全重写了Son的prototype,重新指向了另一个引用。所以,我们就能知道为什么对象实例中的属性能够保存,而prototype中的会彻底被删除了。
如果想给Son添加原型方法或属性,那只能是在
Son.prototype = new Father();之后进行添加:
Son.prototype = new Father(); Son.prototype.getJob = function(){ return this.job; }
这样就能添加上getJob方法了。
到这里,会有人问,如果
Son.prototype = Father.prototype会怎么样呢?我们刚才也说了Father.prototype指向的是
大SB,而
大SB也是一个实例对象,是Object的一个实例,这就相当于:
Son.prototype == 大SB; // 指向了大SB
因此,如果是
Son.prototype = Father.prototype的话,那么Son只能继承到Father.prototype上的
job和
getName(),其他的属性是获取不到的。
5. 总结
本文简单的介绍了一下prototype,让我们能对prototype有个浅显的认识。当然,博主才疏学浅,文章里会有很多疏漏和不完善的地方,欢迎大家批评指正。本人博客:www.xiabingbao.com
相关文章推荐
- Chart.js绘图,折线图、柱状图
- js中attr 与find 获取属性值,
- javascript运算符优先级
- Jackson 框架,轻易转换JSON
- javascript 循环遍历绑定事件问题
- js选项卡切换效果
- 读书笔记:编写可维护的javascript
- 高性能javascript 文件加载阻塞
- Js设置服务器找不到图片时,显示一张默认图
- Asp.net MVC CSS/Javascript Bundle 配置文件
- 中国省市 Json 二级联动
- 十大要避免的Ext JS开发方法
- Java抓取网页数据(原来的页面+Javascript返回数据)
- Javascript模块化编程(三):require.js的用法
- Javascript模块化编程(二):AMD规范
- Javascript模块化编程(一):模块的写法
- js请求的几种方式
- Selenium2学习-011-WebUI自动化实战实例-009-JavaScript 在 Selenium 自动化中的应用实例之一(赋值)
- js代码实现的简单的天数倒计时
- javascript:window.location.replace 与 window.location.reload() 刷新页面的不同效果