WebGL Tutorial
and more

Prototype

撰写时间:2023-05-30

修订时间:2024-11-28

构造器

初识构造器

在JavaScript中,构造器constructor)是用以创建类的实例的函数。

let a = 25; console.log(a.constructor);

上面代码查看变量a的名为constructor的属性值。在终端将显示:

function Number() { [native code] }

关系示意图:

digraph { rankdir = LR; subgraph clusterVariable { label="num"; node [shape=record]; constructor [label="constructor" fillcolor="#555" fontcolor="#999"]; } subgraph clusterConstructor { labelloc=t node [label=""]; colorscheme=rdbu11 color="#33669900"; fontcolor=4; bgcolor="#3A3A3A"; subgraph cluster_Number { Number [shape=box3d]; label="Number\nconstructor"; } subgraph cluster_function { function [shape=component]; label="constructor\nfunction"; } } constructor -> Number [label="reference" style=dashed color=gray fontcolor=gray]; Number -> function [label="is" arrowhead=inv style=dashed]; }

即,使用了Number这个构造器来创建了一个值为25的整数的实例。

在其它编程语言中,我们从类创建对象实例。但在JavaScript中,函数是最重要的一等公民,我们从函数创建对象实例。能创建对象实例的函数,就称为构造器。

因此,像上面一样,以后当我们看到一个函数,其函数名是大写的JavaScript内置的数据类型,我们就应当清醒地认识到,这不是一个普通的函数,而是一个能创建对象实例的构造器。

再探构造器

let date = new Date(); console.log(date);

显示:

Thu Nov 28 2024 15:50:07 GMT+0800 (中国标准时间)

Date是一个类,而dateDate这个类的一个实例。这是我们从其他编程语言中得到的普遍共识。

但在JavaScript中,Date是什么?

console.log(Date);

终端显示:

function Date() { [native code] }

当我们使用console.log(Date)来查看Date时,从C语言, Python语言一路走来的我们会想当然地认为,这是一个名为Date的类。但正如上面所见,Date不是一个类,而是构造器,一个可以创建对象实例的函数。

查看date这个实例的构造器:

console.log(date.constructor);

终端显示:

function Date() { [native code] }

两者是否为同一对象?

console.log(date.constructor === Date); // true

变量date的构造器就是Date,两者为同一对象。

综上,当我们编写如下代码:

console.log(Boolean); console.log(String); console.log(Array); ...

时,我们所看到的,就是各种用以创建相应实例的构造器。

终极构造器

let date = new Date(); console.log(date.constructor); // Date

变量date的构造器为Date,后者呢?它有没有相应的构造器?

console.log(Date.constructor);

终端显示:

function Function() { [native code] }

说明Date的构造器为Function。也即Function构造器先创建了Date构造器,然后再通过后者来创建各个相应与日期有关的实例。

查看Object的构造器:

console.log(Object.constructor); // Function

就连根对象Object,其构造器也为Function

Function呢?既然你这么牛,你的构造器又是哪个?

console.log(Function.constructor); // Function console.log(Function.constructor === Function); // true

不用再找了,Function就是传说中的终极构造器,它甚至是它自己的构造器!

神通广大的孙悟空,绝非凡人所生,因此它得从一块仙石中蹦出来。你能想像,孙悟空将它自己生下来吗?孙悟空做不到,而Function却可以做得到!Function简直就是宇宙第一推动力啊!

通过字面符创建对象

当我们看到:

let data = new Date();

的代码时,很清楚,显式地使用了Date构造器来创建实例。

而对于以下代码:

let a = 5; let b = "abc"; let c = {name: 'Mike'}; console.log(a.constructor); // Number console.log(b.constructor); // String console.log(c.constructor); // Object

则是通过字面符literal notation)的方式来创建对象实例。在其内幕,通过调用了相应的构造器来创建对象实例。

Prototype

Prototype的作用

let person = { name: 'mike', getName: function() { return this.name; } }; console.log(person.name); // mike console.log(person.getName()); // mike console.log(person.toString()); // [object object]

person有一个name属性,一个getName方法。但我们并未其声明toString方法,哪来的?

可以调用personhasOwnProperty来查看自己是否有特定的属性。

console.log(person.hasOwnProperty('name')); // true console.log(person.hasOwnProperty('getName')); // true console.log(person.hasOwnProperty('toString')); // false

说明person没有自己的属性toString。它是从哪里来的?

Object.prototype

每个构造器都是一个函数,都有一个prototype属性。该属性用于实现基于prototype的继承(protyotype-based inheritance)以及共享的属性(shared properties)。

prototype用以向下提供可以共享的属性(包括属性,以及方法)。__proto__用以向上链接。Object.prototype是JavaScript中最顶层的,用以向一切对象提供共享的prototype.

let obj = { name: 'sarkuya', age: 25, showAge: function() { return this.age; } }; console.dir(obj.__proto__);

显示:

Object __defineGetter__:function() __defineSetter__:function() __lookupGetter__:function() __lookupSetter__:function() constructor:function() hasOwnProperty:function() isPrototypeOf:function() propertyIsEnumerable:function() toLocalString:function() toString:function() valueOf:function()

所显示出来的正是Object.prototype。定义在Object.prototype之下的所有方法及其他属性,均可以被其他对象共享。因此,我们可以直接调用:

console.log(obj.toString()); // 继承了Object的toString()方法

上面的代码console.dir(obj.__proto__)console.dir(Object.prototype)所显示的结果是一样的。因此,下面的断言成立:

console.log(obj.__proto__ === Object.prototype); // true

对象obj__proto__属性指向了类Objectprototype属性。这个就叫做prototype链。因此我们可以按照其他面向对象编程语言的说法来说:对象obj所属的类,继承于类Object

构造器中的prototype属性

构造器虽说函数,但与普通函数相比,多了一个prototype属性。

console.dir(obj.constructor.prototype);

显示:

MyObj constructor: function(name, age) "Object" prototype

prototype的属性值是一个含有两个属性的object对象。第一个属性名称为constructor,指向该构造器自身。第二项属性集中列出了可供下级子对象所共享的属性与方法。从上面可以看出,这些可共享的属性与方法均来自于Object.prototype

然而,我们不能引用第二个属性名称,它由JavaScript内部管理。相反,我们可以像上节所述一样,通过该构造器所实例化的对象来引用它:

console.log(obj.__proto__ === Object.prototype); // true

再看__proto__

无论是MyObj, 还是obj,都带有__proto__属性。

先看obj__proto__属性:

console.dir(obj.__proto__);

显示:

MyObj constructor: function(name, age) "Object" prototype

它与上一节中的obj.constructor.prototype是一样的。我们甚至也可验证:

console.log(obj.__proto__ === obj.constructor.prototype); // true

也就是说,一个具体对象的__proto__属性链向了其构造器的prototype属性。

再来看MyObj__proto__属性:

function () { [natvie code] } apply: function() arguments: ... bind: function() call: function() caller: ... constructor: function() length: 0 name: "" toString: function() Symbol(Symbol.hasinstance): function() "Object" prototype

MyObj以标准的Function作为其构造器。

查看Function的构造器:

console.log(Function.constructor);

显示:

function Function() { [native code] }

说明,Function这个内在对象也有其构造器,其构造器的名称为Function

再看Function__proto__属性:

console.log(Function.__proto__);

显示:

function () { [natvie code] }

通过prototype属性提供共享数据

function MyObj(name, age) { this.name = name; this.age = age; }; MyObj.prototype.address = "default address"; MyObj.prototype.add = function (x, y) { return x + y; }; let obj = new MyObj('sarkuya', 25); console.dir(obj.constructor.prototype);

显示:

MyObj add: function(x, y) address: "default address" constructor: function(name, age) "Object" prototype

在构造器的prototype内的属性,均可被继承的对象访问。因此,上面代码中,add, address,以及Objectprototype属性内的所有属性及方法,均可被子对象访问。而nameage只能被obj自己访问。

这就是JavaScript判断对象是否具有相应属性名称的机制。简而言之,它只需要检查该对象构造器的prototype的属性,并按从近到远的方式来确定。

了解此点后,作为开发人员,只需把握一条:如果需要子对象共享属性,则把这些属性放在构造器的prototype的属性中即可。至于如何建立繁杂的prototype链,这是JavaScript内部操作的问题,从而大大减轻了开发人员的工作量。

prototype内的属性不是自身的属性

console.log(obj.address); // "default address" console.log(obj.hasOwnProperty('name')); // true console.log(obj.hasOwnProperty('age')); // true console.log(obj.hasOwnProperty('address')); // false console.log(obj.hasOwnProperty('add')); // false

Object类

Object是JavaScript是顶层的类。它有以下属性:

  1. protoytpe
  2. __proto__

方法:

  1. assign
  2. create
  3. defineProperties
  4. defineProperty
  5. entries
  6. freeze
  7. fromEntries
  8. getOwnPropertyDescriptor
  9. getOwnPropertyDescriptors
  10. getOwnPropertyNames
  11. getOwnPropertySymbols
  12. getOwnPropertyKeys
  13. getPrototypeOf
  14. hasOwn
  15. is
  16. isExtensible
  17. isFrozen
  18. isSealed
  19. keys
  20. preventExtensions
  21. seal
  22. setPrototypeOf
  23. values

Object.prototype有以下方法:

  1. hasOwnProperty
  2. isPrototypeOf
  3. propertyIsEnumerable
  4. toLocaleString
  5. toString
  6. valueOf

Object.__proto__有getter及setter, 其中,getter调用了Object.getPrototypeOf方法返回其原型。

Object.prototype还有以下传统方法:

  1. __defineGetter
  2. __defineSetter
  3. __lookupGetter
  4. __lookupSetter

参考资源

  1. ECMA262 Objects
  2. Native prototypes
  3. Inheritance and the prototype chain