测试目录:

clipboard.png

在html文件中引入js文件

clipboard.png
以下所有文件都在base.js中玩~

原型(Prototype)-构造器(Constructor)

一说到原型,一定和对象直接相关。
以前我们这样声明一个对象

clipboard.png

当对象是一个用户时

clipboard.png

clipboard.png

当我们又需要一个用户时

clipboard.png

clipboard.png

可以看到两个对象的结构无区别。

那么问题来了,如果我们需要创建100个用户时,也要这么做吗?当然不要
两个原因:

  1. 用户的信息是动态的,我们不能在代码中把它们写死。

所以想到一种办法简化我们的工作:创建一个function专门为我们生成user

clipboard.png

clipboard.png

可以看出,打印的结果与前面的没有任何区别~
但是我们却少写了很多代码。

这个function的本质就是生成对象。
在这个function中,先新建一个空对象,然后将它填满,最后返回这个对象。
这样的函数我们称它为工厂函数(Factory Function),专门生产对象。

在原生JS中,有一种更加简便的生产对象的方式

clipboard.png

这个function中的this就代表它即将生成的对象。
注意:一旦使用这种方式来定义一个工厂函数时,就要用new关键词来叫它。

clipboard.png

clipboard.png

打印结果依然与前面的没区别~

这种写法相当于原生JS帮我们省去了声明对象和返回对象的步骤。

像这样用new关键词叫的function通常我们称它为构造器/构造函数(Constructor),构造函数的第一个字母一般大写。
不大写也没关系,大写不过是为了让其他人知道这是一个构造函数。

clipboard.png

Constructor的概念应该理解的差不多了,下面我们再造几个构造器作为练习。

实例一:

clipboard.png

clipboard.png

实例二:

clipboard.png

clipboard.png

原型-prototype和__proto__

生成一个对象的过程叫实例化(Instantistion)。不管在哪种语言里面都一样。

clipboard.png

我们再给这个构造器加一些功能,比如greet和eat。

clipboard.png

实例化一个对象whh

clipboard.png

运行whh.greet();

clipboard.png

当我们还需要一个用户

clipboard.png

clipboard.png

可见两位用户都可以greet。但是注意,它们两都实例了this.greet这个能力。也就是说,whh这个对象中有greet这个能力(方法),lsd这个对象中也有同样的方法。这两个方法是完全独立的,我们来验证这一点,在控制台中测试:

clipboard.png

如图,说明它每实例化一次都会给自己创造一份与其它对象同样的方法。在本例中也就是greet和eat方法。这里可以理解为拷贝代码。将构造器中的方法拷贝到实例化的对象当中。

这个时候我们想 有必要一次次拷贝吗?能不能想一种办法把它们放到一个地方?当实例化的对象要用的时候用一个东西就可以了。这就引入了原型(prototype)。

创建一个function。

clipboard.png

在控制台输入a.prototype

clipboard.png

发现它是一个对象。
原生JS就有这样一个机制,在我们创建一个function的时候,它就首先给我们prototype这样一个对象,放到function下。

为什么要这么做呢?prototype有什么用呢?
prototype就是用于给它即将生成的对象继承下去的属性。也就是说我们一眼看不出来,但它实际拥有的能力。我们来举个栗子

clipboard.png

clipboard.png

它没有在自有的属性中显示name属性,而在继承的属性中显示。但当我们使用的时候是相当于自己的属性在用的。而且不管我们new了几个对象,它们中的__proto__都是一样的(其实是指向同一对象的)。下图可验证。

clipboard.png

既然如此,我们在以下例子中就可以把greet方法放到prototype中了。

clipboard.png

修改为

clipboard.png

在控制台中

clipboard.png

也就是说,现在的function只会存在一个地方,它不再需要每次都拷到实例的对象中去。

用原型的方式来指定一个方法还有一个好处,可以动态更新。

当我们想知道一个对象来自于那个工厂函数,可以在控制台中输入 对象.constructor
如图

clipboard.png

那么就意味着,如果我们想复制一个对象的结构,可以这样做:

clipboard.png

这就相当于

clipboard.png

以上就是有关prototype和__proto__的逻辑。

原生对象的原型

学到这里也许我们会有个疑问,当我们就这样创建一个对象时,它的原型是什么呢?或者说它有没有父亲呢?是谁创建的它呢?

clipboard.png

我们来打印一下就知道了~

clipboard.png

clipboard.png

它的constructor是一个叫Object的函数,也就是说,它是有父亲的。
也就是说,以下两种写法是等价的。

我们来测试一下以上说法是否正确。

声明两个对象

clipboard.png

然后再控制台中测试

clipboard.png

也就是说上面两种创建对象的方法是等价的~

还有一种情况,当我们想创建一个不继承任何东西的对象时该怎么做呢?
我们可以这样:

clipboard.png

clipboard.png

这样我们就创建了一个最干净的对象~

在参数中设置原型,比如

clipboard.png

clipboard.png

看到这里,我们应该不再恐惧对象这个东西了,它的继承关系我们已经搞的很清楚了。
下面我们做个练习:
a是一个数组,我们打印它

clipboard.png

clipboard.png

可以清楚的看到,它的constructor是一个叫Array的function

也就相当于

clipboard.png

两种写法是等价的。

我们继续往下看

clipboard.png

Array()也有继承的属性,它的constructor是Object()。

综上,如果Object()是爷爷,那么Array()就是爸爸,而我们声明的数组对象a就是儿子。

我们来试一下从爸爸那继承来的方法:(拿push方法来举例)

clipboard.png

证明它从爸爸那继承下来的方法确实能用。

我们再来试一下从爷爷那继承来的方法:(拿toString方法来举例)

clipboard.png

总结:在JS中只要我们不明确的用Object.create()来创建对象,其余都是继承Object.prototype的。

如何实现多级继承链

有这样一条继承链

clipboard.png

这样的一条继承链在代码中如何体现? 我们可以应用到之前说过的构造器。

创建三个构造器,并且使用Person构造器创建一个叫lsd的人。

clipboard.png

我们从最顶级开始看
实例两个对象并打印

clipboard.png

clipboard.png

前面我们提到过,实例出来的对象会各自拷一份能力。所以打印结果是false。

clipboard.png

事实上这些纯逻辑(纯能力)的东西是没有必要拷贝的,那我们为什么不将它们放到原型中去呢?

clipboard.png

刷新网页

clipboard.png

此时的eat都是指向prototype的。相当于借了一个能力过来,而非拷贝一个能力过来。

接下来我们看Mammal这个构造器

clipboard.png

clipboard.png

那么此时实例化的对象m有没有eat和sleep这种能力呢?

clipboard.png

可见是没有的。因为此时三个构造器是独立的,我们还没有指定它们之间的继承关系。那么如果指定这条继承链呢?

拿Mammal和Animal说,Mammal不仅要继承Animal的prototype,而且还要有自己的东西。

我们可以用到前面提到的create方法。

clipboard.png

这种写法就相当于 {__proto__:Animal.prototype}

现在我们再来测试一下实例化的对象m

clipboard.png

这样我们就可以说它继承成功了。

不过现在还有一个问题,我们在控制台调用一下m对象的constructor。

clipboard.png

打印结果是Animal而非Mammal。而事实上Mammal才是m对象的constructor。这是为啥呢?

仔细看对象m的打印结果,发现并没有constructor。

clipboard.png

这是因为我们在前面把它的prototype重写了,覆盖掉了。

clipboard.png

所以才会继承了上一级的constructor。

补救方法是,我们再明确指定一下就可以啦。

clipboard.png

大功告成~

接下来我们看Person

clipboard.png

clipboard.png

到这里我们就完成了原型的三级继承~~~~撒花!

现在还有一个问题,不同的动物毛色可能不一样,体重可能也不一样,等等等。也就是说,它们会共有一些属性,不过值不一样。
我们可以这样做:

clipboard.png

clipboard.png

此时实例化的对象中就有了我们用this指定的两个显性属性"color"和"weight"

但是当我们实例一个Mammal的对象并传入参数,然后打印。

clipboard.png

clipboard.png

我们发现,打印出来的是一个空对象。
我们希望的是,只要是动物(不管是哺乳动物还是人),都存在毛色,体重等等属性。
这个功能如何实现呢?
我们可以这样做:

clipboard.png

我们希望Mammal中的this与Animal中的this绑定到一块。然后正常传入参数。

clipboard.png

在Person中同理

clipboard.png

我们来打印一个Mammal中实例化的对象m做测试,看看它是否继承Animal中的两个显性属性。

clipboard.png

clipboard.png

我们再来试一下Person

clipboard.png

clipboard.png

我们再new一个Person。

clipboard.png

clipboard.png

可见,显性属性是拷贝的。它们之间互不影响。这就是显性属性的继承方式。


onion
12 声望1 粉丝

前端工程师


下一篇 »
GIT入门