typescript 的 polyfill 学习1-Class 继承篇

Class 继承

js 是多范式的编程语言,同样也是支持面向对象编程的,类 是面向对象中是很重要的概念。
区别于传统的java,c#基于模板的类,js是基于原型的。

类继承一般是通过原型链的方式来实现,在es3时代,可以使用Base.js这个库来进行类编程。

而ES6通过关键字class来定义类,这种语法糖让写法更加清晰,更像传统的类编程,也因此屏蔽了原型链的细节,会减少初学者的困惑,不过也因为这样就失去了理解js本身的语法特性的机会。

下面用ts写的两个类,我们看看转成es5后,js是怎么模拟这个 语法糖的。

代码使用amd进行模块封装:

{
    "compilerOptions": {
        "outDir": "./dist/",
        "sourceMap": true,
        "noImplicitAny": false,
        "module": "amd",
        "target": "es5",
        "jsx": "react"
    },
    "include": [
        "./src/**/*"
    ]
}

ts代码:

 

//基类
export class A {
    
    

    public  Pro1:string  = "pro1";//公共成员
    private fPro1:string  = "fPro1";//私有成员
    
    //私有方法
    private  fMethod1():string{ 
        return "123";
    }
    //保护方法(可继承的方法)
    protected pMethod1():string{
        return "456";
    }
    //公共方法
    public  Method1(){

    }
    
   

  
}

//子类B
export class B  extends A {
    
        public  Pro2:string  ="Pro2";
        private fPro2:string ="fPro2";

        constructor(para:string){
            super();         //构造器重载
            this.fPro2 = para ;
        }

        public  Method2(){
    
        }
        //方法重载
        protected pMethod1():string{
            return  super.pMethod1();
        }
        private  fMethod2():string{
                     return "123";
                }
    }
 

我们定义了基类A,并且定义了 私有变量,保护变量,公共变量,私有方法,保护方法,公共方法

定义了继承类B,同样定义了自己的成员,并且构造器重载 和 保护方法重载

父类的实现

 var A = /** @class */
    (function() {
        function A() {
            this.Pro1 = "pro1";
           
            this.fPro1 = "fPro1";
        }
        A.prototype.fMethod1 = function() {
            return "123";
        }
        ;
        A.prototype.pMethod1 = function() {
            return "456";
        }
        ;
        A.prototype.Method1 = function() {}
        ;
        return A;
    }()); //闭包作用域
    

可以看到,通过闭包的的作用域里面新建构造函数给变量A赋值一个构造函数

构造函数里面对类成员变量进行初始化,并且在 原型对象(显示原型)定义类方法。里面js里面没有访问修饰符,我们看到私有方法和公有方法的定义并没有区别。

子类的实现

var B = /** @class */
    (function(_super) {
        __extends(B, _super);
        function B(para) {
            var _this = _super.call(this) || this;
            _this.Pro2 = "Pro2";
            _this.fPro2 = "fPro2";
            _this.fPro2 = para;
            return _this;
        }
        B.prototype.Method2 = function() {}
        ;
        B.prototype.pMethod1 = function() {
            return _super.prototype.pMethod1.call(this);
        }
        ;
        B.prototype.fMethod2 = function() {
            return "123";
        }
        ;
        return B;
    }(A));
    exports.B = B;

子类定义的闭包函数传入父类(基类)A做为参数_super,同样也是返回构造函数B.
跟父类不同的是,首先通过调用 __extends函数,对B 和 _super 做了处理。根据js
函数提升的特性,此时B 这个构造函数已经被定义,所以__extends函数的调用应该是在最后面。
在讲解__extends函数之前,我们先看看 重载的定义。

首先是构造器的重载。 构造函数通过调用 A.call(this),父类被调用,父类的构造函数被执行。

这里我们顺便复习一下new 操作符的执行原理:

var obj  = {};//我们创建了一个空对象obj

obj.__proto__ = Base.prototype;//我们将这个空对象的__proto__成员指向了Base函数对象prototype成员对象

Base.call(obj);//我们将Base函数对象的this指针替换成obj,然后再调用Base函数,于是我们就给obj对象赋值了一个id成员变量,这个成员变量的值是”base”,关于call函数的用法。

这样我们就知道,当我们new B()的时候,就开始执行B构造函数里面的代码.
在这里,__extends做了什么处理呢,我们来分析一下:

__extends 实现

var __extends = (this && this.__extends) || (function() {
    var extendStatics = Object.setPrototypeOf || ({
        __proto__: []
    }instanceof Array && function(d, b) {
        d.__proto__ = b;
    }
    ) || function(d, b) {
        for (var p in b)
            if (b.hasOwnProperty(p))
                d[p] = b[p];
    }
    ;
    return function(d, b) { //d:父类 b:子类
        extendStatics(d, b);
        function __() {
            this.constructor = d;
        }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype,
        new __());
    }
    ;
})();

同样的,函数都是在闭包里面定义的。__extends(B, _super);得知,我们传入父类和子类函数。

首先通过 extendStatics 函数的处理。extendStatics函数这边先不介绍,最后再说。

因为:

new B().__ proto__ === B.prototype

通过上面构造函数的代码里面知道,类成员都是在构造函数里面定义的,其中
方法成员全部定义在prototype里面给所有成员共享。

对于类实例来说,也就是new B() 出来的对象来说,方法成员要全部存放在__ proto__里面的,但是成员方法的来源并不一样,有的是来自于子类,有的来自于
父类,甚至来自与父父类...
如果我们通过原型链来实现的话,很自然的我们希望,每一级的父类对应原型链上的每一级。

  也就是说我们希望:


          new B().__ proto__             存放B定义的所有方法成员 

          new B().__ proto__.__ proto__  存放A定义的所有方法成员
          .......

左边是继承链,右边是继承链

我们可能会这么做, B.prototype = new A(), 这个时候new B()的原型对象上的确存在了A和B 的所有成员方法,但是至少有两个问题:

  1. 原型对象上 还放着变量成员,变量成员不应该被共享

  2. 第一级的原型对象上面应该只存放子类B的成员方法

我们看看他们是怎么解决的,既然直接实例A不行,那就是new 一个空的对象。

d.prototype =  new __();

为了保证该空对象有原型功能,我们这么做。

function __() {
            this.constructor = d;
        }
       

然后

__.prototype = b.prototype

这样就保证了 new B().__ proto__. proto 存放A定义的所以方法成员

很完美,我们看看整体代码:

d:父类 A  b:子类 B
function __() {
            this.constructor = d;
        }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype,
        new __());

静态成员

我们知道,传统面向对象编程语言 静态成员可以通过 static 指定,静态成员跟方法成员一样,可以被共享,typescript也是有static 关键字的, 最终也是定义在 原型对象上面。

extendStatics(d, b); // 通过命名我们也知道,这个是为了解决静态成员的
var extendStatics = Object.setPrototypeOf || ({
        __proto__: []
    }instanceof Array && function(d, b) {
        d.__proto__ = b;
    }
    ) || function(d, b) {
        for (var p in b)
            if (b.hasOwnProperty(p))
                d[p] = b[p];
    }
    ;

这个比较简单 ,通过代码我们得知,这个其实就是Object.setPrototypeOf函数及其等价的代码。

Object.setPrototypeOf 定义:

将一个指定的对象的原型设置为另一个对象或者null(既对象的[[Prototype]]内部属性).
B.__ proto__ = A , 为什么需要等价代码呢,是因为 proto 并不是标准(虽然每个浏览器都支持了)。

好了,我们看一下一个子类实例的效果:

436695-20171017215325240-1029447690.png

转载于:https://www.cnblogs.com/lusess/p/7684140.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值