Javascript OOP 继承与原型链详解

什么是OOP?

面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程范型,同时也是一种程序开发的方法。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。

原型

对于那些熟悉基于类的面向对象语言(Java 或者 C++)的开发者来说,JavaScript 的语法是比较怪异的,这是由于 JavaScript 是一门动态语言,而且它没有类的概念( ES6 新增了class 关键字,但只是语法糖,JavaScript 仍旧是基于原型)。

涉及到继承这一块,Javascript 只有一种结构,那就是:对象。在 javaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)。

虽然,原型继承经常被视作 JavaScript 的一个弱点,但事实上,原型继承模型比经典的继承模型更强大。举例来说,在原型继承模型的基础之上建立一个经典的继承模型是相当容易的。

__proto__

__proto__是对象的一个内部隐藏属性,它的值是该对象的原型。
__proto__属性可用于获取对象的原型。

1
2
3
4
var obj = {x:1};//var obj = new Object();obj.x = 1;
obj
obj.__proto__ === Object.prototype;
Object.getPrototypeOf(obj) === Object.prototype

从 ECMAScript 6 开始,Object.getProtoypeOf(obj)方法返回指定对象的原型(也就是该对象内部属性[[prototype]]的值),Object.setProtoypeOf(obj, prototype)将一个指定的对象的原型设置为另一个对象或者null(既对象的[[Prototype]]内部属性).

obj.jpg

构造函数的prototype属性

JavaScript 不包含传统的类继承模型,而是使用 prototype 原型模型。

1
2
function Foo(){}
Foo.prototype
Foo.jpg

我们创建的每一个函数都会有prototype预设属性,Foo.prototype是一个对象。Foo.protoytype结构:

1
2
3
4
5
Foo.prototype
{
constructor: function Foo(),
__proto__: Object.prototype
}

通常,我们说Foo.prototype就是Foo的原型。Object构造函数用来创建对象。

我们看到,Foo.prototype中包含construtor属性和__proto__属性,contructor就是构造函数Foo本身,__proto__属性指向对象原型。

对象原型Object.prototype包含了一些方法如 tostring, valueOf

Object.prototype.jpg

如何为Foo的原型添加属性

添加属性方法只需

1
2
3
Foo.prototype.sayHi=function(){
console.log('Hi');
}

这样所有构造函数Foo new出来的实例都会有sayHi属性

构造函数Foo与new一个实例

1
2
3
4
5
6
7
8
9
function Foo(){
this.x = 1;
}
var foo = new Foo();
Foo.prototype.sayHi=function(){
console.log('Hi');
}
foo.x; //1
foo.sayHi(); //Hi
foo.sayHi.jpg

实例foo的原型就是Foo.prototype
foo.proto.jpg

原型链

一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.hi=function(){
console.log('Hi,my name is',this.name,",I'm",this.age,'years old now.')
};
Person.prototype.LEGS_NUM = 2;
Person.prototype.ARMS_NUM = 2;
Person.prototype.walk = function(){
console.log(this.name,'is walking..');
};
function Student(name,age,className){
Person.call(this,name,age);
this.className = className;
}
/*使用Object.create一个新的对象,如果Student.prototype=Person.prototype,会导致Student.prototype增加属性时,Person.prototype也增加*/
Student.prototype = Object.create(Person.prototype);
/*上一步时Student.prototype的constructor变为了Person,这里要重新设置为Student*/
Student.prototype.constructor = Student;
Student.prototype.hi = function(){
console.log('Hi,my name is',this.name,",I'm",this.age,'years old now,and from',this.className,'.');
};
Student.prototype.learn = function(subject){
console.log(this.name,'is learning',subject,'at',this.className,'.');
};
//test
var bson = new Student('bson',27,'Class 3,Grade 2');
bson.hi(); //Hi,my name is bson,I'm 27 years old now,and from Class3 Grade 2.
console.log(bson.LEGS_NUM); //2
bson.walk(); //bson is walking..
bson.learn('math'); //bson is learning math at Class 3 Grade 2.

我们看下以上代码的流程,首先构造函数Person并给其原型添加一系列属性(如hi),然后构造函数Student,并将Foo的属性应用到Student,再通过

1
2
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

继承Person的原型。
Object.create在ECMAScript 5才有,之前的版本,我们可以自行构造这个方法

1
2
3
4
5
6
7
if(!Object.create){
Object.create = function(proto){
function F(){};
F.prototype = proto;
return new F();
}
}

oop_test.jpg

根据控制台信息,很容易看出实例 bson 通过__proto__指向 Student.prototype ,而Student.prototype 通过__proto__指向 Person.prototype ,Person.prototype 通过__proto__指向 Object.prototype 。这就是一条原型链。原型链上的所有方法 bson都可以调用。
proto.jpg