Prototype
撰写时间:2023-05-30
修订时间:2025-12-08
构造器
初识构造器
在JavaScript中,构造器(constructor)是用以创建类的实例的函数。
上面代码查看变量a的名为constructor的属性值。在终端将显示:
关系示意图:
即,使用了Number这个构造器来创建了一个值为25的整数的实例。
在其它编程语言中,我们从类创建对象实例。但在JavaScript中,函数是最重要的一等公民,我们从函数创建对象实例。能创建对象实例的函数,就称为构造器。
因此,像上面一样,以后当我们看到一个函数,其函数名是大写的JavaScript内置的数据类型,我们就应当清醒地认识到,这不是一个普通的函数,而是一个能创建对象实例的构造器。
再探构造器
显示:
Date是一个类,而date是Date这个类的一个实例。这是我们从其他编程语言中得到的普遍共识。
但在JavaScript中,Date是什么?
终端显示:
当我们使用console.log(Date)来查看Date时,从C语言, Python语言一路走来的我们会想当然地认为,这是一个名为Date的类。但正如上面所见,Date不是一个类,而是构造器,一个可以创建对象实例的函数。
查看date这个实例的构造器:
终端显示:
两者是否为同一对象?
变量date的构造器就是Date,两者为同一对象。
综上,当我们编写如下代码:
时,我们所看到的,就是各种用以创建相应实例的构造器。
终极构造器
变量date的构造器为Date,后者呢?它有没有相应的构造器?
终端显示:
说明Date的构造器为Function。也即Function构造器先创建了Date构造器,然后再通过后者来创建各个相应与日期有关的实例。
查看Object的构造器:
就连根对象Object,其构造器也为Function。
Function呢?既然你这么牛,你的构造器又是哪个?
不用再找了,Function就是传说中的终极构造器,它甚至是它自己的构造器!
神通广大的孙悟空,绝非凡人所生,因此它得从一块仙石中蹦出来。你能想像,孙悟空将它自己生下来吗?孙悟空做不到,而Function却可以做得到!Function简直就是宇宙第一推动力啊!
通过字面符创建对象
当我们看到:
的代码时,很清楚,显式地使用了Date构造器来创建实例。
而对于以下代码:
则是通过字面符(literal notation)的方式来创建对象实例。在其内幕,通过调用了相应的构造器来创建对象实例。
构造器的属性
构造器有两个重要属性,一个是name,返回构造器的字符串表示;另一个是prototype,下面详细谈到。
Prototype
遍历自身属性
遍历自身属性分为2种情况,一是遍历特定类的属性,二是遍历一个实例的属性。
遍历特定类的属性时,使用Object.getOwnPropertyNames方法来遍历。
遍历一个实例的属性时,使用Object.getOwnPropertyNames方法或for ... in语句来遍历。
Object.getOwnPropertyNames包括不可列举的所有自身属性,但不包括链路中的属性;for ... in语句可遍历所有链路中的属性,但仅可遍历可列举的属性。
Object构造器
Object所有自身属性
Object是创建对象的顶层构造器,下面列出其所有的自身属性:
Object的prototype属性
Object的prototype属性是一个对象。下面列出该对象的所有属性。
可以看到,当我们创建一个特定对象时,这个对象将自动共享Object的prototype对象的所有属性,包括我们比较熟悉的toString方法在内。
需指出的是,不是每个对象都有prototype属性,只有构造器才有此属性。如前所述,Object是一个创建任意对象实例的顶层构造器。
构造器的prototype对象有一个constructor属性,指向构造器自己。
prototype链
JavaScript通过prototype链来实现对象的继承。
具体细节是,每个对象都由一个特定的构造器来创建。每个构造器都有一个prototype属性。而prototype属性所指向的对象中,又有一个__proto__属性,指向父级构造器的prototype,从而形成一个prototype链。
作为最顶层的构造器,Object.prototype.__proto__的值为null,表示上面没有最高层的构造器了。
当我们以字面符方式来创建一个对象时,创建该对象的构造器是Object。
此时,prototype链中只有一级构造器。即obj由Object构造器创建。
从上面输出结果来看,若从其他编程语言的角度,我们可以说obj是Object的子类,继承了Object的toString等方法。但在JavaScript中,Object只不过是obj的构造器,由所创建的对象实例所共享的属性都全部统一置于prototype属性中。
从对象实例obj的角度,我们可以获知创建该对象是哪个构造器。
我们可以调用Object.getPrototypeOf,直接获取创建该对象实例的构造器的prototype。
上面谈到,Object的prototype属性所指向的对象是专供子类继承的,而该对象中又有一个__proto__属性,因此,任何由Object所创建的实例都因继承的原因而也有一个__proto__属性。该属性值与Object.getPrototypeOf的返回值是同一对象。
下面看有多级构造器的情况。
变量date的构造器是Date,Date的构造器是Object。从而形成了一个两级的prototype链。
下面以Number为例来查看各个对象的关系图:
图示:
自定义prototype链
可直接将实例对象的__proto__属性值指向另一对象,从而改变原有的prototype链。
对于代码:
JavaScript引擎将做以下3件事:
- 使用Object这个构造器来创建一个新的构造器。
- 将新构造器的prototype属性设置为protoObj。
- 使用新构造器来创建obj实例。
Object.create()
可以通过Object.create方法以在创建对象时指定对象的原型。
可以在创建对象时同时设置对象的各个属性。
第2个参数是一个称为属性描述器的对象。在该对象中,各个键名是字符串,键值是一个描述器对象 (descriptor object)。上面为name及age两个属性分别指定了属性值。
实现自定义构造器
可以创建自定义构造器,并通过设置其prototype属性以让所创建的实例共享相应属性。
在构造器的prototype内的属性,均可被所创建的对象访问。因此,上面代码中,add, address,以及Object的prototype属性内的所有属性及方法,均可被所创建的对象访问。而name及age只能被obj自己访问。
这就是JavaScript判断对象是否具有相应属性名称的机制。简而言之,它只需要检查该对象构造器的prototype的属性,并按从近到远的方式来确定。
了解此点后,作为开发人员,只需把握一条:将需要各个对象实例所共享的属性放在构造器的prototype的属性中即可。至于跟踪繁杂的prototype链
,这是JavaScript内部操作的问题,从而大大减轻了开发人员的工作量。
