面向对象

  • 类与实例
    • 类的声明
    • 生成实例
  • 类与继承
    • 如何实现继承
    • 继承的几种方式

一. 类与实例

JavaScript的所有数据都可以看成对象(一切皆为对象)。那是不是我们已经在使用面向对象编程了呢?

当然不是。如果我们只使用NumberArraystring以及基本的{...}定义的对象,还无法发挥出面向对象编程的威力。

JavaScript 的ES6标准还没出来之前是不区分类和实例的概念,而是通过原型(prototype)(模拟类的)来实现面向对象编程。

1.类的声明

1
2
3
4
5
6
7
8
9
10
11
// ES5的声明
function Animal(){
this.name = "root";
}

// ES6中的Class关键字的声明
class Animal2{
constructor(){
this.name = "root";
}
}

2.生成实例

通过new 关键字来实例化上面的声明

1
console.log(new Animal(), new Animal2());

类

二. 类与继承

1.如何实现继承

js中实现继承的方式主要是通过原型链完成的。

JavaScript的原型继承实现方式就是:

  1. 定义新的构造函数,并在内部用call()调用希望“继承”的构造函数,并绑定this
  2. 借助中间函数F实现原型链继承,最好通过封装的inherits函数完成;
  3. 继续在新的构造函数的原型上定义新方法。

2.继承的几种方式

1.借助构造函数实现继承

核心: 使用父类的构造函数(Parent1.prototype.constructor)来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)(只实现了部分继承,如果父类的属性都在构造函数上,那没有问题;如果父类的原型对象上还有方法,那子类是拿不到这些方法的)

缺点: 方法都在构造函数中定义, 只能继承父类的实例属性和方法,不能继承原型属性/方法,无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。

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
function Parent1 (name) {
this.name = name;
this.friends = ['小李','小红'];
this.getName = function () {
return this.name;
}
};

// Parent1.prototype.geSex = function () { //这里对原型进行扩展的方法就无法复用了
// console.log("男");
// };

function Child1 (age) {
Parent1.call(this,'老明');  //这一句是核心关键
//这样就会在新Child1对象上执行Parent1构造函数中定义的所有对象初始化代码,
// 结果Child1的每个实例都会具有自己的friends属性的副本
this.age = age;
};

var result = new Child1(23);
console.log(result.name);    //老明
console.log(result.friends);  //["小李", "小红"]
console.log(result.getName());  //老明
console.log(result.age);    //23
console.log(result.getSex());  //这个会报错,调用不到父原型上面扩展的方法

2.借助原型链实现继承

弥补构造函数实现继承不足

核心: 将父类的实例作为子类的原型

缺点: 在一个类上实例了两个对象,改第一个对象的属性,第二个对象也跟着改变; 引起这个问题的原因:原型链上的原型对象是共用的;

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
function Parent2 (name) {
this.name = name;
};

Parent2.prototype.getName = function () { //对原型进行扩展
return this.name;
};

function Child2 (age) {
this.age = age;
};

Child2.prototype = new Parent2('老明'); //这一句是关键 //通过构造器函数创建出一个新对象,把老对象的东西都拿过来。

Child2.prototype.getAge = function () {
return this.age;
};

/* 改第子类的属性,父类也跟着改变 */
// Child2.prototype.getName = function () { //这里可以重写从父类继承来的方法,且会优先调用自己的。
// console.log(222);
// };

var result = new Child2(22);
console.log(result.getName()); //老明  //调用了从Parent2原型中继承来的方法(继承到了当前对象的原型中)  
console.log(result.getAge()); //22   //调用了从Child2原型中扩展来的方法

3.组合方式

组合继承(所有的实例都能拥有自己的属性,并且可以使用相同的方法,组合继承避免了原型链和借用构造函数的缺陷,结合了两个的优点,是最常用的继承方式)

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后再通过将父类实例作为子类原型,实现函数复用

缺点:调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

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
function Parent3 (name) {
this.name = name;
this.friends = ['小李','小红'];
};

Parent3.prototype.getName = function () {
return this.name;
};

function Child3 (age) {
Parent3.call(this,'老明');  //这一步很关键
this.age = age;
};

Child3.prototype = new Parent3('老明');  //这一步也很关键
var result = new Child3(24);
console.log(result.name);    //老明
result.friends.push("小白");   // 增加数组值
console.log(result.friends);  //['小李','小红','小智']
console.log(result.getName());  //老明
console.log(result.age);    //24

var result1 = new Child3(25); //通过借用构造函数都有自己的属性,通过原型享用公共的方法
console.log(result1.name);  //老明
console.log(result1.friends);  //['小李','小红']

4.寄生组合继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

缺点:堪称完美,但实现较为复杂

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
function Parent4(name) {
this.name = name;
this.friends = ['小李','小红'];
}

Parent4.prototype.getName = function () {
return this.name;
};

function Child4(age) {
Parent4.call(this,"小明");
this.age = age;
}

(function () {
var Super = function () {}; // 创建一个没有实例方法的类
Super.prototype = Parent4.prototype;
Child4.prototype = new Super(); //将实例作为子类的原型
})();

var result1 = new Child4(23); // Child4 {name: "小明", friends: Array(2), age: 23}
console.log(result1.name); // 小明
result1.friends.push('小白');
console.log(result1.friends); // ["小李", "小红", "小白"]
console.log(result1.getName()); // 小明
console.log(result1.age); //23

var result2 = new Parent4('root'); // Parent4 {name: "root", friends: Array(2)}