JS学习之原型与继承

来源:互联网 发布:无线路由器品牌 知乎 编辑:程序博客网 时间:2024/06/07 20:39

其实这篇文章很早就想打算写了,不过因为在和同学忙一个互联网+的项目,一直没时间。刚好最近开始复习前端的知识,发现自己以前用JavaScript和jQuery都属于拿来主义,没有真正理解透其中的原理,所以就立马记录下来。

之前面试华为的时候,有个同学被面试官问JavaScript的继承是怎么实现的,当时就愣住了,面试官又问异步机制的实现方法,也没答上来,一面就挂了。


首先,JavaScript中可以通过function和new来创建对象。两者的区别就是function创建的对象具有一个prototype属性,而new出来的只是一个普通对象。

就类似于在C++里面,定义一个类,它是可以作为基类被继承的,它本身包含了一个指向自己的this指针(非静态),而new出来的只是一个实例,虽然也有this指针,然而并不能被继承。我在这里实际上是把原型这东西跟面向对象的概念挂钩,以方便自己理解。

假设现在我们有以下代码

function T(arg){    this.arg = arg;}var Obj = new T(1);
在这里,T就可以看作是Obj的构造函数。那么这几行代码实际执行的是什么呢?

个人理解如下:
① 执行var Obj = {},初始化一个空的Obj对象;

② Obj._proto_ = T.prototype;

③ T.call(Obj, 1);

这里涉及到call,我们先简单地理解为用Obj替换T,使得Obj获得T的所有方法和数据。这一点在继承中很重要。

我们可以看到,这里Obj是通过_proto_指向T.prototype的,为何?因为之前说过,new出来的对象是没有prototype属性的,只有函数对象有prototype属性。

到这里我们好像还是云里雾里,实际上我们需要搞清楚一个概念:prototype是函数本身的一个属性,例如T.prototype;而它指向了一个随着函数一起被创建出来的原型对象,我们称它为T prototype,它包含了数据成员和方法,以及一个constructor属性,来表明它是由哪个函数所构造的。

大概是这样:

【函数对象】T ——》【T的属性】T.prototype ——》【T构造出来的原型对象】T prototype ——》【原型对象中的属性】T prototype.constructor ——》【函数对象】T ;


【new实例化对象】Obj ——》【实例化对象的属性】Obj._proto_ ——》【T构造出来的原型对象】T prototype 。

将函数对象和原型对象区分开之后,就很好理解了,函数对象构造原型对象,原型对象作为所有new实例对象的原型。

如果这时候打印T.prototype._proto_,会发现输出null,这是因为我们已经到达了原型链的终点。


理解了原型之后,我们就可以探索JavaScript中的继承了。

JavaScript里面的继承有多种实现方式,下面先来讲利用prototype实现的继承。其实这部分有点复杂,我到现在还是有点蒙蔽。
假如现在有一个函数对象如下:

function A(){};A.prototype.Greet = function (){ alert("Hello from A Prototype"); }var insA = new A();insA.Greet(); //Hello from A Prototype

我i们想要定义一个函数对象B来继承A:

function B(){};B.prototype = new A();B.prototype.Greet = function (){ alert("Hello from B Prototype"); }B.prototype.constructor = B; //这里如果不加修改的话,会发现B的原型对象的构造函数照搬了A原型对象的constructor属性,也就是Avar insB = new B();insB.Greet(); //Hello from B Prototype
上面就是一个简单的原型继承。

还有结合call/apply实现的继承方法:

function A(arg_1){    this.arg_1 = arg_1;}function B(arg_2){    this.arg_2 = arg_2;}B.prototype = new A(); //此时B中拥有了arg_1属性,当访问B.arg_1的时候,先查找B的实例属性,如果没有,则回溯原型链,在原型对象中找到arg_1并返回但是此时我们可以发现,B虽然拥有了A的属性,但好像没办法对其进行操作,所以我们修改一下:function B(arg_1, arg_2){    A.call(this, arg_1);    this.arg_2 = arg_2;}

这时候就可以对arg_1赋值了,但是原来A中的值岂不是没有了?虽然可以通过B.prototype.x来找到最原始的副本,但是据说这样不好(?)...

所以只能删除掉B的原型中的x属性:

delete B.prototype.x;

最后别忘了将constructor给改回来,否则B prototype会指向函数对象A,这往往是我们所不希望的,我们希望B继承自己的构造函数:

B.prototype.constructor = B;

总结:这部分内容确实看得我一愣一愣的,尤其是继承这部分比C++要难理解一些。之前做项目的时候也没怎么深入研究,直接call就水过去了,看来还是要继续学习。




 





1 0
原创粉丝点击