JS继承浅析

简介

  • JS作为脚本语言,主要是在浏览器中执行阶段性的特定任务,所以有其本身的特点:
    • 一切都是对象,函数(function)也不例外
    • 每个对象都有内置变量constructor和__proto__,constructor指向构造函数,__proto__指向构造函数的原型
  • 后续的不断发展,在很多方面得到了延伸:
    • 模块化(AMD/CMD/CommonJs)
    • MVVM框架(React/Vue/Angular)
    • 前后端适配(Node/Browser)
  • 在推出新的规范ES6的同时,引入了类及继承。本文简要介绍继承,并讨论其在JS中的实现。
    • 继承是面向对象四大特征(抽象、封装、继承、多态)之一,主要为了复用已有功能。使子类可直接拥有或访问,父类允许的成员变量和方法
    • JS的继承方式比较奇特,不像传统的面向对象语言(C++、Java等),而是通过其原型链
    • 一个对象在访问变量或者函数时,首先在类内部查找,其次是父类内部,最后是其.__proto__

实现继承

创建基类

  • 创建一个基类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var Widget = function(){
    // 内部成员变量
    this.messages = [];
    };

    // 方法
    Widget.prototype.push = function(element) {
    this.messages.push(element);
    }
  • 子类SubWidget继承后,可访问父类的变量和方法:

    1
    2
    3
    4
    5
    6
    7
    8
    var sub1 = new SubWidget( 'foo' );
    var sub2 = new SubWidget( 'bar' );

    sub1.messages.push( 'foo' );
    sub2.messages.push( 'bar' );

    console.log(sub1.messages); // ['foo']
    console.log(sub2.messages); // ['bar']

实例继承

1
var SubWidget = new Widget();
  • 优点:SubWidget是一个实例(非函数),可以访问父类及原型的所有变量和方法
  • 缺点:没有prototype属性,并且共享所有元素

原型继承

继承可以分成两部分:父类成员(变量或方法)和prototype成员

1 继承父类成员

1
2
3
4
5
6
var SubWidget = function( name ){
// 继承父类的成员
Widget.call( this, arguments);
// 初始化自有成员
this.name = name;
};

后初始化自有成员变量或方法,可覆盖父类的同名变量或方法。此时,SubWidget.prototype指向空对象

2 继承父类prototype

只要能显示 访问父类prototype中的变量和方法 即可

2.1 原型为父类的实例

1
SubWidget.prototype = new Widget();

子类prototype中有父类的内部变量,且constructor指向父类(而不是子类本身)。

2.2 原型为父类原型

1
SubWidget.prototype = Widget.prototype;

改变子类prototype的同时,会改变父类prototype

2.3 混合

1
2
3
4
5
6
7
function Super(parent) {
var F = function(){};
F.prototype = parent.prototype;
this.prototype = new F();
this.prototype.constructor = this;
}
super.call(SubWidget, Widget);

需要增加一个内部函数

完整实现

结合1和2.3的完整实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function superProtoType(parent) {
var F = function(){};
F.prototype = parent.prototype;
this.prototype = new F();
this.prototype.constructor = this;
}

var SubWidget = (function(){
var constructFunc = function(name) {
Widget.call(this, this.arguments);
this.name = name;
};
superProtoType.call(constructFunc, Widget);

return constructFunc;
})();

是不是类似于ES6实现的继承方式^_^

图1

图1:数字为优先级

继承与实例

  • 函数.prototype.constructor需要指向自身(Code1:函数默认创建时,即指向自身),否则会改变实例的constructor指向

    1
    2
    3
    4
    5
    6
    7
    var testCls = function() {
    this.array = [];
    }
    var inst = new testCls();
    console.log(inst.constructor); // testCls
    console.log(testCls.prototype.constructor); // testCls
    console.log(testCls.prototype); // Object{constructor: function}

    Code1:log中可以看出,.constructor都指向testCls

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var testCls = function() {
    this.array = [];
    }
    testCls.prototype = {
    push: function() {

    }
    };
    var inst = new testCls();
    console.log(inst.constructor); // Object function
    console.log(testCls.prototype.constructor); // Object function
    console.log(testCls.prototype); // Object {push: function}

    Code2:改变.prototype指向后,.constructor都指向改变后的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var testCls = function() {
    this.array = [];
    }
    testCls.prototype = {
    push: function() {

    },
    constructor: testCls
    };
    var inst = new testCls();
    console.log(inst.constructor); // testCls
    console.log(testCls.prototype.constructor); // testCls
    console.log(testCls.prototype); // Object {push: function, constructor: function}

    Code3:与Code1默认行为一致

  • A instanceof B 的判断逻辑为:A.__proto__…__proto__ === B.prototype。所以在图1中,sub1 instanceof SubWidget和Widget均返回true。

  • Function、Object的构造函数为Function,所以Function(Object).__proto__ === Function.prototype

图2红线所示,Function,Object为Function的实例

  • Function.prototype是一个函数,构造函数是Function。但Function.prototype.__proto__ === Object.prototype,由3可替换为Function(Object).__proto__.__proto__ === Object.prototype

图2红线和绿线所示,Function,Object为Object的实例

图2

图2

坚持原创技术分享,您的支持将鼓励我继续创作!