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 并不是标准(虽然每个浏览器都支持了)。

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

typescript 的 polyfill 学习1-Class 继承篇的更多相关文章

  1. c++学习笔记之继承篇

    title: c++学习笔记之继承篇 date: 2017-03-26 16:36:33 tags: [c++,继承,public,virtual,private,protected] categor ...

  2. typescript 的 polyfill 学习

    我们知道typescript 是ES 超集.这意味着,不仅仅ES 的各种语法特性都会包括,还能保证通过typescript的编译服务可以很方便的转成ES向下兼容的版本,这得意于typescript强大 ...

  3. Java菜鸟学习笔记(23)--继承篇(二):继承与组合

    组合是什么 1.继承和组合都是一种随思想渗透而下的编码方式,其根本目的都是为了复用类,减少重复代码 2.要实现一个类的复用,可以分为组合语法和继承语法 3.组合就是通过将一个对象置于一个新类中,将其作 ...

  4. 8-C++远征之继承篇-学习笔记

    C++远征之继承篇 开篇介绍 整个C++远征计划: 起航->离港->封装->继承 为什么要用继承? 为什么要有继承? 如何来定义基类 <----> 派生类? 基类到派生类 ...

  5. TypeScript 入门教程学习笔记

    TypeScript 入门教程学习笔记 1. 数据类型定义 类型 实例 说明 Number let num: number = 1; 基本类型 String let myName: string = ...

  6. openresty 学习笔记番外篇:python的一些扩展库

    openresty 学习笔记番外篇:python的一些扩展库 要写一个可以使用的python程序还需要比如日志输出,读取配置文件,作为守护进程运行等 读取配置文件 使用自带的ConfigParser模 ...

  7. Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点

    Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...

  8. JS对象继承篇

    JS对象继承篇 ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的 原型链 其基本思路是利用原型让一个引用类型继承另一个引用类型的属性和方法 function Person() ...

  9. 从零开始学习jQuery (一) 入门篇

    本系列文章导航 从零开始学习jQuery (一) 入门篇 一.摘要 本系列文章将带您进入jQuery的精彩世界, 其中有很多作者具体的使用经验和解决方案,  即使你会使用jQuery也能在阅读中发现些 ...

随机推荐

  1. 201521123072《java程序设计》第八周总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 List中指定元素的删除(题目4-1) 1.1 实验总结 在删除List中的元素中要考虑元素删 ...

  2. Java程序设计第五周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 1.2 可选:使用常规方法总结其他上课内容. 2. 书面作业 **代码阅读:Child压缩包内源代码 Child.java源代 ...

  3. 20152112308 《Java程序设计》第3周学习总结

    本周学习总结 书面作业 1.代码阅读 public class Test1 { private int i = 1;//这行不能修改 private static int j = 2; public ...

  4. Servlet第三篇【request和response简介、response的常见应用】

    response.request对象 Tomcat收到客户端的http请求,会针对每一次请求,分别创建一个代表请求的request对象.和代表响应的response对象 既然request对象代表ht ...

  5. [06] Java的数据类型

    1.基本数据类型 1.1 基本数据类型 byte.chart.short.int.long.float.double.boolean 共8种数据类型为基本数据类型: 数据类型     位数     取 ...

  6. Shiro初识与总结

    1.1简介 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码学和会话管理.使用Shiro的易于理解的API,您可以快速.轻松地获得任何应用程序,从最小的移动应用程序 ...

  7. Kafka水位(high watermark)与leader epoch的讨论

    ~~~这是一篇有点长的文章,希望不会令你昏昏欲睡~~~ 本文主要讨论0.11版本之前Kafka的副本备份机制的设计问题以及0.11是如何解决的.简单来说,0.11之前副本备份机制主要依赖水位(或水印) ...

  8. struts2前后台交互

    1.前台到后台A.form提交,后台用getParameter()方法拿到数据:B.url用?+&C.Ajax使用data:{username:account,password:passwor ...

  9. JavaScript中的位置屬性

    屏幕中的位置(直接使用,無需前綴): screenLeft.screenTop:除了火狐都支持 screenX.screenY: 窗口的大小(谷歌的inner=outer,直接使用,無需前綴): in ...

  10. MVC发布网站

    首先Vs打开解决方案 在Global.asax中加入下列代码,否则会出现CSS JS失效 BundleTable.EnableOptimizations = false; 用户 'NT AUTHORI ...