怎么又忽然想起写JavaScript原型继承这个基础的内容。最近在写一个小程序,要从底层实现继承,突然发现我对这么基础知识的记忆模糊起来。于是迅速找文章来读,才又重新拾起,现在想把相关的内容通过图形化的方式记录在自己的博客上,保不齐过一段时间我又忘了呢。
构造函数
在JavaScript中,一个普通的函数即可充当构造函数:
function Car() { }
上例中 Car
是一个函数的实例,它有一个prototype属性,可以在该属性上添加方法:
Car.prototype.move = function () { };
通过 new
关键字即可创建一个 Car
的实例:
var myCar = new Car();
并且可以调用 move
方法:
myCar.move();
我们都知道 move
方法并不是位于 myCar
这个对象上,而是在 myCar
对象的原型链上。myCar
对象有个 __proto__
属性,该属性指向 Car.prototype
。当调用 myCar.move()
时,JavaScript 引擎首先看方法是否存在于 myCar
对象,如果不存在则再看是否存在于 myCar.__proto__
上,由于 myCar.__proto__
与 Car.prototype
指向同一个对象,由此便可以调用到 move()
方法。下面的图描述了这样的关系:
在 JavaScript 中,还有一个 instanceof 操作符,通过它可以知道某个对象是否是某个构造函数的实例:
myCar instanceof Car; // true
当调用 instanceof
操作符时,JavaScript 引擎会判断 myCar
的 __proto__
是否与 Car.prototype
指向同一个对象,如果是则返回 true
。
原型继承
接下来我们看如何通过原型的方式实现面向对象的继承:
function Sedan() { Car.call(this); }
插入解释一下什么是 Sedan:
A sedan is a car with seats for four or more people, a fixed roof, and a boot that is separate from the part of the car that you sit in.
在 Sedan
构造函数中,通过 Car.call(this)
实现了在子类的构造函数中调用父类构造函数的方法。接下来我们修改 Sedan.prototype
对象为一个 Car
的实例:
Sedan.prototype = new Car();
接下来可以实例化一个 Sedan
对象,并调用 move
方法:
var mySedan = new Sedan(); mySedan.move();
下图描述了继承关系:
Car.prototype
对象还有一个 constructor
属性,它指向 Car
本身:
Car.prototype.constructor === Car; // true
显然在第一个例子中,下面的结果是 true
:
myCar.constructor === Car; // true
通过原型链(__proto__
属性),访问 myCar.constructor
的时候实际上就是访问了原型对象的 constructor
属性,也就是 Car.prototype.constructor
。
但是你会发现 mySedan 的 constructor 也成为 Car 了。显然是因为 mySedan 原型链被修改到了 Car 的实例上,顺着原型链就会最终访问 Car.prototype.constructor。因此在实际使用中,要把修改的 constructor 改回来:
var originalConstructor = Sedan.prototype.constructor; Sedan.prototype = new Car(); Sedan.prototype.constructor = originalConstructor;
最终的关系图如下所示:
留言