Javascript系统复习:Javascript中的面向对象(OOP)之继承和原型

Posted by mieruko on 2016-08-29

前言:

Javascript中的OOP,其实不止Javascript中有OOP,面向对象思想在很多语言中都有体现。
先看一下面向对象的定义:

面向对象程序设计,是一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性,灵活性和扩展性。

面向对象的几个重点思想:

  • 继承
  • 封装
  • 多态
  • 抽象

这些思想在Javascript中都可以体现。

继承

基于原型的继承

根据一个构造函数生成的对象会继承这个构造函数原型链上的内容。
🌰:

1
2
3
4
5
6
7
8
9
function Foo() {
this.y = 2;
}
typeof Foo.prototype; //"object"
Foo.prototype.x = 1;
var obj3 = new Foo();

obj3.y; //2
obj3.x; //1

那么我们此时来关注一下Foo.prototype这个东西。
当我们写出一个空函数的时候,这个函数自动拥有一个prototype属性,并且默认拥有两个属性:

  • constructor
  • __proto__

如何使一个class继承另一个class?

🌰:

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
33
34
35
36
37
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.LES_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;
}

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

Student.prototype.hi = function() {
console.log("Hi my name is " + this.name + "years old now, and from" + this.className + ".");
}

Student.prototype.learn = function(subject) {
console.log(this.name + "is learning" + subject + "at" + this.className + ".");
}

//test
var wanzi = new Student("wanzi",20,"class3,grade2");
wanzi.hi(); // Hi, my name is Wanzi, I'm 20 years old now,and from class 3, grade2
wanzi.LEGS_NUM; //2
wanzi.walk(); // wanzi is walking
wanzi.learn("math"); // wanzi is learning math at class3, grade2

唔。。。。感觉好像没有什么可以用语言描述的,我认为有必要记住这个套路。

原型链

原型和原型之间组成的链条就是原型链,原型链是由__proto__这个属性决定的。
我们在研究原型链的时候,是不断地查找每个prototype中的__proto__来找出它的上一个原型~~
当我们使用instanecof这个运算符来判断一个实例属于哪个类的时候,我们用的也是原型链,即__proto__组成的那个链条。
楼上那段代码,我们在调用hi的时候,发生了啥?
它首先看这个对象上有没有hi方法,没有,于是它查找__proto__,根据这个属性找到了构造函数的原型,发现原型上有,于是调用这个。
类似地,ARMS_NUM,LEGS_NUM,也都是这种套路。

找到原型的方法

对于一个对象来说,我们有以下方法找到它的原型:

用__proto__属性来查找

1
2
3
var obj = {x:1};
obj.x; //1
obj.__proto__; // Object{}

用Object.getPrototypeOf来查找

1
2
3
var obj = {x:1};
obj.x; //1
Object.getPrototypeOf(obj); // {} (指向的还是Object的原型对象)

并不是所有的对象原型链上都有Object.prototype

🌰:

1
2
var obj2 = Object.create(null);
obj2.__proto__; //undefined

这个对象是没有原型的。

并不是所有的函数都有prototype属性

🌰:

1
2
3
4
5
function abc() {

}
var binded = abc.bind(null);
binded.prototype; // undefined

prototype属性更进一步

注意,当我们修改prototype的属性值的时候,会影响已经创建的实例。
如果直接改动prototype这个东西,也就是重新给prototype赋值,是不会影响已经创建的实例的。
🌰(接第一段长代码):

1
2
3
4
5
6
Student.prototype.x = 101;
wanzi.x; //101

Student.prototype = {y:2};
wanzi.y; //undefined
wanzi.x; //101

Object.prototype是原型链的末端,它的\_proto__指向null。

基于原型链的instanceof运算符

instanceof左边是一个对象,右边是一个构造器,它会判断右边的prototype属性是否出现在左边的原型链上。
🌰(接在第一大段代码后面):

1
2
3
wanzi instanceof Person; //true
wanzi instanecof Student; //true
wanzi instanceof Object; //true

这些true,true,true就是因为左边的原型链上有右边的prototype。

实现继承的方式

用代码来说明:

1
2
3
4
5
6
7
8
9
10
function Person() {

}
function Student() {

}
Student.prototype = Person.prototype; //1
Student.prototype = new Person(); //2
Student.prototype = Object.create(Person.prototype); //3
Student.prototype.constructor = Person;

第一种方法是不行的,改变了Student.prototype的同时也会改变Person,这样是不行的。
方法2存在一个问题是你如果给Person传了参数就会很奇怪。
方法3相对来说是比较合适的。
不过注意,Object.create方法是ES5之后才有,对于ES5之前,我写过另一篇文章来模拟一个类似的方法实现继承的,在这里:实现一个正确的长长的原型链

总结

这篇文章写完,感觉自己的脑袋清醒了很多。
原型链继承这一块,我是先看了廖雪峰老师的教程,然后看的书,觉得它们之间配合起来,对我的理解很有帮助。
总之,很开心,嘿嘿嘿~~