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可以查看这里
小结
这篇文章是用五笔打出来的……