JavaScript面向对象
在阅读本文前,我假设你已了解以下知识:(如果不清楚的,请自行查阅相关资料):
- 对象
- 函数
- Chrome调试工具
引入
函数除了拿来调用外,还可以创建新的类型:
function People(name) {
this.name = name;
this.getName = function() {
return this.name;
}
}
var p = new People("Jack");
console.log(p instanceof People); // => true
console.log(p.getName()); // => "Jack"
那么,这个对象里都有什么呢?我们可以把它打印出来看看:

可以看到,除了我们放进去的name、getName,还有一个__proto__。它是什么,我先卖个关子。我们先看看“instanceof”,和其他语言类似:
var p = new People("Jack");
console.log(p instanceof People); // => true
console.log(p instanceof Object); // => true
我们可以笼统地认为,People继承于Object。不过,JS中的instanceof其实没有那么简单,MDN上是这样写的:The instanceof operator tests whether the prototype of a constructor appears anywhere in the prototype chain of an object.
接下来我们先看看prototype是什么,将上面的People打印一下:

它其实就是一个普普通通的对象,那么,它和new出来的对象(上面的p)有什么关系?可以用这张图表示:

这张图是什么意思呢?在内存中,People.prototype和它实例化出的p.__proto__是同一个对象。在JS中,所有对象都是Object的子类,所以这个对象同时也是Object的一个实例(相当于new Object)。
这有什么意义?我们知道,JavaScript中会这样寻找属性、方法:
- 在自己身上寻找
- 在自己的
__proto__上寻找 - 在
__proto__的__proto__上寻找……
所以,我们可以这样写:
function People(name) {
this.name = name;
}
People.prototype.getName = function() {
return this.name;
}
var p = new People("Jack");
console.log(p.getName());
var p2 = new People("Jason");
console.log(p2.getName());
这样一来,当你运行p.getName()的时候,JS首先会在p自己身上查找,因为自己身上没有找到,就会到自己的原型——People.prototype上面去查找。
再举个例子,我们从文档中可以查到hasOwnprototype的方法,MDN上有这样的描述:
所有继承了 Object 的对象都会继承到 hasOwnprototype 方法。这个方法可以用来检测一个对象是否含有特定的自身属性;
如果我们运行p.hasOwnprototype("name"),JavaScript怎么找到hasOwnprototype的呢?它会:
- 在p自己身上寻找——没找到
- 在自己的
__proto__,也就是People.prototype上寻找——没找到 - 在
People.prototype的__proto__上寻找。因为People.prototype是一个对象,直接父类是Object。所以,也就是Object.prototype上寻找
可以画出这样的示意图:

这就是为什么被称为“原型链”:原型(prototype)被一个一个地,“串”在了一条“链子”上面。
继承
上面介绍的内容其实已经非常像继承了。我们来实现一下。
我们要做这几件事:
- 使其能找到相应的方法
- 使其有正确的类型(instanceof)
- 使其可以正常的实例化
- 使其各方面都正常工作
如何使其能找到相应的方法,我们在上面已经说过了。接下来我们说说第二项:使其有正确的类型。
假设系统中有People(人)、Student(学生)、CollegeStudent(大学生)三个类:
function People(name) {
this.name = name;
}
People.prototype.getName = function() {
return this.name;
}
function Student() {
}
function CollegeStudent() {
}
我们要做的事有:
- 让
CollegeStudent.prototype.__proto__是Student.prototype - 让
Student.prototype.__proto__是People.prototype
因此,我们编写如下代码:(Object.create的作用见此)
function People(name) {
this.name = name;
}
People.prototype.getName = function() {
return this.name;
}
function Student() {
}
Student.prototype = Object.create(People.prototype);
function CollegeStudent() {
}
CollegeStudent.prototype = Object.create(Student.prototype);
这样它们就有了正确的类型:
var p = new CollegeStudent(); console.log(p instanceof CollegeStudent); // => true console.log(p instanceof Student); // => true console.log(p instanceof People); // => true
现在我们看看第三步:使其可以正常的实例化。在其他语言中,我们会这样写:
class People {
private String name;
public People(String name) {
this.name = name;
}
}
class Student extends People {
public Student(String name) {
super(name); // 这里
}
}
class People {
private $name;
public function __construct($name) {
$this->name = $name;
}
}
class Student extends People {
public function __construct($name) {
parent::__construct($name); // 这里
}
}
在JavaScript中也需要做类似的事。XXX.call的作用是改变其内部的this指向,例如下面我们在Student函数中调用了People.call(this, name),则在运行People函数的时候,People函数内部的this实际上是Student的实例。(相关文档)
function People(name) {
this.name = name;
}
People.prototype.getName = function() {
return this.name;
}
function Student(name) {
People.call(this, name);
}
Student.prototype = Object.create(People.prototype);
function CollegeStudent(name) {
Student.call(this, name);
}
CollegeStudent.prototype = Object.create(Student.prototype);
最后,我们要恢复constructor:
function People(name) {
this.name = name;
}
People.prototype.getName = function() {
return this.name;
}
function Student(name) {
People.call(this, name);
}
Student.prototype = Object.create(People.prototype);
Student.prototype.constructor = Student;
function CollegeStudent(name) {
Student.call(this, name);
}
CollegeStudent.prototype = Object.create(Student.prototype);
CollegeStudent.prototype.constructor = CollegeStudent;
完整Demo可以查看这里
ES6
如果你看完了上面的“长篇大论”,你一定会觉得这太麻烦了。所以,ES6中,可以很轻松的实现继承:
class People {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
class Student extends People {
constructor(name) {
super(name);
this.isStudent = true;
}
}
class CollegeStudent extends Student {
constructor(name, department) {
super(name);
this.department = department;
}
}
不过,它只是一个“语法糖”。我们可以打印出来看看:

总的来说,它实际上做了两件事:方法上浮(至__proto__),属性下沉(至对象)
完整Demo可以查看这里
小结
这篇文章是用五笔打出来的……