博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Javascript: 从prototype漫谈到继承(1)
阅读量:5264 次
发布时间:2019-06-14

本文共 4310 字,大约阅读时间需要 14 分钟。

本文同时发布在另一独立博客 

javasscript的prototype原型链一直是一个难点,这篇文章是对自己这段时期学习的一个总结,在这里不谈ECMAScript标准,也不会用UML图画出各种关系(结合这两方面谈的文章非常的多,但大部分都相当晦涩,比如汤姆大叔),只力求最浅显易懂,深入浅出,供以后自己和各位参考。

javascript的function一种对象(object),他们有方法和属性,方法比如call/apply,而prototype则是function的一个属性。

一旦你定义了一个函数,它即自带了一个prototype属性

function t(){}; typeof t.prototype // "object";

 

你可能已经知道使用函数作为一个构造函数,来生产一系列对象。比如

function Some(name, color){
this.name = name; this.color = color; this.method =function(){} } var a1 =new Some("Lee","black");//实例化一个对象

 

上面的Some类的属性和方法也可以放在prototype对象中,比如

function Some(){} Some.prototype.name ="Lee"//形式一 Some.prototype ={ //形式二    name:"lee",    color:"black",    method:function(){} } var a1 =newSome("Lee","black");//实例化一个对象

 

虽然形式不同,但至少现在使用起来的效果是一致的。当你使用a.Lee或者a.method时,结果是一样的,现在还看不出分别

Ok,那么第一点要注意的是,prototype是活着(live)的属性! 

function Some(){} var a =newSome(); a.method // undefined Some.prototype.method = function(){
console.log("hello"); }a.method // function () {console.log("Hello")}

 

上面的代码想说明的是,在生成实例a时,构造函数没有method方法,所以a也没有,可以理解;但是之后构造函数在prototype属性里又添加上去了,虽然是在a生成之后添加的,但是a仍然照样拥有,与构造函数添加的时间无关。

第二个问题来了,如果这个对象内部和prototype都定义了相同的字段怎么办,比如

function Some(){
this.color ="yellow"; } Some.prototype.color ="black"; var a =newSome(); a.color //?

上面的代码中,我在对象的内部和prototype上分别都定义了color,当我从实例中访问的时候,应该显示的是哪一个颜色?

要注意的是第二点,javascript引擎首先会检查a的属性里有没有color,如果没有的话去它的构造函数的prototype(a.constructor.prototype)里有没有该属性

 

让我们再看的远一点,任何一个对象都应该有自己的构造函数,函数的prototype属性也是个对象,那它的构造函数是什么?

functionSome(){
this.color ="yellow"; } var a =newSome(); a.constructor.prototype.constructor // function Some() {this.color = "yellow";}a.constructor.prototype.constructor.prototype // Some {}

 

上面的原型链可以无限的追溯下去,通过原型链,可以追溯到最终的构造函数Object(),这也就解释了,为什么即使我们没有在函数上定义toString()函数,a.toString()的方法也是存在的,因为它最终调用的追溯到的Object的toString方法。

 

新的问题是,如何区分自己的property和原型链上的属性,并且你能保证所有的属性都是可以访问的吗?

众所周知,用for...in循环就可以解决这个问题,关于这个问题,只需要记住三点

  • 虽然在循环中对象自己的属性和原型链属性都会被列举出来,但并非所有属性都会被列举,比如一个数组的length和.splice之类的方法就不一定会被列举出来,可以列举出来的属性都是可枚举的(enumerable)
  • 如何区分对象自己的属性还是原型链的属性?使用hasOwnProperty()方法
  • 注意propertyIsEnumerable()方法,虽然该方法名字是“可枚举的属性”,但是原型链中所有的属性都会反悔false,即使是可枚举的

还有一个对象的属性叫做__prop__,个人认为用处不大,只推荐在调试的时候使用,具体用法google去吧

关于原型的继承

如何写一个好的继承方法?这是一个逐渐演化过程,先从最简单的继承谈起

function Parent(){
this.deep ="Hello"; } function Child(){
this.shallow ="World"; } Child.prototype =new Parent(); var c =newChild(); console.log(c.deep);

 

当我们要访问c的deep属性时

  • 首先去c对象下查看有没有deep属性,没有
  • 再去c.construct.prototype对象的属性里查找,Parent的实例里查找,有

但是上面的代码有一个问题,当你不断实例化Child时,Parent也不会被实例化,都会生成一个deep载入内存中,如果这个deep是共享的话,不如把deep放在prototype中

function Parent(){} Parent.prototype.deep ="Hello"; function Child(){
this.shallow ="World"; } Child.prototype =new Parent(); var c =newChild(); console.log(c.deep)
 

当我们要访问c的deep属性时

  • 首先去c对象下查看有没有deep属性,没有
  • 再去c.construct.prototype对象的属性里查找,Parent的实例parent里查找,没有
  • 再去parent.construct.prototype查找deep,有

这么做的弊端之一就是在查找某个属性的时候可能会多查找一轮

让我们继续改进,我们发现我们需要的deep只在Parent的prototype上,那么其实我只需要Parent的prototype而不是Parent的实例

function Parent(){} Parent.prototype.deep ="Hello"; function Child(){
this.shallow ="World"; } Child.prototype = Parent.prototype; var c =new Child(); console.log(c.deep);

 

这样既避免了Parent的实例化,又避免了上一个例子中多一步的查找。但是有一个副作用,因为是对对象直接的引用,所以当Child.prototype.deep被修改时,Parent.prototype.deep也会被修改。那我们继续优化的目标就很明确了,要阻止这种对父类prototype的直接引用。

于是我们决定使用一个中间变量

function Parent(){} Parent.prototype.deep ="Hello";// 注意,来了var F =function(){};F.prototype = Parent.prototype; function Child(){
this.shallow ="World"; } Child.prototype = new F(); var c = new Child(); console.log(c.deep)

我们用F来作为一个中间变量,来阻止child对deep的修改可能影响parent

当我们要访问c的deep属性时

  • 首先去c对象下查看有没有deep属性,没有
  • 再去c.construct.prototype对象的属性里查找,F的实例里查找,没有
  • 再去F.construct.prototype查找deep,有

让我们来捋一捋为什么对Child.prototype的修改不会影响Parent.prototype

  • 在上一个例子中,我们对Child.prototype的操作就是对Parent.prototype的操作,无论读还是写,用的是别人的
  • 在这个例子中,Child.prototype不是对Parent的直接引用,而是一个新的空对象。在没有deep而我们需要deep时,被迫去Child.prototype的构造函数上去找,追溯到了Parent.protoype,而当我们需要写时,操纵的其实是Child.prototype = {}这个空对象。

于是我们把最后一个代码片段抽象为一个方法

function extend(Child,Parent){
var F =function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); // 一旦重置了函数的prototype,需要重新赋值prototype.constructor, // 忽略这方面的介绍 Child.prototype.constructor = Child; // 保留对父类的引用, // 忽略对这方面的介绍 Child.uber =Parent.prototype; }

 

转载于:https://www.cnblogs.com/hh54188/archive/2013/05/04/3059364.html

你可能感兴趣的文章
WPF文本框只允许输入数字[转]
查看>>
事务的四种隔离级别和七种传播行为
查看>>
dom4j 通用解析器,解析成List<Map<String,Object>>
查看>>
13. 用Roberts、Sobel、Prewitt和Laplace算子对一幅灰度图像进行边缘检测。观察异同。...
查看>>
第一个项目--用bootstrap实现美工设计的首页
查看>>
使用XML传递数据
查看>>
手机自带输入法emoji表情的输入,提交及显示——前端解决方案
查看>>
TYVJ.1864.[Poetize I]守卫者的挑战(概率DP)
查看>>
LOJ.6160.[美团CodeM初赛 RoundA]二分图染色(容斥 组合)
查看>>
基于CMMI的敏捷开发过程文档裁剪
查看>>
0925 韩顺平java视频
查看>>
软件需求规格说明书
查看>>
53. Maximum Subarray
查看>>
iOS-程序启动原理和UIApplication
查看>>
SpringMVC入门(二)—— 参数的传递、Controller方法返回值、json数据交互、异常处理、图片上传、拦截器...
查看>>
git的安装
查看>>
mysql 8.0 zip包安装
查看>>
Spring框架系列(三)--Bean的作用域和生命周期
查看>>
springboot + mybatis
查看>>
awk 统计
查看>>