JavaScript中的继承方式

Author Avatar
GeniusFunny 2月 03, 2018
  • 在其它设备中阅读本文章

引入

继承是OOP的核心内容之一。JavaScript中常见继承方式为:类式继承、构造函数式继承、组合式继承、原型式继承、寄生式继承、寄生组合式继承。

类式继承

类式继承,通过子类的原型prototype对父类的实例化实现。由于子类通过其原型prototype对父类实例化继承了父类,所以父类中的共有属性要是引用类型就会在子类中被所有实例共用。由于子类实现的继承是靠其原型prototype对父类的实例化实现的,因此在创建父类时是无法向父类传递参数的,因而在实例化父类时无法对父类构造函数内的属性进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//声明父类
function SuperClass() {
this.superValue = true;
}
//为父类添加共有方法
SuperClass.prototype.getsuperValue = function() {
return this.superValue;
}
//声明子类
function SubClass() {
this.subValue = false;
}
//继承父类(
SubClass.prototype = new SuperClass();
//为子类添加共有方法
SubClass.prototype.getsubValue = function () {
return this.subValue;
}

构造函数式继承

构造函数继承,通过在子类的构造函数作用环境中执行一次父类的构造函数来实现;由于没用涉及prototype,所以父类的原型方法不会被子类继承。要想被子类继承,就得把属性和函数放到父类的构造函数中,并且创建出来的(子类的)实例都会单独拥有一份,违背了 “代码复用”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//声明父类
function SuperClass(id) {
//引用类型共有属性
this.books = ['JavaScript', 'html', 'css'];
//值类型共有属性
this.id = id;
}
//父类声明原型方法
SuperClass.prototype.showBooks = function() {
console.log(this.books);
}
//声明子类
function SubClass(id) {
//继承父类
SuperClass.call(this, id); //将子类的变量在父类中都执行一遍,父类中是给this绑定属性的,所以通过call就使得子类继承了父类的共有属性。
}

组合继承

组合继承,将类式继承和构造函数式继承综合,构造函数继承时执行了一遍父类的构造函数,在实现子类原型的类式继承又调用一遍父类构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function SuperClass(name) {
this.name = name;
this.books = ["javascript", "html", "css"];
}
SuperClass.prototype.getName = function () {
return this.name;
}
function SubClass(name, time) {
//构造函数式基础父类name属性
SuperClass.call(this, name);
//子类新增共有属性
this.time = time;
}
//类式继承 子类原型继承父类
SubClass.prototype = new SuperClass();
//子类原型方法
SubClass.prototype.getTime = function () {
return this.time;
}

原型式继承

原型式继承,借助原型prototype可以根据已有的对象创建一个新的对象,同时不必创建新的自定义对象类型。

1
2
3
4
5
6
7
8
9
//对类式继承的封装
function inheritObejct(o) {
//声明一个过渡函数对象
function F() {}
//过渡对象的原型继承父对象
F.prototype = o;
//返回过渡对象的一个实例, 该对象的原型继承了父对象
return new F();
}

基于这种在对象之间直接构建继承关系的理念,DC大神给出了上述的函数,这个inheritObject函数后来被ES5采纳,更名为Object.create()。

寄生式继承

寄生式继承,对原型继承的第二次封装,并且在此过程中对继承的对象进行了拓展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//声明基对象
let book = {
name: 'js book',
alikeBook: ["css book", "html book","html book"]
};
function createBook(obj) {
//通过原型继承方式创建新对象
let o = new inheritObejct(obj);
//拓展新对象
o.getName = function() {
return name;
}
//返回拓展后的新对象
return o;
}

寄生组合式继承

这里的寄生继承处理的是类的原型

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
function inhreitPrototype(SubClass, SuperClass) {
//复制一份父类的原型副本保存在变量中
let p = inheritObejct(SuperClass.prototype);
//修正因为重写子类原型导致子类的constructor属性被修改
p.constructor = SubClass;
//设置子类的原型
SubClass.prototype = p;
}
//实例
//定义父类
function SuperClass(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
//定义父类的原型方法
SuperClass.prototype.getName = function() {
return this.name;
}
//定义子类
function SubClass(name, time) {
//构造函数式继承
SuperClass.call(this,name);
//子类新增属性
this.time = time;
}
/*寄生式继承父类原型*/
inhreitPrototype(SubClass, SuperClass);
/*子类新增原型方法*/
SubClass.prototype.getTime = function () {
return this.time;
}

extend与deepCopy

在JavaScript中继承是依赖于原型prototype链实现的,只有一条原型链,理论上不能继承多个父类。但是由于JavaScript是一门很灵活的语言,所以我们可是自己动手实现一个继承多对象属性的函数。

原型链

定义

JavaScript中每个函数都有一个指向某一对象的prototype属性,该函数被new操作符调用时会创建并返回一个对象,返回的对象中有一个指向其原型对象的proto属性,引擎在背后通过运用proto可以使得新建的对象可以调用相关原型的对象的方法和属性。原型对象本身也是对象,所以本身也包含了指向其原型的proto,由此就形成了一条链,称之为原型链。

作用


如图,有了原型链,如果某个属性在对象B中而不在对象A中,我们依然可以把这个属性当作A的属性来访问。这就是继承的作用,它可以使得每个对象都能访问其继承链上的任何属性。

继承单对象属性的extend方法(针对值类型的属性)

1
2
3
4
5
6
function extend(child, parent) {
for (let prop in parent) {
child[prop] = parent[prop];
}
return child;
}

继承多对象的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
function mix() {
let i = 1,
len = arguments.length,
child = arguments[0],
arg;
for(; i < len; i++) {
arg = arguments[i];
for( let prop in arg) {
child[prop] = arg[prop];
}
}
return child;
}

深拷贝

在JavaScript中复制一个对象的属性时,如果对象的属性为值类型则只需要简单的赋值语句即可,如果属性为引用类型,则需要依次复制该属性的属性,这里采用递归的思想解决这个问题。(如果不使用递归,则通过JSON解析解决)

为什么要这么做?

当创建一个对象时,这个对象就被存储在内存中的某个物理位置,相关的变量和属性就会指向这些位置。

  1. 创建一个新对象,并让变量A指向该对象。
  2. 创建一个新变量B,并设置其与A相等,此时B和A指向了同一个对象,也就是内存中的同一个地址。
  3. 修改变量B所指的对象的color属性,将它设置为”white”,此时A.color 的值也为”white”。
  4. 再创建一个对象,然后让变量B指向这个新对象,A和B此时指向了内存中不同的位置,A、B从此再无关联。

    具体实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function deepCopy(parent, child) {
    child = child || {};
    for (let prop in parent) {
    if (parent.hasOwnProperty(prop)) {
    if (typeof parent[prop] === 'object') {
    child[i] = Array.isArray(parent[prop])? [] : {};
    deepCopy( parent[prop], child);
    } else {
    child[prop] = parent[prop];
    }
    }
    }
    return child;
    }

总结




关于

参考文献:
《JavaScript面向对象编程指南》
《JavaScript设计模式》
《你不知道的JavaScript(上卷)》