JavaScript没有传统面向对象语言的类继承机制,而是基于原型链继承实现的,其本质是使用函数模拟类的特征。我们可以通过prototype将属性写到原型链上,调用new操作符创建对象(实例化)时,对象实例会把类原型链上的属性关联到自身的__proto__属性上;而子类继承父类时,是将子类的prototype属性指向父类的prototype属性,并在子类prototype属性添加自己的方法和属性实现对父类的扩展。
1. 类与实例
1.1 类定义
在JavaScript中我们会像下面这样模拟一个类:
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
Person.prototype.sayName = function() {
console.log(this.name);
}
该类包含两个属性和一个方法。
1.2 实例化
类定义后,就可以通过new关键创建类实例:
var person = new Person('王二小', '女');
person.__proto__ === Person.prototype; // true
JavaScript中实例化不同与传统面向对象语言(如:Java、C++等),其实例化基于对象原型。
__proto__是一个对象内部属性,继承自Object.prototype.__proto__。当类被实例化时,对象的(类实例)的__proto__属性会指向类的原型,即:类的prototype属性。
这就是基于原型的类的实现方式,也就是原型链的实现方式。实例化后当调用对象的属性或方法时,会有如下过程:
- 查找对象是否有该属性或方法,如果则在则返回或调用
- 如果不存在,则通过
__proto__属性,在原型链上查找有没有属性或方法
2. 类的继承
继承、封装、多态是面向对象语言三大特征,JavaScript基于原型同样可以模拟出这三个特性。单就继承来说又分为单继承和多继承,但JavaScript只能实现单继承。
我可以像下面这样模拟一类继承:
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
Person.prototype.sayName = function() {
console.log(this.name + ',我是一个人');
}
// 等同于Object.create的方法
function object(prop) {
var F = function () {};
F.prototype = prop;
return new F();
}
function Student(name, sex) {
Person.call(this, name, sex);
}
Student.prototype = object(Person.prototype)
Student.prototype.constructor = Student;
Student.prototype.work = function() {
console.log('我的工作是学习');
}
在这个继承过程中,我们做了以下几件事:
- 在子类的构造函数中调用父类的构造函数
- 将父类的原型属性
prototype复制到子类的原型prototype中 - 将子类的构造器
constructor指向子类的构造函数
JavaScript中继承的本质是原型链的复制,创建子类的实例后,其__proto__属性会指定子类的prototype,但它同时是一个父类的实例。
student.__proto__ === Person.prototype; // false student.__proto__ === Student.prototype; // true student instanceof Student; // true student instanceof Person; // true
仍然可以通过__proto__.__proto__访问父类中的方法:
student.__proto__.__proto__.sayName(); // undefined,我是一个人
除上述的自定义object方法外,还可以使用Object.create()实现类原型的复制。无论使用哪种方式,这只对面向对象继承的一种模拟,其与传统的类的实例化与继承相比有以下几个特点:
- 在传统的类中,“类名”同进是一个构造函数,而JavaScript使用函数模拟了构造函数
- 在传统的类中有可以被继承到子类中的属性和方法,而而JavaScript使用
prototype实现了继承 - 传统的类通过
new关键字来实例化一个类,而JavaScript的实例化过程是对prototype属性的复制
注意:在ECMAScript 2015(ES6)中新增了class类定义的方式,但其本质仍然是基于函数的类模拟。
